commit e246eb1f17e418566054ad52fcbe0f0cc0319259 Author: Sampo Niskanen Date: Sun May 31 17:23:49 2009 +0000 Create the new trunk diff --git a/.classpath b/.classpath new file mode 100644 index 000000000..290b5a5e7 --- /dev/null +++ b/.classpath @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/.project b/.project new file mode 100644 index 000000000..977f74894 --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + OpenRocket + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 000000000..9e11801e7 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,4 @@ +2009-05-24 Sampo Niskanen + + * Initial release 0.9.0 + diff --git a/LICENSE.TXT b/LICENSE.TXT new file mode 100644 index 000000000..b8b2c1e2c --- /dev/null +++ b/LICENSE.TXT @@ -0,0 +1,700 @@ +OpenRocket - A model rocket simulator + +Copyright (C) 2007-2009 Sampo Niskanen + +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 (below) for more details. + + +Additional permission under GNU GPL version 3 section 7: + +The licensors grant additional permission to package this Program, or +any covered work, along with any non-compilable data files (such as +thrust curves or component databases) and convey the resulting work. + + +------------------------------------------------------------------------ + + + 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/README.TXT b/README.TXT new file mode 100644 index 000000000..017418e9e --- /dev/null +++ b/README.TXT @@ -0,0 +1,20 @@ + +OpenRocket - an Open Source model rocket simulator +-------------------------------------------------- + +Copyright (C) 2007-2009 Sampo Niskanen + + +For license information see the file LICENSE.TXT. + +For more information see http://openrocket.sourceforge.net/ + + + +To start the software run the class + + net.sf.openrocket.gui.main.BasicFrame + +or from the JAR file run + + $ java -jar OpenRocket-.jar diff --git a/TODO b/TODO new file mode 100644 index 000000000..a39c66479 --- /dev/null +++ b/TODO @@ -0,0 +1,126 @@ + +GUI: + +- Preferences dialog + + +BUGS: + + +COMPUTATION: + + +FILE/STORAGE: + + +OTHER: + +- web-sivut + + +DIPPA: + + + + +------------------- + +LATER: + +- Simulation delete/copy/paste hotkeys + (either component or simulation selected, but not both) +- Add BodyComponent at end of rocket when no component is selected +- Showing events in plot (maybe future) +- Search field in motor selection dialog +- Through-the-wall fins +- Store materials + +- Streamer CD estimation + +- exporting (maybe later) + +- Make ThicknessRingComponent implement RadialParent and allow + attaching components to a TubeCoupler + + + + +DONE: + +- Automatic diameters of body components +- Copy/paste + +18.4.: +- Esc, Ctrl-Z and Y etc. +- Look and feel + +19.4.: +- Nose cone and transition shoulders in GUI +- zoom, cut/copy/paste etc. icons + +23.4.: +- Figure or rocket not updating when using a new BasicFrame + +24.4.: +- File save and load +- Motor configuration editing (pre-alpha) +- Save simulations + +25.4.: +- Multi-stages simulation (pre-alpha) +- Make sure simulations end +- Mass and CG overrides (pre-alpha) +- General loader + +26.4.: +- Centering ring inner diameter automatics (pre-alpha) +- Landing simulation (pre-alpha ??) +- Parachute/Streamer editing in GUI (pre-alpha) +- Launch lug editing in GUI (pre-alpha) + +29.4.: +- Actual plotting done +- Refactored source code packages + +2.5.: +- Plotting (pre-alpha) +- Gravity model +- More units and specific custom units (angle, temperature, ...) +- Transition/Nose cone description text wrapping +- Fin set CP jumps at Mach 0.9 + +- Error dialogs for load/save/etc + +3.5.: +- More materials (pre-alpha) +- File opening from command line + +9.5.: +- Rocket configuration dialog +- Warnings in poor conditions (transition supersonic) +- New or old fin-body interference? +- poista tiedot laminaarisesta vastuksesta +- vertailuosio + +11.5.: +- Better default values for components +- Component analysis dialog show zero total mass and CG +- Compression support in save +- Simulation storage options + +12.5.: +- Load simulations +- Update file version to 1.0 + +13.5.: +- statistiikat softasta + +17.5.: +- jonkin verran TODOja +- conclusion +- viitteet +- Draw the component icons +- splashscreen + +18.5.: +- About dialog + version number diff --git a/build.xml b/build.xml new file mode 100644 index 000000000..f56396575 --- /dev/null +++ b/build.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/datafiles/thrustcurves/00INDEX.txt b/datafiles/thrustcurves/00INDEX.txt new file mode 100644 index 000000000..883761388 --- /dev/null +++ b/datafiles/thrustcurves/00INDEX.txt @@ -0,0 +1,3685 @@ +Rocket motor simulation data downloaded from ThrustCurve.org. +This ZIP file contains 526 simulator data files. +For more info, please see http://www.thrustcurve.org/ + +AMW_I195.eng + Manufacturer: Animal Motor Works + Designation: WW-38-390 + Data Format: RASP + Data Source: mfr + Contributor: John DeMar + +AMW_I220.eng + Manufacturer: Animal Motor Works + Designation: SK-38-390 + Data Format: RASP + Data Source: mfr + Contributor: John DeMar + +AMW_I271.eng + Manufacturer: Animal Motor Works + Designation: BB-38-390 + Data Format: RASP + Data Source: user + Contributor: Greg Gardner + +AMW_I285.eng + Manufacturer: Animal Motor Works + Designation: GG-38-390 + Data Format: RASP + Data Source: user + Contributor: Greg Gardner + +AMW_I315.eng + Manufacturer: Animal Motor Works + Designation: SK-38-640 + Data Format: RASP + Data Source: mfr + Contributor: Koen Loeven + +AMW_I325.eng + Manufacturer: Animal Motor Works + Designation: WW-38-640 + Data Format: RASP + Data Source: mfr + Contributor: John DeMar + +AMW_I375.eng + Manufacturer: Animal Motor Works + Designation: GG-38-640 + Data Format: RASP + Data Source: user + Contributor: Robert DeHate + +AMW_J357.eng + Manufacturer: Animal Motor Works + Designation: WT-54-1050 + Data Format: RASP + Data Source: cert + Contributor: Carl Tulanko + +AMW_J365.eng + Manufacturer: Animal Motor Works + Designation: SK-54-1400 + Data Format: RASP + Data Source: user + Contributor: Robert DeHate + +AMW_J370.eng + Manufacturer: Animal Motor Works + Designation: GG-54-1050 + Data Format: RASP + Data Source: user + Contributor: John Coker + +AMW_J400.eng + Manufacturer: Animal Motor Works + Designation: RR-54-1050 + Data Format: RASP + Data Source: user + Contributor: John Coker + +AMW_J440.eng + Manufacturer: Animal Motor Works + Designation: BB-38-640 + Data Format: RASP + Data Source: user + Contributor: Robert DeHate + +AMW_J450.eng + Manufacturer: Animal Motor Works + Designation: ST-54-1050 + Data Format: RASP + Data Source: cert + Contributor: Conway Stevens + +AMW_J450_1.eng + Manufacturer: Animal Motor Works + Designation: ST-54-1050 + Data Format: RASP + Data Source: cert + Contributor: Carl Tulanko + +AMW_J480.eng + Manufacturer: Animal Motor Works + Designation: BB-54-1050 + Data Format: RASP + Data Source: user + Contributor: John Coker + +AMW_J500.eng + Manufacturer: Animal Motor Works + Designation: J500ST + Data Format: RASP + Data Source: user + Contributor: John Coker + +AMW_K1000.eng + Manufacturer: Animal Motor Works + Designation: SK-54-2550 + Data Format: RASP + Data Source: user + Contributor: John Coker + +AMW_K1075.eng + Manufacturer: Animal Motor Works + Designation: GG-54-2550 + Data Format: RASP + Data Source: user + Contributor: John Coker + +AMW_K365.eng + Manufacturer: Animal Motor Works + Designation: RR-75-1700 + Data Format: RASP + Data Source: user + Contributor: John Coker + +AMW_K450.eng + Manufacturer: Animal Motor Works + Designation: BB-75-1700 + Data Format: RASP + Data Source: user + Contributor: John Coker + +AMW_K470.eng + Manufacturer: Animal Motor Works + Designation: ST-75-1700 + Data Format: RASP + Data Source: user + Contributor: John Coker + +AMW_K475.eng + Manufacturer: Animal Motor Works + Designation: WT-54-1400 + Data Format: RASP + Data Source: user + Contributor: John Coker + +AMW_K530.eng + Manufacturer: Animal Motor Works + Designation: GG-54-1400 + Data Format: RASP + Data Source: user + Contributor: John Coker + +AMW_K555.eng + Manufacturer: Animal Motor Works + Designation: SK-54-1750 + Data Format: RASP + Data Source: mfr + Contributor: Koen Loeven + +AMW_K560.eng + Manufacturer: Animal Motor Works + Designation: RR-54-1400 + Data Format: RASP + Data Source: user + Contributor: John Coker + +AMW_K570.eng + Manufacturer: Animal Motor Works + Designation: WT-54-1750 + Data Format: RASP + Data Source: cert + Contributor: Carl Tulanko + +AMW_K600.eng + Manufacturer: Animal Motor Works + Designation: WT-75-2500 + Data Format: RASP + Data Source: cert + Contributor: Conway Stevens + +AMW_K600_1.eng + Manufacturer: Animal Motor Works + Designation: WT-75-2500 + Data Format: RASP + Data Source: cert + Contributor: Carl Tulanko + +AMW_K605.eng + Manufacturer: Animal Motor Works + Designation: RR-75-2500 + Data Format: RASP + Data Source: user + Contributor: John Coker + +AMW_K650.eng + Manufacturer: Animal Motor Works + Designation: RR-54-1750 + Data Format: RASP + Data Source: mfr + Contributor: Koen Loeven + +AMW_K670.eng + Manufacturer: Animal Motor Works + Designation: GG-54-1750 + Data Format: RASP + Data Source: cert + Contributor: Conway Stevens + +AMW_K670_1.eng + Manufacturer: Animal Motor Works + Designation: GG-54-1750 + Data Format: RASP + Data Source: cert + Contributor: Carl Tulanko + +AMW_K700.eng + Manufacturer: Animal Motor Works + Designation: BB-54-1400 + Data Format: RASP + Data Source: user + Contributor: John Coker + +AMW_K800.eng + Manufacturer: Animal Motor Works + Designation: BB-54-1750 + Data Format: RASP + Data Source: mfr + Contributor: Koen Loeven + +AMW_K950.eng + Manufacturer: Animal Motor Works + Designation: ST-54-1750 + Data Format: RASP + Data Source: cert + Contributor: Conway Stevens + +AMW_K950_1.eng + Manufacturer: Animal Motor Works + Designation: ST-54-1750 + Data Format: RASP + Data Source: cert + Contributor: Carl Tulanko + +AMW_K975.eng + Manufacturer: Animal Motor Works + Designation: WT-54-2550 + Data Format: RASP + Data Source: cert + Contributor: Carl Tulanko + +AMW_L1060.eng + Manufacturer: Animal Motor Works + Designation: GG-75-3500 + Data Format: RASP + Data Source: cert + Contributor: Conway Stevens + +AMW_L1060_1.eng + Manufacturer: Animal Motor Works + Designation: GG-75-3500 + Data Format: RASP + Data Source: cert + Contributor: Carl Tulanko + +AMW_L1080.eng + Manufacturer: Animal Motor Works + Designation: BB-75-3500 + Data Format: RASP + Data Source: user + Contributor: John Coker + +AMW_L1100.eng + Manufacturer: Animal Motor Works + Designation: RR-54-2550 + Data Format: RASP + Data Source: user + Contributor: John Coker + +AMW_L1111.eng + Manufacturer: Animal Motor Works + Designation: ST-75-3500 + Data Format: RASP + Data Source: user + Contributor: John Coker + +AMW_L1300.eng + Manufacturer: Animal Motor Works + Designation: BB-54-2550 + Data Format: RASP + Data Source: user + Contributor: John Coker + +AMW_L1400.eng + Manufacturer: Animal Motor Works + Designation: SK-75-6000 + Data Format: RASP + Data Source: mfr + Contributor: John DeMar + +AMW_L666.eng + Manufacturer: Animal Motor Works + Designation: SK-75-3500 + Data Format: RASP + Data Source: user + Contributor: Joel Rogers + +AMW_L700.eng + Manufacturer: Animal Motor Works + Designation: BB-75-2500 + Data Format: RASP + Data Source: user + Contributor: John Coker + +AMW_L777.eng + Manufacturer: Animal Motor Works + Designation: WT-75-3500 + Data Format: RASP + Data Source: cert + Contributor: Conway Stevens + +AMW_L777_1.eng + Manufacturer: Animal Motor Works + Designation: WT-75-3500 + Data Format: RASP + Data Source: cert + Contributor: Carl Tulanko + +AMW_L900.eng + Manufacturer: Animal Motor Works + Designation: RR-75-3500 + Data Format: RASP + Data Source: user + Contributor: John Coker + +AMW_M1350.eng + Manufacturer: Animal Motor Works + Designation: WT-75-6000 + Data Format: RASP + Data Source: cert + Contributor: Carl Tulanko + +AMW_M1480.eng + Manufacturer: Animal Motor Works + Designation: RR-75-6000 + Data Format: RASP + Data Source: user + Contributor: John Coker + +AMW_M1730.eng + Manufacturer: Animal Motor Works + Designation: SK-98-11000 + Data Format: RASP + Data Source: mfr + Contributor: Joel Rogers + +AMW_M1850.eng + Manufacturer: Animal Motor Works + Designation: GG-75-6000 + Data Format: RASP + Data Source: cert + Contributor: Conway Stevens + +AMW_M1850_1.eng + Manufacturer: Animal Motor Works + Designation: GG-75-6000 + Data Format: RASP + Data Source: cert + Contributor: Carl Tulanko + +AMW_M1900.eng + Manufacturer: Animal Motor Works + Designation: BB-75-6000 + Data Format: RASP + Data Source: user + Contributor: John Coker + +AMW_M2500.eng + Manufacturer: Animal Motor Works + Designation: GG-75-7600 + Data Format: RASP + Data Source: cert + Contributor: Carl Tulanko + +AMW_M3000.eng + Manufacturer: Animal Motor Works + Designation: ST-75-7600 + Data Format: RASP + Data Source: cert + Contributor: Conway Stevens + +AMW_N2020.eng + Manufacturer: Animal Motor Works + Designation: WT-98-11000 + Data Format: RASP + Data Source: user + Contributor: Joel Rogers + +AMW_N2600.eng + Manufacturer: Animal Motor Works + Designation: GG-98-11000 + Data Format: RASP + Data Source: user + Contributor: John Coker + +AMW_N2700.eng + Manufacturer: Animal Motor Works + Designation: BB-98-11000 + Data Format: RASP + Data Source: user + Contributor: John Coker + +AMW_N2800.eng + Manufacturer: Animal Motor Works + Designation: WW-98-17500 + Data Format: RASP + Data Source: mfr + Contributor: John DeMar + +AMW_N4000.eng + Manufacturer: Animal Motor Works + Designation: BB-98-17500 + Data Format: RASP + Data Source: user + Contributor: Robert DeHate + +AeroTech_D13.eng + Manufacturer: AeroTech + Designation: D13 + Data Format: RASP + Data Source: cert + Contributor: Christopher Kobel + +AeroTech_D15.eng + Manufacturer: AeroTech + Designation: D15 + Data Format: RASP + Data Source: cert + Contributor: Christopher Kobel + +AeroTech_D21.eng + Manufacturer: AeroTech + Designation: D21 + Data Format: RASP + Data Source: cert + Contributor: Christopher Kobel + +AeroTech_D24.eng + Manufacturer: AeroTech + Designation: D24 + Data Format: RASP + Data Source: user + Contributor: Stan Hemphill + +AeroTech_D7.eng + Manufacturer: AeroTech + Designation: D7 + Data Format: RASP + Data Source: user + Contributor: John Coker + +AeroTech_D9.eng + Manufacturer: AeroTech + Designation: D9 + Data Format: RASP + Data Source: cert + Contributor: Christopher Kobel + +AeroTech_E11.eng + Manufacturer: AeroTech + Designation: E11J + Data Format: RASP + Data Source: user + Contributor: John Coker + +AeroTech_E12.eng + Manufacturer: AeroTech + Designation: E12J + Data Format: RASP + Data Source: user + Contributor: John Coker + +AeroTech_E15.eng + Manufacturer: AeroTech + Designation: E15 + Data Format: RASP + Data Source: cert + Contributor: Mark Koelsch + +AeroTech_E15_1.eng + Manufacturer: AeroTech + Designation: E15 + Data Format: RASP + Data Source: cert + Contributor: Christopher Kobel + +AeroTech_E16.eng + Manufacturer: AeroTech + Designation: E16 + Data Format: RASP + Data Source: cert + Contributor: Christopher Kobel + +AeroTech_E18.eng + Manufacturer: AeroTech + Designation: E18 + Data Format: RASP + Data Source: cert + Contributor: Christopher Kobel + +AeroTech_E23.eng + Manufacturer: AeroTech + Designation: E23 + Data Format: RASP + Data Source: cert + Contributor: Christopher Kobel + +AeroTech_E28.eng + Manufacturer: AeroTech + Designation: E28 + Data Format: RASP + Data Source: cert + Contributor: Christopher Kobel + +AeroTech_E30.eng + Manufacturer: AeroTech + Designation: E30 + Data Format: RASP + Data Source: cert + Contributor: Christopher Kobel + +AeroTech_E6.eng + Manufacturer: AeroTech + Designation: E6 + Data Format: RASP + Data Source: cert + Contributor: Mark Koelsch + +AeroTech_E7.eng + Manufacturer: AeroTech + Designation: E7 + Data Format: RASP + Data Source: user + Contributor: John Coker + +AeroTech_F10.eng + Manufacturer: AeroTech + Designation: F10 + Data Format: RASP + Data Source: cert + Contributor: Mark Koelsch + +AeroTech_F12.eng + Manufacturer: AeroTech + Designation: F12 + Data Format: RASP + Data Source: cert + Contributor: Christopher Kobel + +AeroTech_F13.eng + Manufacturer: AeroTech + Designation: F13-RC + Data Format: RASP + Data Source: user + Contributor: John Coker + +AeroTech_F16.eng + Manufacturer: AeroTech + Designation: F16-RC + Data Format: RASP + Data Source: user + Contributor: John Coker + +AeroTech_F20.eng + Manufacturer: AeroTech + Designation: F20 + Data Format: RASP + Data Source: user + Contributor: John Coker + +AeroTech_F21.eng + Manufacturer: AeroTech + Designation: F21W + Data Format: RASP + Data Source: user + Contributor: Stan Hemphill + +AeroTech_F22.eng + Manufacturer: AeroTech + Designation: F22 + Data Format: RASP + Data Source: user + Contributor: John Coker + +AeroTech_F23.eng + Manufacturer: AeroTech + Designation: F23FJ + Data Format: RASP + Data Source: user + Contributor: John Coker + +AeroTech_F23_1.eng + Manufacturer: AeroTech + Designation: F23-RC-SK + Data Format: RASP + Data Source: user + Contributor: John Coker + +AeroTech_F24.eng + Manufacturer: AeroTech + Designation: F24 + Data Format: RASP + Data Source: cert + Contributor: Christopher Kobel + +AeroTech_F25.eng + Manufacturer: AeroTech + Designation: F25W + Data Format: RASP + Data Source: user + Contributor: John Coker + +AeroTech_F26.eng + Manufacturer: AeroTech + Designation: F26FJ + Data Format: RASP + Data Source: user + Contributor: John Coker + +AeroTech_F27.eng + Manufacturer: AeroTech + Designation: F27R + Data Format: RASP + Data Source: user + Contributor: John Coker + +AeroTech_F32.eng + Manufacturer: AeroTech + Designation: F32 + Data Format: RASP + Data Source: user + Contributor: John Coker + +AeroTech_F35.eng + Manufacturer: AeroTech + Designation: F35W + Data Format: RASP + Data Source: mfr + Contributor: Christopher Kobel + +AeroTech_F37.eng + Manufacturer: AeroTech + Designation: F37 + Data Format: RASP + Data Source: user + Contributor: John Coker + +AeroTech_F39.eng + Manufacturer: AeroTech + Designation: F39 + Data Format: RASP + Data Source: cert + Contributor: Christopher Kobel + +AeroTech_F40.eng + Manufacturer: AeroTech + Designation: F40 + Data Format: RASP + Data Source: user + Contributor: John Coker + +AeroTech_F42.eng + Manufacturer: AeroTech + Designation: F42T + Data Format: RASP + Data Source: user + Contributor: John Coker + +AeroTech_F50.eng + Manufacturer: AeroTech + Designation: F50 + Data Format: RASP + Data Source: user + Contributor: John Coker + +AeroTech_F52.eng + Manufacturer: AeroTech + Designation: F52 + Data Format: RASP + Data Source: user + Contributor: John Coker + +AeroTech_F62.eng + Manufacturer: AeroTech + Designation: F62T + Data Format: RASP + Data Source: user + Contributor: Stan Hemphill + +AeroTech_F72.eng + Manufacturer: AeroTech + Designation: F72 + Data Format: RASP + Data Source: user + Contributor: John Coker + +AeroTech_G101.eng + Manufacturer: AeroTech + Designation: G101T + Data Format: RASP + Data Source: cert + Contributor: Mark Koelsch + +AeroTech_G104.eng + Manufacturer: AeroTech + Designation: G104T + Data Format: RASP + Data Source: user + Contributor: Stan Hemphill + +AeroTech_G12.eng + Manufacturer: AeroTech + Designation: G12-RC + Data Format: RASP + Data Source: user + Contributor: John Coker + +AeroTech_G25.eng + Manufacturer: AeroTech + Designation: G25 + Data Format: RASP + Data Source: user + Contributor: John Coker + +AeroTech_G33.eng + Manufacturer: AeroTech + Designation: G33 + Data Format: RASP + Data Source: user + Contributor: John Coker + +AeroTech_G339.eng + Manufacturer: AeroTech + Designation: G339N-P + Data Format: RASP + Data Source: cert + Contributor: Bill Wagstaff + +AeroTech_G35.eng + Manufacturer: AeroTech + Designation: G35 + Data Format: RASP + Data Source: user + Contributor: John Coker + +AeroTech_G38.eng + Manufacturer: AeroTech + Designation: G38FJ + Data Format: RASP + Data Source: user + Contributor: John Coker + +AeroTech_G40.eng + Manufacturer: AeroTech + Designation: G40W + Data Format: RASP + Data Source: user + Contributor: John Coker + +AeroTech_G53.eng + Manufacturer: AeroTech + Designation: G53FJ + Data Format: RASP + Data Source: mfr + Contributor: Christopher Kobel + +AeroTech_G54.eng + Manufacturer: AeroTech + Designation: G54 + Data Format: RASP + Data Source: user + Contributor: John Coker + +AeroTech_G55.eng + Manufacturer: AeroTech + Designation: G55 + Data Format: RASP + Data Source: user + Contributor: John Coker + +AeroTech_G61.eng + Manufacturer: AeroTech + Designation: G61W + Data Format: RASP + Data Source: user + Contributor: John Coker + +AeroTech_G64.eng + Manufacturer: AeroTech + Designation: G64 + Data Format: RASP + Data Source: user + Contributor: John Coker + +AeroTech_G67.eng + Manufacturer: AeroTech + Designation: G67R + Data Format: RASP + Data Source: user + Contributor: Stan Hemphill + +AeroTech_G69.eng + Manufacturer: AeroTech + Designation: G69N + Data Format: RASP + Data Source: mfr + Contributor: Christopher Kobel + +AeroTech_G71.eng + Manufacturer: AeroTech + Designation: G71R + Data Format: RASP + Data Source: mfr + Contributor: Christopher Kobel + +AeroTech_G71_1.eng + Manufacturer: AeroTech + Designation: G71R + Data Format: RASP + Data Source: mfr + Contributor: Edward K. Chess + +AeroTech_G75.eng + Manufacturer: AeroTech + Designation: G75J + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_G75_1.eng + Manufacturer: AeroTech + Designation: G75J + Data Format: RASP + Data Source: user + Contributor: Stan Hemphill + +AeroTech_G76.eng + Manufacturer: AeroTech + Designation: G76G + Data Format: RASP + Data Source: mfr + Contributor: Christopher Kobel + +AeroTech_G76_1.eng + Manufacturer: AeroTech + Designation: G76G + Data Format: RASP + Data Source: cert + Contributor: John DeMar + +AeroTech_G77.eng + Manufacturer: AeroTech + Designation: G77R + Data Format: RASP + Data Source: user + Contributor: Stan Hemphill + +AeroTech_G78.eng + Manufacturer: AeroTech + Designation: G78G + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_G79.eng + Manufacturer: AeroTech + Designation: G79W + Data Format: RASP + Data Source: user + Contributor: John Coker + +AeroTech_G80.eng + Manufacturer: AeroTech + Designation: G80 + Data Format: RASP + Data Source: cert + Contributor: John DeMar + +AeroTech_G80_1.eng + Manufacturer: AeroTech + Designation: G80 + Data Format: RASP + Data Source: cert + Contributor: John DeMar + +AeroTech_G80_2.eng + Manufacturer: AeroTech + Designation: G80 + Data Format: RASP + Data Source: cert + Contributor: John DeMar + +AeroTech_H112.eng + Manufacturer: AeroTech + Designation: H112J + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_H123.eng + Manufacturer: AeroTech + Designation: H123W + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_H125.eng + Manufacturer: AeroTech + Designation: H125W + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_H128.eng + Manufacturer: AeroTech + Designation: H128W + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_H148.eng + Manufacturer: AeroTech + Designation: H148R + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_H165.eng + Manufacturer: AeroTech + Designation: H165R + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_H180.eng + Manufacturer: AeroTech + Designation: H180W + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_H210.eng + Manufacturer: AeroTech + Designation: H210R + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_H220.eng + Manufacturer: AeroTech + Designation: H220T + Data Format: RASP + Data Source: user + Contributor: John Coker + +AeroTech_H238.eng + Manufacturer: AeroTech + Designation: H238T + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_H242.eng + Manufacturer: AeroTech + Designation: H242T + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_H242_1.eng + Manufacturer: AeroTech + Designation: H242T + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_H250.eng + Manufacturer: AeroTech + Designation: H250G + Data Format: RASP + Data Source: mfr + Contributor: Jim Yehle + +AeroTech_H268.eng + Manufacturer: AeroTech + Designation: H268R + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_H45.eng + Manufacturer: AeroTech + Designation: H45W + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_H55.eng + Manufacturer: AeroTech + Designation: H55W + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_H669.eng + Manufacturer: AeroTech + Designation: H669N-P + Data Format: RASP + Data Source: user + Contributor: Greg Gardner + +AeroTech_H70.eng + Manufacturer: AeroTech + Designation: H70W + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_H73.eng + Manufacturer: AeroTech + Designation: H73J + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_H97.eng + Manufacturer: AeroTech + Designation: H97J + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_H999.eng + Manufacturer: AeroTech + Designation: H999 + Data Format: RASP + Data Source: user + Contributor: Greg Gardner + +AeroTech_I115.eng + Manufacturer: AeroTech + Designation: I115W + Data Format: RASP + Data Source: cert + Contributor: Mark Koelsch + +AeroTech_I117.eng + Manufacturer: AeroTech + Designation: I117FJ + Data Format: RASP + Data Source: cert + Contributor: Mark Koelsch + +AeroTech_I1299.eng + Manufacturer: AeroTech + Designation: I1299N-P + Data Format: RASP + Data Source: user + Contributor: Jim Yehle + +AeroTech_I132.eng + Manufacturer: AeroTech + Designation: I132W + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_I154.eng + Manufacturer: AeroTech + Designation: I154J + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_I161.eng + Manufacturer: AeroTech + Designation: I161W + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_I195.eng + Manufacturer: AeroTech + Designation: I195J + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_I195_1.eng + Manufacturer: AeroTech + Designation: I195J + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_I200.eng + Manufacturer: AeroTech + Designation: I200W + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_I211.eng + Manufacturer: AeroTech + Designation: I211W + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_I215.eng + Manufacturer: AeroTech + Designation: I215R + Data Format: RASP + Data Source: cert + Contributor: Mark Koelsch + +AeroTech_I218.eng + Manufacturer: AeroTech + Designation: I218R + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_I225.eng + Manufacturer: AeroTech + Designation: I225FJ + Data Format: RASP + Data Source: mfr + Contributor: Christopher Kobel + +AeroTech_I229.eng + Manufacturer: AeroTech + Designation: I229T + Data Format: RASP + Data Source: cert + Contributor: Mark Koelsch + +AeroTech_I245.eng + Manufacturer: AeroTech + Designation: I245G + Data Format: RASP + Data Source: mfr + Contributor: Jim Yehle + +AeroTech_I284.eng + Manufacturer: AeroTech + Designation: I284W + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_I284_1.eng + Manufacturer: AeroTech + Designation: I284W + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_I285.eng + Manufacturer: AeroTech + Designation: I285R + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_I300.eng + Manufacturer: AeroTech + Designation: I300T + Data Format: RASP + Data Source: user + Contributor: John Coker + +AeroTech_I305.eng + Manufacturer: AeroTech + Designation: I305FJ + Data Format: RASP + Data Source: mfr + Contributor: Christopher Kobel + +AeroTech_I357.eng + Manufacturer: AeroTech + Designation: I357T + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_I364.eng + Manufacturer: AeroTech + Designation: I364FJ + Data Format: RASP + Data Source: mfr + Contributor: Christopher Kobel + +AeroTech_I366.eng + Manufacturer: AeroTech + Designation: I366R + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_I435.eng + Manufacturer: AeroTech + Designation: I435T + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_I435_1.eng + Manufacturer: AeroTech + Designation: I435T + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_I599.eng + Manufacturer: AeroTech + Designation: I599N + Data Format: RASP + Data Source: cert + Contributor: Mark Koelsch + +AeroTech_I600.eng + Manufacturer: AeroTech + Designation: I600R + Data Format: RASP + Data Source: user + Contributor: John Coker + +AeroTech_I65.eng + Manufacturer: AeroTech + Designation: I65W + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_J125.eng + Manufacturer: AeroTech + Designation: J125W + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_J1299.eng + Manufacturer: AeroTech + Designation: J1299N-P + Data Format: RASP + Data Source: user + Contributor: Greg Gardner + +AeroTech_J135.eng + Manufacturer: AeroTech + Designation: J135W + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_J145.eng + Manufacturer: AeroTech + Designation: J145H 2-jet std. + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_J180.eng + Manufacturer: AeroTech + Designation: J180T + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_J1999.eng + Manufacturer: AeroTech + Designation: J1999N-P + Data Format: RASP + Data Source: user + Contributor: Greg Gardner + +AeroTech_J210.eng + Manufacturer: AeroTech + Designation: J210H 4-jet std. + Data Format: RASP + Data Source: user + Contributor: John Coker + +AeroTech_J250.eng + Manufacturer: AeroTech + Designation: J250FJ + Data Format: RASP + Data Source: cert + Contributor: Mark Koelsch + +AeroTech_J260.eng + Manufacturer: AeroTech + Designation: J260HW 3-jet EFX + Data Format: RASP + Data Source: user + Contributor: John Coker + +AeroTech_J275.eng + Manufacturer: AeroTech + Designation: J275W + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_J315.eng + Manufacturer: AeroTech + Designation: J315R + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_J350.eng + Manufacturer: AeroTech + Designation: J350W-L + Data Format: RASP + Data Source: user + Contributor: John Coker + +AeroTech_J350_1.eng + Manufacturer: AeroTech + Designation: J350W + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_J390.eng + Manufacturer: AeroTech + Designation: J390-turbo + Data Format: RASP + Data Source: user + Contributor: John Coker + +AeroTech_J415.eng + Manufacturer: AeroTech + Designation: J415W + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_J420.eng + Manufacturer: AeroTech + Designation: J420R + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_J460.eng + Manufacturer: AeroTech + Designation: J460T + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_J500.eng + Manufacturer: AeroTech + Designation: J500G + Data Format: RASP + Data Source: mfr + Contributor: Jim Yehle + +AeroTech_J540.eng + Manufacturer: AeroTech + Designation: J540R + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_J570.eng + Manufacturer: AeroTech + Designation: J570W + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_J575.eng + Manufacturer: AeroTech + Designation: J575FJ + Data Format: RASP + Data Source: cert + Contributor: Simon Crafts + +AeroTech_J800.eng + Manufacturer: AeroTech + Designation: J800T-PS + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_J825.eng + Manufacturer: AeroTech + Designation: J825R + Data Format: RASP + Data Source: user + Contributor: Greg Gardner + +AeroTech_J90.eng + Manufacturer: AeroTech + Designation: J90W + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_K1050.eng + Manufacturer: AeroTech + Designation: K1050W + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_K1100.eng + Manufacturer: AeroTech + Designation: K1100T + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_K1275.eng + Manufacturer: AeroTech + Designation: K1275 + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_K1499.eng + Manufacturer: AeroTech + Designation: K1499N-P + Data Format: RASP + Data Source: user + Contributor: Jim Yehle + +AeroTech_K185.eng + Manufacturer: AeroTech + Designation: K185W + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_K1999.eng + Manufacturer: AeroTech + Designation: K1999N-P + Data Format: RASP + Data Source: mfr + Contributor: Christopher Kobel + +AeroTech_K250.eng + Manufacturer: AeroTech + Designation: K250W + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_K270.eng + Manufacturer: AeroTech + Designation: K270W + Data Format: RASP + Data Source: cert + Contributor: Mark Koelsch + +AeroTech_K458.eng + Manufacturer: AeroTech + Designation: K458W + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_K485.eng + Manufacturer: AeroTech + Designation: K485H (3 jet) + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_K550.eng + Manufacturer: AeroTech + Designation: K550W + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_K560.eng + Manufacturer: AeroTech + Designation: K560W + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_K650.eng + Manufacturer: AeroTech + Designation: K650T + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_K680.eng + Manufacturer: AeroTech + Designation: K680R + Data Format: RASP + Data Source: user + Contributor: John Coker + +AeroTech_K695.eng + Manufacturer: AeroTech + Designation: K695R + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_K700.eng + Manufacturer: AeroTech + Designation: K700W + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_K780.eng + Manufacturer: AeroTech + Designation: K780R + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_K828.eng + Manufacturer: AeroTech + Designation: K828FJ + Data Format: RASP + Data Source: cert + Contributor: Mark Koelsch + +AeroTech_L1120.eng + Manufacturer: AeroTech + Designation: L1120W + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_L1150.eng + Manufacturer: AeroTech + Designation: L1150R + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_L1300.eng + Manufacturer: AeroTech + Designation: L1300R + Data Format: RASP + Data Source: user + Contributor: John Coker + +AeroTech_L1420.eng + Manufacturer: AeroTech + Designation: L1420R + Data Format: RASP + Data Source: user + Contributor: John Coker + +AeroTech_L1500.eng + Manufacturer: AeroTech + Designation: L1500T + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_L850.eng + Manufacturer: AeroTech + Designation: L850W + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_L952.eng + Manufacturer: AeroTech + Designation: L952W + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_M1297.eng + Manufacturer: AeroTech + Designation: M1297W + Data Format: RASP + Data Source: user + Contributor: Greg Gardner + +AeroTech_M1315.eng + Manufacturer: AeroTech + Designation: M1315W + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_M1419.eng + Manufacturer: AeroTech + Designation: M1419W + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_M1550.eng + Manufacturer: AeroTech + Designation: M1550R + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_M1600.eng + Manufacturer: AeroTech + Designation: M1600R + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_M1850.eng + Manufacturer: AeroTech + Designation: M1850W-PS + Data Format: RASP + Data Source: user + Contributor: Greg Gardner + +AeroTech_M1939.eng + Manufacturer: AeroTech + Designation: M1939W + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_M2000.eng + Manufacturer: AeroTech + Designation: M2000R + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_M2400.eng + Manufacturer: AeroTech + Designation: M2400T + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_M2500.eng + Manufacturer: AeroTech + Designation: M2500T + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_M650.eng + Manufacturer: AeroTech + Designation: M650W + Data Format: RASP + Data Source: user + Contributor: Greg Gardner + +AeroTech_M750.eng + Manufacturer: AeroTech + Designation: M750W + Data Format: RASP + Data Source: user + Contributor: Greg Gardner + +AeroTech_M845.eng + Manufacturer: AeroTech + Designation: M845 + Data Format: RASP + Data Source: user + Contributor: John Coker + +AeroTech_N2000.eng + Manufacturer: AeroTech + Designation: N2000W + Data Format: RASP + Data Source: cert + Contributor: John Coker + +AeroTech_N4800.eng + Manufacturer: AeroTech + Designation: N4800T + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Apogee_1/2A2.eng + Manufacturer: Apogee Components + Designation: 1/2A2 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Apogee_1/4A2.eng + Manufacturer: Apogee Components + Designation: 1/4A2 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Apogee_A2.eng + Manufacturer: Apogee Components + Designation: A2 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Apogee_B2.eng + Manufacturer: Apogee Components + Designation: B2 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Apogee_B7.eng + Manufacturer: Apogee Components + Designation: B7 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Apogee_C10.eng + Manufacturer: Apogee Components + Designation: C10 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Apogee_C4.eng + Manufacturer: Apogee Components + Designation: C4 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Apogee_C6.eng + Manufacturer: Apogee Components + Designation: C6 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Apogee_D10.eng + Manufacturer: Apogee Components + Designation: D10 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Apogee_D3.eng + Manufacturer: Apogee Components + Designation: D3 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Apogee_E6.eng + Manufacturer: Apogee Components + Designation: E6 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Apogee_F10.eng + Manufacturer: Apogee Components + Designation: F10 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Cesaroni_G60.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 134 G60-14A + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Cesaroni_G69.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 121 G69-14A + Data Format: RASP + Data Source: user + Contributor: Pete Carr + +Cesaroni_G69_1.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 121 G69-14A + Data Format: RASP + Data Source: mfr + Contributor: Casey Hatch + +Cesaroni_G79.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 129 G79SS-13A + Data Format: RASP + Data Source: user + Contributor: Pete Carr + +Cesaroni_G79_1.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 129 G79SS-13A + Data Format: RASP + Data Source: mfr + Contributor: Casey Hatch + +Cesaroni_H120.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 261 H120-14A + Data Format: RASP + Data Source: cert + Contributor: Len Bryan + +Cesaroni_H143.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 247 H143SS-13A + Data Format: RASP + Data Source: mfr + Contributor: Casey Hatch + +Cesaroni_H153.eng + Manufacturer: Cesaroni Technology Inc. + Designation: H153 + Data Format: RASP + Data Source: mfr + Contributor: Casey Hatch + +Cesaroni_H565.eng + Manufacturer: Cesaroni Technology Inc. + Designation: H565 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Cesaroni_I170.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 382 I170-14A + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Cesaroni_I205.eng + Manufacturer: Cesaroni Technology Inc. + Designation: I205 + Data Format: RASP + Data Source: mfr + Contributor: Casey Hatch + +Cesaroni_I212.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 364 I212SS-14A + Data Format: RASP + Data Source: mfr + Contributor: Casey Hatch + +Cesaroni_I240.eng + Manufacturer: Cesaroni Technology Inc. + Designation: I240 + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Cesaroni_I285.eng + Manufacturer: Cesaroni Technology Inc. + Designation: I285 + Data Format: RASP + Data Source: mfr + Contributor: Casey Hatch + +Cesaroni_I287.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 486 I287SS-15A + Data Format: RASP + Data Source: mfr + Contributor: Casey Hatch + +Cesaroni_I350.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 601 I350SS-16A + Data Format: RASP + Data Source: mfr + Contributor: Casey Hatch + +Cesaroni_I360.eng + Manufacturer: Cesaroni Technology Inc. + Designation: I360 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Cesaroni_I540.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 634I540WT + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Cesaroni_J210.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 836 J210-16A + Data Format: RASP + Data Source: mfr + Contributor: Casey Hatch + +Cesaroni_J280.eng + Manufacturer: Cesaroni Technology Inc. + Designation: J280SS + Data Format: RASP + Data Source: mfr + Contributor: Casey Hatch + +Cesaroni_J285.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 648 J285-15A + Data Format: RASP + Data Source: mfr + Contributor: Casey Hatch + +Cesaroni_J295.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 1195 J295-15A + Data Format: RASP + Data Source: mfr + Contributor: Casey Hatch + +Cesaroni_J300.eng + Manufacturer: Cesaroni Technology Inc. + Designation: J300 + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Cesaroni_J330.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 765 J330-16A + Data Format: RASP + Data Source: mfr + Contributor: Casey Hatch + +Cesaroni_J360.eng + Manufacturer: Cesaroni Technology Inc. + Designation: J360 + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Cesaroni_J380.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 1043 J380SS-16A + Data Format: RASP + Data Source: mfr + Contributor: Casey Hatch + +Cesaroni_J400.eng + Manufacturer: Cesaroni Technology Inc. + Designation: J400SS + Data Format: RASP + Data Source: mfr + Contributor: Casey Hatch + +Cesaroni_J410.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 774 J410-16A + Data Format: RASP + Data Source: cert + Contributor: Len Bryan + +Cesaroni_K445.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 1635 K445-A + Data Format: RASP + Data Source: mfr + Contributor: Casey Hatch + +Cesaroni_K510.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 2486 K510-P-U + Data Format: RASP + Data Source: mfr + Contributor: Len Lekx + +Cesaroni_K510_1.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 2486 K510-P-U + Data Format: RASP + Data Source: mfr + Contributor: Casey Hatch + +Cesaroni_K530.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 1412 K530SS-16A + Data Format: RASP + Data Source: mfr + Contributor: Casey Hatch + +Cesaroni_K570.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 2060 K570-A + Data Format: RASP + Data Source: mfr + Contributor: Casey Hatch + +Cesaroni_K575.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 2493 K575-P + Data Format: RASP + Data Source: user + Contributor: John Coker + +Cesaroni_K650.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 1750 K650SS-16A + Data Format: RASP + Data Source: mfr + Contributor: Casey Hatch + +Cesaroni_K660.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 2437 K660-17A + Data Format: RASP + Data Source: mfr + Contributor: Casey Hatch + +Cesaroni_L1090.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 4815 L1090-P + Data Format: RASP + Data Source: user + Contributor: John Coker + +Cesaroni_L1115.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 5015 L1115-P-U + Data Format: RASP + Data Source: mfr + Contributor: Len Lekx + +Cesaroni_L1115_1.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 5015 L1115-P-U + Data Format: RASP + Data Source: mfr + Contributor: Casey Hatch + +Cesaroni_L610.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 4842 L610-P + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Cesaroni_L730.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 2765 L730-P + Data Format: RASP + Data Source: mfr + Contributor: Casey Hatch + +Cesaroni_L800.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 3757 L800-P-U + Data Format: RASP + Data Source: mfr + Contributor: Len Lekx + +Cesaroni_L800_1.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 3757 L800-P-U + Data Format: RASP + Data Source: mfr + Contributor: Casey Hatch + +Cesaroni_L890.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 3762 L890-P + Data Format: RASP + Data Source: user + Contributor: John Coker + +Cesaroni_M1060.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 7441 M1060-P + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Cesaroni_M1400.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 6251 M1400-P-U + Data Format: RASP + Data Source: mfr + Contributor: Len Lekx + +Cesaroni_M1400_1.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 6251 M1400-P-U + Data Format: RASP + Data Source: mfr + Contributor: Casey Hatch + +Cesaroni_M1450.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 9955 M1450-P + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Cesaroni_M2505.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 7450 M2505-P + Data Format: RASP + Data Source: mfr + Contributor: Casey Hatch + +Cesaroni_M520.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 7400 M520-P + Data Format: RASP + Data Source: mfr + Contributor: Casey Hatch + +Cesaroni_M795.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 10133 M795-P + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Cesaroni_N1100.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 14005 N1100-P + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Cesaroni_N2500.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 13766 N2500-P + Data Format: RASP + Data Source: mfr + Contributor: Casey Hatch + +Cesaroni_O5100.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 29990 O5100-P + Data Format: RASP + Data Source: user + Contributor: John Coker + +Cesaroni_O5800.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 30605 O5800-P + Data Format: RASP + Data Source: cert + Contributor: Len Bryan + +Cesaroni_O8000.eng + Manufacturer: Cesaroni Technology Inc. + Designation: 40960 O8000-P + Data Format: RASP + Data Source: cert + Contributor: Len Bryan + +Contrail_G100.eng + Manufacturer: Contrail Rockets LLC + Designation: G100-PVC + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_G123.eng + Manufacturer: Contrail Rockets LLC + Designation: G123-HP + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_G130.eng + Manufacturer: Contrail Rockets LLC + Designation: G130-PVC + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_G234.eng + Manufacturer: Contrail Rockets LLC + Designation: G234-HP + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_G300.eng + Manufacturer: Contrail Rockets LLC + Designation: G300-PVC + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_H121.eng + Manufacturer: Contrail Rockets LLC + Designation: H121-HP + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_H141.eng + Manufacturer: Contrail Rockets LLC + Designation: H141-PVC + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_H211.eng + Manufacturer: Contrail Rockets LLC + Designation: H211-PVC + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_H222.eng + Manufacturer: Contrail Rockets LLC + Designation: H222-HP + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_H246.eng + Manufacturer: Contrail Rockets LLC + Designation: H246-HP + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_H277.eng + Manufacturer: Contrail Rockets LLC + Designation: H277-HP + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_H300.eng + Manufacturer: Contrail Rockets LLC + Designation: H300-HP + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_H303.eng + Manufacturer: Contrail Rockets LLC + Designation: H303-PVC + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_H340.eng + Manufacturer: Contrail Rockets LLC + Designation: H340-SP + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_I155.eng + Manufacturer: Contrail Rockets LLC + Designation: I155-HP + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_I210.eng + Manufacturer: Contrail Rockets LLC + Designation: I210-PVC + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_I221.eng + Manufacturer: Contrail Rockets LLC + Designation: I221-PVC + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_I290.eng + Manufacturer: Contrail Rockets LLC + Designation: I290-SP + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_I307.eng + Manufacturer: Contrail Rockets LLC + Designation: I307-HP + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_I333.eng + Manufacturer: Contrail Rockets LLC + Designation: I333-PVC + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_I400.eng + Manufacturer: Contrail Rockets LLC + Designation: I400-HP + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_I500.eng + Manufacturer: Contrail Rockets LLC + Designation: I500-HP + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_I727.eng + Manufacturer: Contrail Rockets LLC + Designation: I727-HP + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_I747.eng + Manufacturer: Contrail Rockets LLC + Designation: I747-HP + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_J150.eng + Manufacturer: Contrail Rockets LLC + Designation: J150-HP + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_J222.eng + Manufacturer: Contrail Rockets LLC + Designation: J222-HP + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_J234.eng + Manufacturer: Contrail Rockets LLC + Designation: J234-BG + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_J242.eng + Manufacturer: Contrail Rockets LLC + Designation: J242-PVC + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_J245.eng + Manufacturer: Contrail Rockets LLC + Designation: J245-BG + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_J246.eng + Manufacturer: Contrail Rockets LLC + Designation: J246-HP + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_J272.eng + Manufacturer: Contrail Rockets LLC + Designation: J272-SP + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_J292.eng + Manufacturer: Contrail Rockets LLC + Designation: J292-SP + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_J333.eng + Manufacturer: Contrail Rockets LLC + Designation: J333-HP + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_J345.eng + Manufacturer: Contrail Rockets LLC + Designation: J345-PVC + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_J355.eng + Manufacturer: Contrail Rockets LLC + Designation: J355-BG + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_J358.eng + Manufacturer: Contrail Rockets LLC + Designation: J358-BG + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_J416.eng + Manufacturer: Contrail Rockets LLC + Designation: J416-SP + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_J555.eng + Manufacturer: Contrail Rockets LLC + Designation: J555-HP + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_J642.eng + Manufacturer: Contrail Rockets LLC + Designation: J642-BG + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_J800.eng + Manufacturer: Contrail Rockets LLC + Designation: J800-HP + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_K234.eng + Manufacturer: Contrail Rockets LLC + Designation: K234-BG + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_K265.eng + Manufacturer: Contrail Rockets LLC + Designation: K265-SP + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_K300.eng + Manufacturer: Contrail Rockets LLC + Designation: K300-BS + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_K321.eng + Manufacturer: Contrail Rockets LLC + Designation: K321-BG + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_K404.eng + Manufacturer: Contrail Rockets LLC + Designation: K404-SP + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_K456.eng + Manufacturer: Contrail Rockets LLC + Designation: K456-BG + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_K630.eng + Manufacturer: Contrail Rockets LLC + Designation: K630-SP + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_K678.eng + Manufacturer: Contrail Rockets LLC + Designation: K678-BG + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_K707.eng + Manufacturer: Contrail Rockets LLC + Designation: K707-BG + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_K777.eng + Manufacturer: Contrail Rockets LLC + Designation: K777-SP + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_L1222.eng + Manufacturer: Contrail Rockets LLC + Designation: L1222-SM + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_L2525.eng + Manufacturer: Contrail Rockets LLC + Designation: L2525-GF + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_L369.eng + Manufacturer: Contrail Rockets LLC + Designation: L369-SP + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_L800.eng + Manufacturer: Contrail Rockets LLC + Designation: L800-BG + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_M1575.eng + Manufacturer: Contrail Rockets LLC + Designation: M1575-BG + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_M2700.eng + Manufacturer: Contrail Rockets LLC + Designation: M2700-BS + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_M2800.eng + Manufacturer: Contrail Rockets LLC + Designation: M2800-BG + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_M711.eng + Manufacturer: Contrail Rockets LLC + Designation: M711-BS + Data Format: RASP + Data Source: user + Contributor: John Coker + +Contrail_O6300.eng + Manufacturer: Contrail Rockets LLC + Designation: O6300-BS + Data Format: RASP + Data Source: user + Contributor: John Coker + +Ellis_G20.eng + Manufacturer: Ellis Mountain Rocket Works + Designation: G20 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Ellis_G35.eng + Manufacturer: Ellis Mountain Rocket Works + Designation: G35 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Ellis_G37.eng + Manufacturer: Ellis Mountain Rocket Works + Designation: G37 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Ellis_H275.eng + Manufacturer: Ellis Mountain Rocket Works + Designation: H275 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Ellis_H48.eng + Manufacturer: Ellis Mountain Rocket Works + Designation: H48 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Ellis_H50.eng + Manufacturer: Ellis Mountain Rocket Works + Designation: H50 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Ellis_I130.eng + Manufacturer: Ellis Mountain Rocket Works + Designation: I130 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Ellis_I134.eng + Manufacturer: Ellis Mountain Rocket Works + Designation: I134 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Ellis_I150.eng + Manufacturer: Ellis Mountain Rocket Works + Designation: I150 + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Ellis_I160.eng + Manufacturer: Ellis Mountain Rocket Works + Designation: I160 + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Ellis_I230.eng + Manufacturer: Ellis Mountain Rocket Works + Designation: I230 + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Ellis_I69.eng + Manufacturer: Ellis Mountain Rocket Works + Designation: I69 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Ellis_J110.eng + Manufacturer: Ellis Mountain Rocket Works + Designation: J110 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Ellis_J148.eng + Manufacturer: Ellis Mountain Rocket Works + Designation: J148 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Ellis_J228.eng + Manufacturer: Ellis Mountain Rocket Works + Designation: J228 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Ellis_J270.eng + Manufacturer: Ellis Mountain Rocket Works + Designation: J270 + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Ellis_J330.eng + Manufacturer: Ellis Mountain Rocket Works + Designation: J330 + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Ellis_K475.eng + Manufacturer: Ellis Mountain Rocket Works + Designation: K475 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Ellis_L330.eng + Manufacturer: Ellis Mountain Rocket Works + Designation: L330 + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Ellis_L600.eng + Manufacturer: Ellis Mountain Rocket Works + Designation: L600 + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Ellis_M1000.eng + Manufacturer: Ellis Mountain Rocket Works + Designation: M1000 + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Estes_1/2A3.eng + Manufacturer: Estes Industries + Designation: 1/2A3 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Estes_1/2A6.eng + Manufacturer: Estes Industries + Designation: 1/2A6 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Estes_1/4A3.eng + Manufacturer: Estes Industries + Designation: 1/4A3 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Estes_A10.eng + Manufacturer: Estes Industries + Designation: A10 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Estes_A3.eng + Manufacturer: Estes Industries + Designation: A3 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Estes_A8.eng + Manufacturer: Estes Industries + Designation: A8 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Estes_B4.eng + Manufacturer: Estes Industries + Designation: B4 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Estes_B6.eng + Manufacturer: Estes Industries + Designation: B6 + Data Format: RASP + Data Source: cert + Contributor: Mark Koelsch + +Estes_C11.eng + Manufacturer: Estes Industries + Designation: C11 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Estes_C5.eng + Manufacturer: Estes Industries + Designation: C5 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Estes_C6.eng + Manufacturer: Estes Industries + Designation: C6 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Estes_D11.eng + Manufacturer: Estes Industries + Designation: D11 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Estes_D12.eng + Manufacturer: Estes Industries + Designation: D12 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Estes_E9.eng + Manufacturer: Estes Industries + Designation: E9 + Data Format: RASP + Data Source: cert + Contributor: Mark Koelsch + +GR_K555.eng + Manufacturer: Gorilla Rocket Motors, Inc. + Designation: K555GT + Data Format: RASP + Data Source: mfr + Contributor: Mark Koelsch + +Hypertek_I130.eng + Manufacturer: Hypertek + Designation: 300CC098J - I130 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Hypertek_I136.eng + Manufacturer: Hypertek + Designation: 300CC098J2 - I136 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Hypertek_I145.eng + Manufacturer: Hypertek + Designation: 300CC098JFX - I145FX + Data Format: RASP + Data Source: user + Contributor: John Coker + +Hypertek_I205.eng + Manufacturer: Hypertek + Designation: 300CC125J - I205 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Hypertek_I222.eng + Manufacturer: Hypertek + Designation: 300CC125J2 - I222 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Hypertek_I225.eng + Manufacturer: Hypertek + Designation: 300CC125JFX - I225FX + Data Format: RASP + Data Source: user + Contributor: John Coker + +Hypertek_I260.eng + Manufacturer: Hypertek + Designation: 440CC172J - I260 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Hypertek_I310.eng + Manufacturer: Hypertek + Designation: 440CC172J - I310 + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Hypertek_J115.eng + Manufacturer: Hypertek + Designation: 440CC076J - J115 + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Hypertek_J120.eng + Manufacturer: Hypertek + Designation: 440CC076JFX - J120FX + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Hypertek_J150.eng + Manufacturer: Hypertek + Designation: 440CC086J - J150 + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Hypertek_J170.eng + Manufacturer: Hypertek + Designation: 440CC098J - J170 + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Hypertek_J190.eng + Manufacturer: Hypertek + Designation: 440CC098JFX - J190FX + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Hypertek_J220.eng + Manufacturer: Hypertek + Designation: 440CC110J - J220 + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Hypertek_J250.eng + Manufacturer: Hypertek + Designation: 440CC125J - J250 + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Hypertek_J250_1.eng + Manufacturer: Hypertek + Designation: 440CC125J - J250 + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Hypertek_J270.eng + Manufacturer: Hypertek + Designation: 440CC125JFX - J270FX + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Hypertek_J295.eng + Manufacturer: Hypertek + Designation: 440CC172JFX - J295FX + Data Format: RASP + Data Source: user + Contributor: John Coker + +Hypertek_J317.eng + Manufacturer: Hypertek + Designation: 835CC172J - J317 + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Hypertek_J330.eng + Manufacturer: Hypertek + Designation: 835CC172JFX - J330FX + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Hypertek_J330_1.eng + Manufacturer: Hypertek + Designation: 835CC172JFX - J330FX + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Hypertek_K240.eng + Manufacturer: Hypertek + Designation: 835CC125J - K240 + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Hypertek_L200.eng + Manufacturer: Hypertek + Designation: 1685CC098L - L200 + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Hypertek_L225.eng + Manufacturer: Hypertek + Designation: 1685CC098LFX - L225FX + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Hypertek_L350.eng + Manufacturer: Hypertek + Designation: 1685CC125L - L350 + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Hypertek_L355.eng + Manufacturer: Hypertek + Designation: 1685CC125LFX - L355FX + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Hypertek_L475.eng + Manufacturer: Hypertek + Designation: 1685CC172L - L475 + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Hypertek_L535.eng + Manufacturer: Hypertek + Designation: 1685CC172LFX - L535FX + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Hypertek_L540.eng + Manufacturer: Hypertek + Designation: 2800CC172L - L540 + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Hypertek_L540_1.eng + Manufacturer: Hypertek + Designation: 2800CC172L - L540 + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Hypertek_L550.eng + Manufacturer: Hypertek + Designation: 1685CCRGL - L550 + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Hypertek_L570.eng + Manufacturer: Hypertek + Designation: 2800CC172LFX - L570FX + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Hypertek_L570_1.eng + Manufacturer: Hypertek + Designation: 2800CC172LFX - L570FX + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Hypertek_L575.eng + Manufacturer: Hypertek + Designation: 2800CCRGL - L575 + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Hypertek_L575_1.eng + Manufacturer: Hypertek + Designation: 2800CCRGL - L575 + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Hypertek_L610.eng + Manufacturer: Hypertek + Designation: 1685CCRGLFX - L610FX + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Hypertek_L625.eng + Manufacturer: Hypertek + Designation: 2800CCRGLFX - L625FX + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Hypertek_L625_1.eng + Manufacturer: Hypertek + Designation: 2800CCRGLFX - L625FX + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Hypertek_L740.eng + Manufacturer: Hypertek + Designation: 2800CC200MFX - L740FX + Data Format: RASP + Data Source: user + Contributor: John Coker + +Hypertek_L970.eng + Manufacturer: Hypertek + Designation: 2800CC300M - L970 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Hypertek_M1000.eng + Manufacturer: Hypertek + Designation: 4630CCRGM - M1000 + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Hypertek_M1000_1.eng + Manufacturer: Hypertek + Designation: 4630CCRGM - M1000 + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Hypertek_M1001.eng + Manufacturer: Hypertek + Designation: 5478CCRGM - M1001 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Hypertek_M1010.eng + Manufacturer: Hypertek + Designation: 4630CCRGMFX - M1010FX + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Hypertek_M1010_1.eng + Manufacturer: Hypertek + Designation: 4630CCRGMFX - M1010FX + Data Format: RASP + Data Source: cert + Contributor: John Coker + +Hypertek_M1015.eng + Manufacturer: Hypertek + Designation: 3500CCRGMFX - M1015FX + Data Format: RASP + Data Source: user + Contributor: John Coker + +Hypertek_M1040.eng + Manufacturer: Hypertek + Designation: 4630CCRGMFX - M1040FX + Data Format: RASP + Data Source: user + Contributor: John Coker + +Hypertek_M740.eng + Manufacturer: Hypertek + Designation: 2800CC200M - M740 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Hypertek_M956.eng + Manufacturer: Hypertek + Designation: 3500CCRGM - M956 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Hypertek_M960.eng + Manufacturer: Hypertek + Designation: 2800CC300MFX - M960FX + Data Format: RASP + Data Source: user + Contributor: John Coker + +KBA_I170.eng + Manufacturer: Kosdon by AeroTech + Designation: I170S + Data Format: RASP + Data Source: user + Contributor: John Coker + +KBA_I280.eng + Manufacturer: Kosdon by AeroTech + Designation: I280F + Data Format: RASP + Data Source: user + Contributor: John Coker + +KBA_I301.eng + Manufacturer: Kosdon by AeroTech + Designation: I301W + Data Format: RASP + Data Source: mfr + Contributor: Mark Koelsch + +KBA_I310.eng + Manufacturer: Kosdon by AeroTech + Designation: I310S + Data Format: RASP + Data Source: user + Contributor: John Coker + +KBA_I370.eng + Manufacturer: Kosdon by AeroTech + Designation: I370F + Data Format: RASP + Data Source: user + Contributor: John Coker + +KBA_I450.eng + Manufacturer: Kosdon by AeroTech + Designation: I450F + Data Format: RASP + Data Source: user + Contributor: John Coker + +KBA_I550.eng + Manufacturer: Kosdon by AeroTech + Designation: I550R + Data Format: RASP + Data Source: mfr + Contributor: Mark Koelsch + +KBA_J405.eng + Manufacturer: Kosdon by AeroTech + Designation: J405S + Data Format: RASP + Data Source: user + Contributor: John Coker + +KBA_J605.eng + Manufacturer: Kosdon by AeroTech + Designation: J605F + Data Format: RASP + Data Source: user + Contributor: John Coker + +KBA_K1750.eng + Manufacturer: Kosdon by AeroTech + Designation: K1750R + Data Format: RASP + Data Source: mfr + Contributor: Mark Koelsch + +KBA_K400.eng + Manufacturer: Kosdon by AeroTech + Designation: K400S + Data Format: RASP + Data Source: user + Contributor: John Coker + +KBA_K600.eng + Manufacturer: Kosdon by AeroTech + Designation: K600F + Data Format: RASP + Data Source: cert + Contributor: John Coker + +KBA_K750.eng + Manufacturer: Kosdon by AeroTech + Designation: K750W + Data Format: RASP + Data Source: mfr + Contributor: Mark Koelsch + +KBA_L1000.eng + Manufacturer: Kosdon by AeroTech + Designation: L1000S + Data Format: RASP + Data Source: cert + Contributor: John Coker + +KBA_L1400.eng + Manufacturer: Kosdon by AeroTech + Designation: L1400F + Data Format: RASP + Data Source: user + Contributor: John Coker + +KBA_M1450.eng + Manufacturer: Kosdon by AeroTech + Designation: M1450W + Data Format: RASP + Data Source: mfr + Contributor: Mark Koelsch + +Loki_H144.eng + Manufacturer: Loki Research + Designation: H144-LW + Data Format: RASP + Data Source: mfr + Contributor: William Carney + +Loki_H500.eng + Manufacturer: Loki Research + Designation: H500 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Loki_I405.eng + Manufacturer: Loki Research + Designation: I405LW + Data Format: RASP + Data Source: mfr + Contributor: William Carney + +Loki_J525.eng + Manufacturer: Loki Research + Designation: J525LW + Data Format: RASP + Data Source: mfr + Contributor: William Carney + +Loki_J528.eng + Manufacturer: Loki Research + Designation: J528LW + Data Format: RASP + Data Source: mfr + Contributor: William Carney + +Loki_K250.eng + Manufacturer: Loki Research + Designation: K250LWM + Data Format: RASP + Data Source: mfr + Contributor: William Carney + +Loki_K350.eng + Manufacturer: Loki Research + Designation: K350LWM + Data Format: RASP + Data Source: mfr + Contributor: William Carney + +Loki_K960.eng + Manufacturer: Loki Research + Designation: K960LWB + Data Format: RASP + Data Source: mfr + Contributor: William Carney + +Loki_L1400.eng + Manufacturer: Loki Research + Designation: L1400LW + Data Format: RASP + Data Source: mfr + Contributor: William Carney + +Loki_L930.eng + Manufacturer: Loki Research + Designation: L930LWB + Data Format: RASP + Data Source: mfr + Contributor: William Carney + +Loki_M1882.eng + Manufacturer: Loki Research + Designation: M1882LW + Data Format: RASP + Data Source: mfr + Contributor: William Carney + +PML_F50.eng + Manufacturer: Public Missiles, Ltd. + Designation: F50T + Data Format: RASP + Data Source: cert + Contributor: Mark Koelsch + +PML_G40.eng + Manufacturer: Public Missiles, Ltd. + Designation: G40W + Data Format: RASP + Data Source: cert + Contributor: Mark Koelsch + +PML_G80.eng + Manufacturer: Public Missiles, Ltd. + Designation: G80T + Data Format: RASP + Data Source: cert + Contributor: Mark Koelsch + +PP_H70.eng + Manufacturer: Propulsion Polymers + Designation: 240NS-H70 + Data Format: RASP + Data Source: user + Contributor: John Coker + +PP_I160.eng + Manufacturer: Propulsion Polymers + Designation: 484NS-I160 + Data Format: RASP + Data Source: user + Contributor: John Coker + +PP_I80.eng + Manufacturer: Propulsion Polymers + Designation: 460NS-I80 + Data Format: RASP + Data Source: user + Contributor: John Coker + +PP_J140.eng + Manufacturer: Propulsion Polymers + Designation: 664NS-J140 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Quest_A6.eng + Manufacturer: Quest Aerospace + Designation: A6 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Quest_B6.eng + Manufacturer: Quest Aerospace + Designation: B6 + Data Format: RASP + Data Source: user + Contributor: John Coker + +Quest_C6.eng + Manufacturer: Quest Aerospace + Designation: C6 + Data Format: RASP + Data Source: cert + Contributor: Mark Koelsch + +Quest_D5.eng + Manufacturer: Quest Aerospace + Designation: D5-P + Data Format: RASP + Data Source: cert + Contributor: Mark Koelsch + +RATT_H70.eng + Manufacturer: R.A.T.T. Works Precision Rocket Motors + Designation: H70 + Data Format: RASP + Data Source: cert + Contributor: John Coker + +RATT_I80.eng + Manufacturer: R.A.T.T. Works Precision Rocket Motors + Designation: I80 + Data Format: RASP + Data Source: cert + Contributor: John Coker + +RATT_I90.eng + Manufacturer: R.A.T.T. Works Precision Rocket Motors + Designation: I90L + Data Format: RASP + Data Source: cert + Contributor: John Coker + +RATT_J160.eng + Manufacturer: R.A.T.T. Works Precision Rocket Motors + Designation: J160 + Data Format: RASP + Data Source: user + Contributor: John Coker + +RATT_K240.eng + Manufacturer: R.A.T.T. Works Precision Rocket Motors + Designation: K240H + Data Format: RASP + Data Source: cert + Contributor: John Coker + +RATT_L600.eng + Manufacturer: R.A.T.T. Works Precision Rocket Motors + Designation: L600 + Data Format: RASP + Data Source: user + Contributor: John Coker + +RATT_M900.eng + Manufacturer: R.A.T.T. Works Precision Rocket Motors + Designation: M900 + Data Format: RASP + Data Source: user + Contributor: John Coker + +RV_F32.eng + Manufacturer: Rocketvision Flight-Star + Designation: F32 + Data Format: RASP + Data Source: user + Contributor: John Coker + +RV_F72.eng + Manufacturer: Rocketvision Flight-Star + Designation: F72 + Data Format: RASP + Data Source: cert + Contributor: Mark Koelsch + +RV_G55.eng + Manufacturer: Rocketvision Flight-Star + Designation: G55 + Data Format: RASP + Data Source: cert + Contributor: Mark Koelsch + +Roadrunner_E25.eng + Manufacturer: Roadrunner Rocketry + Designation: E25 + Data Format: RASP + Data Source: mfr + Contributor: Roadrunner Rocketry + +Roadrunner_F35.eng + Manufacturer: Roadrunner Rocketry + Designation: F35 + Data Format: RASP + Data Source: mfr + Contributor: Roadrunner Rocketry + +Roadrunner_F45.eng + Manufacturer: Roadrunner Rocketry + Designation: F45 + Data Format: RASP + Data Source: mfr + Contributor: Roadrunner Rocketry + +Roadrunner_F60.eng + Manufacturer: Roadrunner Rocketry + Designation: F60 + Data Format: RASP + Data Source: mfr + Contributor: Roadrunner Rocketry + +Roadrunner_G80.eng + Manufacturer: Roadrunner Rocketry + Designation: G80 + Data Format: RASP + Data Source: mfr + Contributor: Roadrunner Rocketry + +SkyR_G125.eng + Manufacturer: Sky Ripper Systems + Designation: G125 + Data Format: RASP + Data Source: user + Contributor: John Coker + +SkyR_G63.eng + Manufacturer: Sky Ripper Systems + Designation: G63 + Data Format: RASP + Data Source: user + Contributor: John Coker + +SkyR_G69.eng + Manufacturer: Sky Ripper Systems + Designation: G69 + Data Format: RASP + Data Source: user + Contributor: John Coker + +SkyR_H124.eng + Manufacturer: Sky Ripper Systems + Designation: H124 + Data Format: RASP + Data Source: mfr + Contributor: Andrew MacMillen + +SkyR_H155.eng + Manufacturer: Sky Ripper Systems + Designation: H155 + Data Format: RASP + Data Source: mfr + Contributor: Andrew MacMillen + +SkyR_H78.eng + Manufacturer: Sky Ripper Systems + Designation: H78 + Data Format: RASP + Data Source: user + Contributor: John Coker + +SkyR_I117.eng + Manufacturer: Sky Ripper Systems + Designation: I117 + Data Format: RASP + Data Source: mfr + Contributor: Andrew MacMillen + +SkyR_I119.eng + Manufacturer: Sky Ripper Systems + Designation: I119 + Data Format: RASP + Data Source: mfr + Contributor: Andrew MacMillen + +SkyR_I147.eng + Manufacturer: Sky Ripper Systems + Designation: I147 + Data Format: RASP + Data Source: mfr + Contributor: Andrew MacMillen + +SkyR_J144.eng + Manufacturer: Sky Ripper Systems + Designation: J144 + Data Format: RASP + Data Source: mfr + Contributor: Andrew MacMillen + +SkyR_J261.eng + Manufacturer: Sky Ripper Systems + Designation: J261G + Data Format: RASP + Data Source: mfr + Contributor: John Coker + +SkyR_J263.eng + Manufacturer: Sky Ripper Systems + Designation: J263G + Data Format: RASP + Data Source: mfr + Contributor: John Coker + +SkyR_J337.eng + Manufacturer: Sky Ripper Systems + Designation: J337B + Data Format: RASP + Data Source: mfr + Contributor: John Coker + +SkyR_J348.eng + Manufacturer: Sky Ripper Systems + Designation: J348B + Data Format: RASP + Data Source: mfr + Contributor: John Coker + +SkyR_K257.eng + Manufacturer: Sky Ripper Systems + Designation: K257G + Data Format: RASP + Data Source: mfr + Contributor: John Coker + +SkyR_K347.eng + Manufacturer: Sky Ripper Systems + Designation: K347B + Data Format: RASP + Data Source: mfr + Contributor: John Coker + +WCH_I110.eng + Manufacturer: West Coast Hybrids + Designation: 499 I110-P + Data Format: RASP + Data Source: mfr + Contributor: Andrew MacMillen diff --git a/datafiles/thrustcurves/AMW_I195.eng b/datafiles/thrustcurves/AMW_I195.eng new file mode 100644 index 000000000..2aacd4aed --- /dev/null +++ b/datafiles/thrustcurves/AMW_I195.eng @@ -0,0 +1,33 @@ +;Animal Motor Works 38-390 +I195WT 38 249 17 0.193 0.495 AMW + 0.0020 10.548 + 0.018 42.653 + 0.046 136.214 + 0.064 179.784 + 0.072 191.248 + 0.078 197.211 + 0.088 198.587 + 0.126 198.587 + 0.175 207.759 + 0.217 211.887 + 0.349 216.931 + 0.401 221.059 + 0.554 225.646 + 0.586 228.856 + 0.626 228.48 + 0.65 230.232 + 1.013 231.607 + 1.105 230.691 + 1.2 225.187 + 1.356 210.511 + 1.392 207.759 + 1.441 206.842 + 1.457 205.007 + 1.519 181.159 + 1.563 155.936 + 1.693 49.992 + 1.727 28.435 + 1.756 16.512 + 1.798 7.338 + 1.86 1.376 + 1.89 0.0 diff --git a/datafiles/thrustcurves/AMW_I220.eng b/datafiles/thrustcurves/AMW_I220.eng new file mode 100644 index 000000000..c07bea72f --- /dev/null +++ b/datafiles/thrustcurves/AMW_I220.eng @@ -0,0 +1,29 @@ +;Animal Motor Works 38-390 +I220SK 38 249 20 0.202 0.495 AMW + 0.0050 12.747 + 0.019 45.25 + 0.036 79.666 + 0.052 125.554 + 0.069 162.519 + 0.076 169.53 + 0.095 174.629 + 0.167 176.541 + 0.229 191.199 + 0.447 235.175 + 0.602 260.668 + 0.733 288.073 + 0.85 302.095 + 0.974 301.457 + 1.094 289.985 + 1.184 268.954 + 1.268 240.273 + 1.302 219.879 + 1.388 177.178 + 1.418 147.224 + 1.435 127.467 + 1.473 91.139 + 1.504 65.645 + 1.543 40.789 + 1.593 19.12 + 1.622 10.197 + 1.65 0.0 diff --git a/datafiles/thrustcurves/AMW_I271.eng b/datafiles/thrustcurves/AMW_I271.eng new file mode 100644 index 000000000..08274968d --- /dev/null +++ b/datafiles/thrustcurves/AMW_I271.eng @@ -0,0 +1,27 @@ +; +; AMW 38-390 +I271BB 38 258 0 0.189 0.493 AMW +0.011 119.530 +0.035 213.907 +0.050 245.903 +0.074 262.705 +0.115 269.446 +0.225 267.736 +0.346 282.929 +0.465 296.411 +0.584 303.152 +0.727 311.504 +0.916 318.245 +1.054 324.986 +1.162 331.400 +1.201 326.696 +1.225 313.214 +1.242 286.249 +1.268 240.990 +1.294 188.888 +1.323 136.833 +1.346 87.565 +1.368 45.467 +1.392 18.523 +1.430 0.000 +; diff --git a/datafiles/thrustcurves/AMW_I285.eng b/datafiles/thrustcurves/AMW_I285.eng new file mode 100644 index 000000000..ea373d5b7 --- /dev/null +++ b/datafiles/thrustcurves/AMW_I285.eng @@ -0,0 +1,31 @@ +; +; AMW 38-390 +I285GG 38 258 0 0.206 0.515 AMW +0.013 61.575 +0.032 119.327 +0.055 164.575 +0.076 191.004 +0.094 201.014 +0.139 212.326 +0.232 231.247 +0.357 258.876 +0.456 267.686 +0.592 278.998 +0.716 289.358 +0.841 291.200 +0.936 290.310 +1.051 285.204 +1.139 277.696 +1.204 280.199 +1.243 278.998 +1.265 268.887 +1.286 242.559 +1.319 187.200 +1.359 134.443 +1.387 86.702 +1.407 52.776 +1.428 31.413 +1.448 16.337 +1.465 5.026 +1.480 0.000 +; diff --git a/datafiles/thrustcurves/AMW_I315.eng b/datafiles/thrustcurves/AMW_I315.eng new file mode 100644 index 000000000..3769d8027 --- /dev/null +++ b/datafiles/thrustcurves/AMW_I315.eng @@ -0,0 +1,34 @@ +; This file my be used or given away. All I ask is that this header +; is maintained to give credit to NAR S&T. Thank you, Jack Kane +; The total impulse, peak thrust, average thrust and burn time are +; the same as the averaged static test data on the NAR web site in +; the certification file. The curve drawn with these data points is as +; close to the certification curve as can be with such a limited +; number of points (32) allowed with wRASP up to v1.6. +;Animal Motor Works 38-640 +I315SK 38 369 20 0.3829 0.7166 AMW +0.011 314.573 +0.030 312.796 +0.066 300.786 +0.084 300.502 +0.120 304.087 +0.175 312.998 +0.266 324.086 +0.356 332.224 +0.447 347.855 +0.538 371.972 +0.629 382.833 +0.719 385.552 +0.810 385.586 +0.901 384.836 +0.992 382.296 +1.082 378.323 +1.173 370.837 +1.264 357.564 +1.355 347.122 +1.445 328.332 +1.536 202.733 +1.627 90.867 +1.718 35.427 +1.808 8.192 +1.815 0.000 diff --git a/datafiles/thrustcurves/AMW_I325.eng b/datafiles/thrustcurves/AMW_I325.eng new file mode 100644 index 000000000..71945970e --- /dev/null +++ b/datafiles/thrustcurves/AMW_I325.eng @@ -0,0 +1,31 @@ +;Animal Motor Works 38-640 +I325WT 38 370 17 0.317 0.712 AMW + 0.014 68.710 + 0.022 113.038 + 0.026 153.671 + 0.037 244.545 + 0.045 299.216 + 0.055 330.246 + 0.065 350.194 + 0.079 365.709 + 0.094 376.79 + 0.124 381.963 + 0.185 373.836 + 0.252 373.836 + 0.35 381.224 + 0.47 382.701 + 0.622 388.611 + 1.102 384.179 + 1.366 364.971 + 1.379 360.537 + 1.415 331.724 + 1.49 223.119 + 1.505 211.298 + 1.551 187.657 + 1.592 162.538 + 1.688 80.529 + 1.726 50.978 + 1.775 27.336 + 1.806 16.993 + 1.834 9.605 + 1.901 0.0 diff --git a/datafiles/thrustcurves/AMW_I375.eng b/datafiles/thrustcurves/AMW_I375.eng new file mode 100644 index 000000000..92aa13ee5 --- /dev/null +++ b/datafiles/thrustcurves/AMW_I375.eng @@ -0,0 +1,26 @@ +; +;Animal Motor Works 38-640 +I375GG 38 369 20 0.3936 0.7338 AMW +0.013 223.878 +0.045 273.929 +0.092 312.421 +0.140 334.383 +0.219 357.983 +0.298 381.992 +0.377 410.267 +0.457 431.141 +0.536 454.458 +0.615 476.825 +0.694 495.473 +0.773 504.665 +0.852 510.942 +0.931 511.972 +1.011 489.639 +1.090 441.350 +1.169 392.762 +1.248 354.753 +1.327 292.385 +1.406 177.309 +1.486 63.879 +1.565 14.901 +1.583 0.000 diff --git a/datafiles/thrustcurves/AMW_J357.eng b/datafiles/thrustcurves/AMW_J357.eng new file mode 100644 index 000000000..b387aa75a --- /dev/null +++ b/datafiles/thrustcurves/AMW_J357.eng @@ -0,0 +1,35 @@ +; AMW Animal Motor Works fixed by dberez 12/08/03 +; +;Animal Motor Works J357 White Wolf +J357WW 54 326 0 0.5481 1.2101 AMW +0.02 129.64 +0.03 205.95 +0.05 265.00 +0.06 316.51 +0.09 326.05 +0.13 314.60 +0.18 301.25 +0.24 299.34 +0.35 312.69 +0.50 326.05 +0.66 333.68 +0.87 345.13 +1.07 358.48 +1.46 383.18 +1.77 398.45 +1.86 400.36 +1.98 402.35 +2.18 398.45 +2.29 390.82 +2.41 369.93 +2.51 354.67 +2.55 352.76 +2.60 347.03 +2.65 335.59 +2.69 310.79 +2.75 249.73 +2.81 175.43 +2.84 108.65 +2.90 53.38 +2.92 20.98 +2.95 0.00 diff --git a/datafiles/thrustcurves/AMW_J365.eng b/datafiles/thrustcurves/AMW_J365.eng new file mode 100644 index 000000000..05fe77594 --- /dev/null +++ b/datafiles/thrustcurves/AMW_J365.eng @@ -0,0 +1,25 @@ +; +;Animal Motor Works 54-1400 +J365SK 54 403 0 0.7571 1.4593 AMW +0.029 389.731 +0.123 360.219 +0.218 334.200 +0.376 326.150 +0.534 334.217 +0.692 341.669 +0.850 347.676 +1.007 359.408 +1.165 370.043 +1.323 383.343 +1.481 399.248 +1.639 417.477 +1.797 443.735 +1.955 472.683 +2.112 501.668 +2.270 497.077 +2.428 425.371 +2.586 349.017 +2.744 262.068 +2.902 107.073 +3.060 41.821 +3.157 0.000 diff --git a/datafiles/thrustcurves/AMW_J370.eng b/datafiles/thrustcurves/AMW_J370.eng new file mode 100644 index 000000000..78837f313 --- /dev/null +++ b/datafiles/thrustcurves/AMW_J370.eng @@ -0,0 +1,44 @@ +; +;Animal Motor Works 54-1050 +;AMW J370GG RASP.ENG file made from NAR data +;File produced FEB 20, 2003 +;This file my be used or given away. All I ask is that this header +;is maintained to give credit to NAR S&T. Thank you, Jack Kane +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +J370GG 54 326 100 0.5983 1.2491 Animal_Motor_Works +0.008 185.496 +0.024 149.516 +0.063 225.273 +0.087 272.647 +0.122 304.829 +0.158 304.829 +0.273 335.212 +0.431 363.796 +0.573 390.381 +0.707 413.168 +0.877 428.459 +1.019 441.852 +1.126 441.852 +1.224 458.46 +1.284 443.951 +1.386 440.153 +1.572 438.454 +1.651 438.554 +1.813 417.765 +2.022 404.673 +2.141 385.883 +2.212 385.883 +2.255 374.59 +2.299 387.882 +2.362 357.599 +2.401 384.184 +2.421 348.204 +2.457 316.122 +2.559 251.758 +2.697 115.635 +2.753 45.624 +2.82 0 diff --git a/datafiles/thrustcurves/AMW_J400.eng b/datafiles/thrustcurves/AMW_J400.eng new file mode 100644 index 000000000..52e017f1b --- /dev/null +++ b/datafiles/thrustcurves/AMW_J400.eng @@ -0,0 +1,34 @@ +; +;AMW J400 RASP.ENG file made from NAR published data +;File produced April 19, 2004 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +J400RR 54 326 100 0.558 1.2314 Animal_Motor_Works +0.043 246.55 +0.06 317.381 +0.081 344.709 +0.107 358.372 +0.15 356.06 +0.201 365.204 +0.308 392.532 +0.568 435.734 +0.863 458.339 +1.094 469.24 +1.209 467.18 +1.466 460.148 +1.705 443.972 +1.923 423.275 +2.132 409.411 +2.303 413.831 +2.402 420.563 +2.47 413.831 +2.517 395.445 +2.543 347.421 +2.568 265.238 +2.598 128.198 +2.615 68.801 +2.632 25.398 +2.66 0 diff --git a/datafiles/thrustcurves/AMW_J440.eng b/datafiles/thrustcurves/AMW_J440.eng new file mode 100644 index 000000000..40adabd84 --- /dev/null +++ b/datafiles/thrustcurves/AMW_J440.eng @@ -0,0 +1,27 @@ +;Animal Motor Works 38-640 +J440BB 38 369 20 0.3853 0.6985 AMW +0.007 468.505 +0.022 509.996 +0.037 527.687 +0.052 532.792 +0.082 530.181 +0.127 525.586 +0.202 521.566 +0.277 519.840 +0.352 521.522 +0.426 525.414 +0.501 531.248 +0.576 538.724 +0.651 541.761 +0.726 538.508 +0.801 531.072 +0.876 516.175 +0.950 494.942 +1.025 477.251 +1.100 433.297 +1.175 313.900 +1.250 187.467 +1.325 101.546 +1.400 45.751 +1.474 22.083 +1.497 0.000 diff --git a/datafiles/thrustcurves/AMW_J450.eng b/datafiles/thrustcurves/AMW_J450.eng new file mode 100644 index 000000000..01a2efa6e --- /dev/null +++ b/datafiles/thrustcurves/AMW_J450.eng @@ -0,0 +1,39 @@ +; +;AMW J450 RASP.ENG file made from NAR published data +; File produced SEPT 4, 2002 +; The total impulse, peak thrust, average thrust and burn time are +; the same as the averaged static test data on the NAR web site in +; the certification file. The curve drawn with these data points is as +; close to the certification curve as can be with such a limited +; number of points (32) allowed with wRASP up to v1.6. +J450 54 326 P .5331 1.1964 AMW + 0.009 251.586 + 0.016 376.074 + 0.030 413.450 + 0.051 430.832 + 0.094 423.296 + 0.162 413.149 + 0.262 395.566 + 0.402 420.182 + 0.495 444.898 + 0.805 504.078 + 1.048 536.028 + 1.223 550.597 + 1.299 563.180 + 1.334 555.319 + 1.470 560.042 + 1.588 559.841 + 1.764 546.980 + 1.921 516.838 + 1.993 496.743 + 2.025 499.154 + 2.047 479.160 + 2.086 414.354 + 2.115 344.525 + 2.141 252.290 + 2.177 140.161 + 2.213 82.780 + 2.239 50.347 + 2.271 27.861 + 2.296 12.860 + 2.330 0.000 diff --git a/datafiles/thrustcurves/AMW_J450_1.eng b/datafiles/thrustcurves/AMW_J450_1.eng new file mode 100644 index 000000000..5b444e7ba --- /dev/null +++ b/datafiles/thrustcurves/AMW_J450_1.eng @@ -0,0 +1,33 @@ +; +;Animal Motor Works J450 Super Tiger +J450ST 54 326 0 0.5331 1.1964 AMW +0.009 251.586 +0.016 376.074 +0.030 413.450 +0.051 430.832 +0.094 423.296 +0.162 413.149 +0.262 395.566 +0.402 420.182 +0.495 444.898 +0.805 504.078 +1.048 536.028 +1.223 550.597 +1.299 563.180 +1.334 555.319 +1.470 560.042 +1.588 559.841 +1.764 546.980 +1.921 516.838 +1.993 496.743 +2.025 499.154 +2.047 479.160 +2.086 414.354 +2.115 344.525 +2.141 252.290 +2.177 140.161 +2.213 82.780 +2.239 50.347 +2.271 27.861 +2.296 12.860 +2.330 0.000 diff --git a/datafiles/thrustcurves/AMW_J480.eng b/datafiles/thrustcurves/AMW_J480.eng new file mode 100644 index 000000000..0eca68897 --- /dev/null +++ b/datafiles/thrustcurves/AMW_J480.eng @@ -0,0 +1,32 @@ +; +;AMW J480 RASP.ENG file made from NAR published data +;File produced April 19, 2004 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +J480BB 54 326 100 0.556 1.2131 Animal_Motor_Works +0.015 225.429 +0.041 348.18 +0.071 388.127 +0.194 422.453 +0.385 459.49 +0.699 502.347 +0.968 528.042 +1.2 536.573 +1.454 543.15 +1.674 533.763 +1.887 522.321 +2.044 519.41 +2.108 525.131 +2.164 528.042 +2.197 488.095 +2.25 419.543 +2.283 333.928 +2.328 231.15 +2.354 176.95 +2.392 111.309 +2.418 68.501 +2.436 37.106 +2.49 0 diff --git a/datafiles/thrustcurves/AMW_J500.eng b/datafiles/thrustcurves/AMW_J500.eng new file mode 100644 index 000000000..f5a088965 --- /dev/null +++ b/datafiles/thrustcurves/AMW_J500.eng @@ -0,0 +1,32 @@ +; +;J500ST entered by Tim Van Milligan +;For RockSim - http://www.rocksim.com +;Based on TRA Certification paperwork from 06-01-2002 +;Initial Mass from Jim Robinson at AMW +;Not approved by TRA or AMW. +J500ST 38 370 20 0.3265 0.744 Animal_Motor_Works +0.006 444.822 +0.025 475.651 +0.04 418.397 +0.053 466.843 +0.059 409.589 +0.071 458.035 +0.077 409.589 +0.1 444.822 +0.127 506.481 +0.204 590.16 +0.25 644.992 +0.3 678.244 +0.34 709.073 +0.402 735.498 +0.445 766.327 +0.516 783.944 +0.6 787.335 +0.637 770.732 +0.68 744.306 +0.76 620.989 +0.859 475.651 +1.00464 303.888 +1.122 171.763 +1.227 52.8502 +1.3 0 diff --git a/datafiles/thrustcurves/AMW_K1000.eng b/datafiles/thrustcurves/AMW_K1000.eng new file mode 100644 index 000000000..09d0d3250 --- /dev/null +++ b/datafiles/thrustcurves/AMW_K1000.eng @@ -0,0 +1,37 @@ +; +;AMW K1000 RASP.ENG file made from NAR published data +;File produced April 19, 2004 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +K1000SK 54 728 100 1.297 2.556 Animal_Motor_Works +0.019 1155.06 +0.045 1426.12 +0.094 1248.23 +0.161 1112.99 +0.239 1128.02 +0.343 1113.99 +0.377 1149.05 +0.44 1121 +0.544 1221.18 +0.633 1178.11 +0.674 1221.18 +0.737 1193.13 +0.883 1200.14 +1.009 1194.13 +1.057 1236.21 +1.188 1137.03 +1.299 1145.05 +1.396 1087.94 +1.516 954.104 +1.631 855.228 +1.717 827.077 +1.777 650.061 +1.848 465.932 +1.93 303.141 +2.023 147.463 +2.083 83.879 +2.132 41.484 +2.18 0 diff --git a/datafiles/thrustcurves/AMW_K1075.eng b/datafiles/thrustcurves/AMW_K1075.eng new file mode 100644 index 000000000..6a1cbb416 --- /dev/null +++ b/datafiles/thrustcurves/AMW_K1075.eng @@ -0,0 +1,39 @@ +; +;Animal Motor Works K1075 RASP.ENG file made from NAR data +;File produced Feb 22, 2003 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +K1075GG 54 726 100 1.3999 2.6658 Animal_Motor_Works +0.009 672.664 +0.015 963.511 +0.022 860.518 +0.047 987.857 +0.075 975.835 +0.106 921.332 +0.215 958.001 +0.529 1092.05 +0.878 1220.29 +1.077 1269.39 +1.158 1311.47 +1.235 1293.43 +1.448 1330.5 +1.577 1318.48 +1.672 1319.48 +1.721 1337.52 +1.759 1337.52 +1.805 1337.52 +1.829 1331.5 +1.856 1384.67 +1.889 1277.4 +1.906 1216.29 +1.938 1052.98 +1.96 871.338 +1.988 659.239 +2.027 453.352 +2.062 301.967 +2.115 138.46 +2.168 41.608 +2.2 0 diff --git a/datafiles/thrustcurves/AMW_K365.eng b/datafiles/thrustcurves/AMW_K365.eng new file mode 100644 index 000000000..25fe1b1f0 --- /dev/null +++ b/datafiles/thrustcurves/AMW_K365.eng @@ -0,0 +1,35 @@ +; +;AMW K365RR RASP.ENG file made from NAR published data +;File produced April 19, 2004 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +K365RR 75 111 100 0.946 2.3456 Animal_Motor_Works +0.049 138.157 +0.068 381.241 +0.084 454.75 +0.106 481.536 +0.164 488.182 +0.291 514.867 +0.435 545.982 +0.666 561.49 +0.868 565.73 +1.082 565.518 +1.296 550.111 +1.591 529.871 +1.805 509.731 +1.828 536.517 +1.886 498.554 +2.124 467.237 +2.501 411.35 +2.924 328.677 +3.296 241.573 +3.638 172.293 +3.969 100.798 +4.195 56.098 +4.265 51.607 +4.346 35.959 +4.433 15.859 +4.51 0 diff --git a/datafiles/thrustcurves/AMW_K450.eng b/datafiles/thrustcurves/AMW_K450.eng new file mode 100644 index 000000000..ece0bace5 --- /dev/null +++ b/datafiles/thrustcurves/AMW_K450.eng @@ -0,0 +1,35 @@ +; +;AMW K450BB RASP.ENG file made from NAR published data +;File produced Aug 19, 2003 +;This file my be used or given away. All I ask is that this header +;is maintained to give credit to NAR S&T. Thank you, Jack Kane. +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +K450BB 75 302 100 0.8816 2.8349 Animal_Motor_Works +0.03 78.903 +0.045 227.9 +0.064 449.955 +0.069 508.417 +0.094 555.187 +0.151 563.956 +0.362 625.442 +0.562 651.85 +0.825 660.62 +1.134 652.254 +1.453 626.147 +1.793 594.296 +2.113 538.958 +2.458 469.106 +2.798 384.538 +3.165 276.686 +3.201 279.609 +3.325 232.94 +3.51 171.757 +3.732 107.65 +3.861 58.018 +3.959 40.55 +4.036 20.149 +4.11 0 diff --git a/datafiles/thrustcurves/AMW_K470.eng b/datafiles/thrustcurves/AMW_K470.eng new file mode 100644 index 000000000..bd780c1df --- /dev/null +++ b/datafiles/thrustcurves/AMW_K470.eng @@ -0,0 +1,36 @@ +; +;AMW K470ST RASP.ENG file made from Tripoli published data +;File produced May 15, 2004 +;This file my be used or given away. All I ask is that this header +;is maintained to give credit to the people who produced the data. +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +K470ST 75 302 100 0.826 2.779 Animal_Motor_Works +0.028 699.309 +0.039 799.337 +0.09 765.845 +0.157 770.311 +0.258 785.941 +0.41 804 +0.572 794.425 +0.707 794.425 +0.886 792.192 +0.998 783.261 +1.15 752.002 +1.318 709.579 +1.447 655.992 +1.593 595.707 +1.728 522.025 +1.885 444.101 +2.092 354.923 +2.356 270.167 +2.664 187.554 +2.945 131.734 +3.27 78.058 +3.433 55.686 +3.478 48.987 +3.556 28.909 +3.7 0 diff --git a/datafiles/thrustcurves/AMW_K475.eng b/datafiles/thrustcurves/AMW_K475.eng new file mode 100644 index 000000000..8551ed5b4 --- /dev/null +++ b/datafiles/thrustcurves/AMW_K475.eng @@ -0,0 +1,38 @@ +; +;Animal Motor Works K475 RASP.ENG file made from NAR data +;File produced Feb 22, 2003 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +K475WW 54 403 100 0.7286 1.4925 Animal_Motor_Works +0.022 127.831 +0.041 386.016 +0.063 548.326 +0.096 521.308 +0.134 499.129 +0.18 486.83 +0.285 486.83 +0.478 501.649 +0.731 523.727 +1.096 553.266 +1.433 577.962 +1.601 588.29 +1.756 582.704 +1.895 580.284 +1.958 575.344 +2.063 550.746 +2.209 518.788 +2.344 477.051 +2.495 417.974 +2.561 354.058 +2.582 334.399 +2.599 331.98 +2.62 297.501 +2.67 226.226 +2.707 157.37 +2.74 98.353 +2.799 49.176 +2.853 17.208 +2.94 0 diff --git a/datafiles/thrustcurves/AMW_K530.eng b/datafiles/thrustcurves/AMW_K530.eng new file mode 100644 index 000000000..e1c460f51 --- /dev/null +++ b/datafiles/thrustcurves/AMW_K530.eng @@ -0,0 +1,43 @@ +; +;Animal Motor Works 54-1400 +;AMW K530GG RASP.ENG file made from NAR data +;File produced Feb 25, 2003 +;This file my be used or given away. All I ask is that this header +;is maintained to give credit to NAR S&T. Thank you, Jack Kane +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +K530GG 54 403 1000 0.7967 1.616 Animal_Motor_Works +0.013 129.764 +0.054 171.852 +0.096 284.122 +0.138 392.892 +0.171 455.975 +0.217 501.662 +0.238 498.063 +0.326 508.66 +0.542 564.745 +0.755 613.831 +1.01 645.423 +1.17 657.23 +1.273 648.922 +1.51 638.425 +1.656 634.925 +1.702 606.833 +1.803 606.833 +1.857 585.839 +1.936 589.338 +1.974 575.242 +2.015 589.338 +2.04 564.745 +2.132 536.652 +2.207 540.251 +2.291 522.656 +2.357 487.566 +2.42 375.297 +2.478 242.033 +2.529 140.361 +2.583 66.651 +2.66 0 diff --git a/datafiles/thrustcurves/AMW_K555.eng b/datafiles/thrustcurves/AMW_K555.eng new file mode 100644 index 000000000..522291a1d --- /dev/null +++ b/datafiles/thrustcurves/AMW_K555.eng @@ -0,0 +1,33 @@ +;Animal Motor Works 54-1750 K555 skidmark +;File provide by Joel Rogers of AMW +K555SK 54 492 0 0.8707 1.7343 AMW +0.063 507.328 +0.144 535.181 +0.226 559.826 +0.308 585.793 +0.389 607.239 +0.471 629.034 +0.553 664.586 +0.634 683.688 +0.716 697.625 +0.798 719.618 +0.879 756.521 +0.961 777.700 +1.043 789.004 +1.124 797.934 +1.206 801.689 +1.288 804.331 +1.369 799.414 +1.451 768.014 +1.533 704.469 +1.614 641.709 +1.696 568.727 +1.778 481.013 +1.859 401.614 +1.941 333.897 +2.023 277.226 +2.104 205.009 +2.186 129.425 +2.268 73.717 +2.349 22.380 +2.368 0.000 diff --git a/datafiles/thrustcurves/AMW_K560.eng b/datafiles/thrustcurves/AMW_K560.eng new file mode 100644 index 000000000..cebab5696 --- /dev/null +++ b/datafiles/thrustcurves/AMW_K560.eng @@ -0,0 +1,37 @@ +; +;AMW K560 RASP.ENG file made from NAR published data +;File produced April 19, 2004 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +K560RR 54 430 100 0.75 1.5866 Animal_Motor_Works +0.023 229.13 +0.046 415.135 +0.059 485.264 +0.078 512.268 +0.106 525.67 +0.154 523.05 +0.211 528.39 +0.261 536.451 +0.369 560.734 +0.511 587.738 +0.657 603.86 +0.77 612.022 +1.096 625.75 +1.358 620.083 +1.627 612.022 +1.839 603.86 +2.057 590.459 +2.218 598.52 +2.335 609.301 +2.385 601.24 +2.407 585.018 +2.426 533.831 +2.467 385.511 +2.507 283.037 +2.542 164.441 +2.576 67.399 +2.595 29.653 +2.62 0 diff --git a/datafiles/thrustcurves/AMW_K570.eng b/datafiles/thrustcurves/AMW_K570.eng new file mode 100644 index 000000000..7c32b5d2d --- /dev/null +++ b/datafiles/thrustcurves/AMW_K570.eng @@ -0,0 +1,28 @@ +; +;Animal Motor Works K570 White Wolf +K570WW 54 492 0 0.9146 1.8151 AMW +0.020 364.42 +0.030 664.79 +0.051 751.47 +0.071 745.81 +0.096 705.25 +0.137 674.93 +0.284 661.38 +0.528 651.24 +0.913 644.51 +1.192 651.24 +1.430 651.24 +1.649 651.24 +1.872 644.51 +2.176 624.23 +2.318 600.64 +2.394 597.33 +2.455 546.63 +2.501 485.89 +2.562 421.84 +2.597 340.83 +2.638 266.54 +2.734 175.48 +2.836 97.86 +2.927 47.24 +3.040 0.00 diff --git a/datafiles/thrustcurves/AMW_K600.eng b/datafiles/thrustcurves/AMW_K600.eng new file mode 100644 index 000000000..0d1e531fe --- /dev/null +++ b/datafiles/thrustcurves/AMW_K600.eng @@ -0,0 +1,40 @@ +; +; Animal Motor Works K600 RASP.ENG file made from NAR data +; File produced August 22, 2002 +; The total impulse, peak thrust, average thrust and burn time are +; the same as the averaged static test data on the NAR web site in +; the certification file. The curve drawn with these data points is as +; close to the certification curve as can be with such a limited +; number of points (32) allowed with wRASP up to v1.6. +K600 75 368 P 1.2233 2.9129 AMW + 0.010 412.229 + 0.029 522.21 + 0.059 547.215 + 0.083 524.8 + 0.122 497.305 + 0.181 484.852 + 0.333 495.113 + 0.690 560.464 + 1.195 643.548 + 1.400 673.833 + 1.420 708.799 + 1.508 701.427 + 1.591 721.551 + 1.782 731.712 + 2.017 752.035 + 2.174 756.816 + 2.257 765.2 + 2.502 766.44 + 2.727 752.931 + 2.918 738.187 + 3.143 705.91 + 3.408 643.847 + 3.603 569.131 + 3.692 526.793 + 3.745 439.426 + 3.799 289.596 + 3.883 112.272 + 3.922 64.862 + 3.971 37.437 + 3.995 22.474 + 4.070 0 diff --git a/datafiles/thrustcurves/AMW_K600_1.eng b/datafiles/thrustcurves/AMW_K600_1.eng new file mode 100644 index 000000000..2d078aadb --- /dev/null +++ b/datafiles/thrustcurves/AMW_K600_1.eng @@ -0,0 +1,34 @@ +; +;Animal Motor Works K600 White Wolf +K600WW 75 368 0 1.2233 2.9129 AMW +0.010 412.229 +0.029 522.21 +0.059 547.215 +0.083 524.8 +0.122 497.305 +0.181 484.852 +0.333 495.113 +0.690 560.464 +1.195 643.548 +1.400 673.833 +1.420 708.799 +1.508 701.427 +1.591 721.551 +1.782 731.712 +2.017 752.035 +2.174 756.816 +2.257 765.2 +2.502 766.44 +2.727 752.931 +2.918 738.187 +3.143 705.91 +3.408 643.847 +3.603 569.131 +3.692 526.793 +3.745 439.426 +3.799 289.596 +3.883 112.272 +3.922 64.862 +3.971 37.437 +3.995 22.474 +4.070 0 diff --git a/datafiles/thrustcurves/AMW_K605.eng b/datafiles/thrustcurves/AMW_K605.eng new file mode 100644 index 000000000..da7701a94 --- /dev/null +++ b/datafiles/thrustcurves/AMW_K605.eng @@ -0,0 +1,31 @@ +; +;AMW K605 RASP.ENG file made from NAR published data +;File produced April 19, 2004 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +K605RR 75 368 100 1.231 2.7688 Animal_Motor_Works +0.03 165.845 +0.053 309.12 +0.077 361.916 +0.142 392.042 +0.527 501.412 +0.988 606.905 +1.515 682.37 +2.101 730.593 +2.355 737.58 +2.692 731.289 +3 712.497 +3.361 671.036 +3.503 663.479 +3.586 659.701 +3.645 633.353 +3.692 573 +3.734 444.838 +3.775 297.785 +3.828 162.066 +3.864 98.015 +3.905 41.471 +3.95 0 diff --git a/datafiles/thrustcurves/AMW_K650.eng b/datafiles/thrustcurves/AMW_K650.eng new file mode 100644 index 000000000..e3789b207 --- /dev/null +++ b/datafiles/thrustcurves/AMW_K650.eng @@ -0,0 +1,38 @@ +; Animal Motor Works 54-1750 +; AMW K650RR RASP.ENG file made from NAR published data +; File produced April 19, 2004 +; This file my be used or given away. All I ask is that this header +; is maintained to give credit to NAR S&T. Thank you, Jack Kane +; The total impulse, peak thrust, average thrust and burn time are +; the same as the averaged static test data on the NAR web site in +; the certification file. The curve drawn with these data points is as +; close to the certification curve as can be with such a limited +; number of points (32) allowed with wRASP up to v1.6. +K650RR 54 492 0 0.931 1.8087 AMW +0.022 308.257 +0.045 566.480 +0.058 620.440 +0.081 639.668 +0.135 639.668 +0.229 643.494 +0.351 662.823 +0.594 701.380 +0.810 724.434 +0.999 743.763 +1.151 751.220 +1.381 747.588 +1.610 736.001 +1.835 709.031 +2.073 685.876 +2.244 674.400 +2.334 682.051 +2.429 685.876 +2.469 666.648 +2.528 597.285 +2.573 481.714 +2.609 358.391 +2.631 250.471 +2.681 146.477 +2.721 65.507 +2.748 23.124 +2.770 0.000 diff --git a/datafiles/thrustcurves/AMW_K670.eng b/datafiles/thrustcurves/AMW_K670.eng new file mode 100644 index 000000000..910d0dc71 --- /dev/null +++ b/datafiles/thrustcurves/AMW_K670.eng @@ -0,0 +1,34 @@ +; +; AMW K670 RASP.ENG file made from NAR published data +; File produced SEPT 4, 2002 +; The total impulse, peak thrust, average thrust and burn time are +; the same as the averaged static test data on the NAR web site in +; the certification file. The curve drawn with these data points is as +; close to the certification curve as can be with such a limited +; number of points (32) allowed with wRASP up to v1.6. +K670 54 492 P- 1.0140 1.9145 AMW + 0.016 294.05 + 0.035 398.577 + 0.086 506.292 + 0.153 496.428 + 0.264 506.093 + 0.461 558.108 + 0.722 629.553 + 0.983 688.044 + 1.116 714.051 + 1.193 785.795 + 1.409 788.784 + 1.737 804.56 + 2.074 781.41 + 2.195 764.87 + 2.226 781.211 + 2.277 764.77 + 2.398 751.517 + 2.440 744.941 + 2.468 718.834 + 2.484 666.521 + 2.525 418.107 + 2.551 218.818 + 2.573 120.768 + 2.595 52.143 + 2.620 0 diff --git a/datafiles/thrustcurves/AMW_K670_1.eng b/datafiles/thrustcurves/AMW_K670_1.eng new file mode 100644 index 000000000..fecc65a97 --- /dev/null +++ b/datafiles/thrustcurves/AMW_K670_1.eng @@ -0,0 +1,28 @@ +; +;Animal Motor Works K670 Green Gorilla +K670GG 54 492 0 1.0140 1.9145 AMW +0.016 294.05 +0.035 398.577 +0.086 506.292 +0.153 496.428 +0.264 506.093 +0.461 558.108 +0.722 629.553 +0.983 688.044 +1.116 714.051 +1.193 785.795 +1.409 788.784 +1.737 804.56 +2.074 781.41 +2.195 764.87 +2.226 781.211 +2.277 764.77 +2.398 751.517 +2.440 744.941 +2.468 718.834 +2.484 666.521 +2.525 418.107 +2.551 218.818 +2.573 120.768 +2.595 52.143 +2.620 0 diff --git a/datafiles/thrustcurves/AMW_K700.eng b/datafiles/thrustcurves/AMW_K700.eng new file mode 100644 index 000000000..5529b06f0 --- /dev/null +++ b/datafiles/thrustcurves/AMW_K700.eng @@ -0,0 +1,36 @@ +; +;AMW K700 RASP.ENG file made from NAR published data +;File produced April 19, 2004 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +K700BB 54 430 100 0.754 1.4831 Animal_Motor_Works +0.014 359.559 +0.022 625.425 +0.03 737.756 +0.047 771.505 +0.082 786.516 +0.106 771.505 +0.144 775.233 +0.272 786.516 +0.477 812.71 +0.693 842.632 +0.97 847.06 +1.283 838.904 +1.516 816.438 +1.706 801.427 +1.779 793.972 +1.811 775.233 +1.841 726.573 +1.873 625.425 +1.909 509.367 +1.95 393.208 +1.982 337.093 +2.035 292.16 +2.073 228.489 +2.111 153.535 +2.155 86.137 +2.193 37.446 +2.24 0 diff --git a/datafiles/thrustcurves/AMW_K800.eng b/datafiles/thrustcurves/AMW_K800.eng new file mode 100644 index 000000000..19f99dedf --- /dev/null +++ b/datafiles/thrustcurves/AMW_K800.eng @@ -0,0 +1,35 @@ +; This file my be used or given away. All I ask is that this header +; is maintained to give credit to NAR S&T. Thank you, Jack Kane +; The total impulse, peak thrust, average thrust and burn time are +; the same as the averaged static test data on the NAR web site in +; the certification file. The curve drawn with these data points is as +; close to the certification curve as can be with such a limited +; number of points (32) allowed with wRASP up to v1.6. +K800BB 54 492 0 0.9140 1.7866 AMW +0.017 516.316 +0.035 745.845 +0.046 817.592 +0.090 860.560 +0.191 889.338 +0.270 908.424 +0.438 918.017 +0.689 945.892 +0.996 955.090 +1.325 922.713 +1.557 894.035 +1.726 874.949 +1.849 884.542 +1.920 894.035 +1.954 894.035 +1.984 855.863 +2.011 741.048 +2.049 592.859 +2.079 492.433 +2.113 430.280 +2.154 377.719 +2.196 329.854 +2.237 243.818 +2.275 152.986 +2.309 71.716 +2.339 33.465 +2.380 0.000 diff --git a/datafiles/thrustcurves/AMW_K950.eng b/datafiles/thrustcurves/AMW_K950.eng new file mode 100644 index 000000000..0ea38baa1 --- /dev/null +++ b/datafiles/thrustcurves/AMW_K950.eng @@ -0,0 +1,39 @@ +; +; AMW K950 RASP.ENG file made from NAR published data +; File produced SEPT 4, 2002 +; The total impulse, peak thrust, average thrust and burn time are +; the same as the averaged static test data on the NAR web site in +; the certification file. The curve drawn with these data points is as +; close to the certification curve as can be with such a limited +; number of points (32) allowed with wRASP up to v1.6. +K950 54 492 P .8874 1.7949 AMW + 0.011 771.836 + 0.025 1204.520 + 0.039 1083.244 + 0.053 1158.054 + 0.067 1036.364 + 0.085 1110.176 + 0.099 1022.399 + 0.135 982.102 + 0.220 968.835 + 0.404 1010.430 + 0.566 1044.343 + 0.701 1079.254 + 0.867 1106.186 + 0.995 1134.115 + 1.211 1114.166 + 1.313 1101.199 + 1.430 1067.285 + 1.529 1020.404 + 1.579 993.772 + 1.642 892.430 + 1.674 818.119 + 1.717 757.273 + 1.738 621.918 + 1.766 466.313 + 1.791 351.306 + 1.823 249.864 + 1.865 175.553 + 1.908 87.696 + 1.943 33.654 + 1.970 0.000 diff --git a/datafiles/thrustcurves/AMW_K950_1.eng b/datafiles/thrustcurves/AMW_K950_1.eng new file mode 100644 index 000000000..78854cc82 --- /dev/null +++ b/datafiles/thrustcurves/AMW_K950_1.eng @@ -0,0 +1,33 @@ +; +;Animal Motor Works K950 Super Tiger +K950ST 54 492 0 .8874 1.7949 AMW +0.011 771.836 +0.025 1204.520 +0.039 1083.244 +0.053 1158.054 +0.067 1036.364 +0.085 1110.176 +0.099 1022.399 +0.135 982.102 +0.220 968.835 +0.404 1010.430 +0.566 1044.343 +0.701 1079.254 +0.867 1106.186 +0.995 1134.115 +1.211 1114.166 +1.313 1101.199 +1.430 1067.285 +1.529 1020.404 +1.579 993.772 +1.642 892.430 +1.674 818.119 +1.717 757.273 +1.738 621.918 +1.766 466.313 +1.791 351.306 +1.823 249.864 +1.865 175.553 +1.908 87.696 +1.943 33.654 +1.970 0.000 diff --git a/datafiles/thrustcurves/AMW_K975.eng b/datafiles/thrustcurves/AMW_K975.eng new file mode 100644 index 000000000..1d4be1643 --- /dev/null +++ b/datafiles/thrustcurves/AMW_K975.eng @@ -0,0 +1,35 @@ +; +;Animal Motor Works K975 White Wolf +K975WW 54 728 0 1.357 2.5985 AMW +0.017 526.644 +0.029 901.850 +0.038 1098.918 +0.046 1151.722 +0.076 1112.867 +0.130 1060.063 +0.219 1053.089 +0.336 1053.089 +0.479 1059.066 +0.609 1091.944 +0.866 1136.778 +1.046 1176.630 +1.164 1175.634 +1.202 1228.437 +1.239 1208.511 +1.315 1215.486 +1.353 1267.293 +1.387 1228.437 +1.487 1241.389 +1.538 1260.319 +1.634 1290.900 +1.723 1281.241 +1.794 1266.297 +1.836 1207.515 +1.933 1049.103 +1.992 851.437 +2.080 666.923 +2.118 640.521 +2.193 462.582 +2.269 212.311 +2.378 119.854 +2.510 0.000 diff --git a/datafiles/thrustcurves/AMW_L1060.eng b/datafiles/thrustcurves/AMW_L1060.eng new file mode 100644 index 000000000..b382e11fd --- /dev/null +++ b/datafiles/thrustcurves/AMW_L1060.eng @@ -0,0 +1,39 @@ +; +; AMW L1060 RASP.ENG file made from NAR published data +; File produced August 22, 2002 +; The total impulse, peak thrust, average thrust and burn time are +; the same as the averaged static test data on the NAR web site in +; the certification file. The curve drawn with these data points is as +; close to the certification curve as can be with such a limited +; number of points (32) allowed with wRASP up to v1.6. +L1060 75 497 P- 1.9188 3.9388 AMW + 0.020 258.773 + 0.024 368.235 + 0.032 328.386 + 0.076 427.96 + 0.100 567.284 + 0.116 751.352 + 0.128 791.202 + 0.169 816.071 + 0.225 816.071 + 0.309 875.795 + 0.518 985.257 + 0.763 1079.639 + 1.024 1174.519 + 1.308 1229.45 + 1.606 1288.375 + 1.782 1298.25 + 1.983 1293.369 + 2.256 1239.437 + 2.525 1184.506 + 2.822 1129.576 + 3.038 1069.651 + 3.111 1044.683 + 3.135 995.145 + 3.183 835.946 + 3.239 552.303 + 3.299 268.661 + 3.327 164.193 + 3.339 84.593 + 3.360 44.783 + 3.400 0 diff --git a/datafiles/thrustcurves/AMW_L1060_1.eng b/datafiles/thrustcurves/AMW_L1060_1.eng new file mode 100644 index 000000000..4f83de134 --- /dev/null +++ b/datafiles/thrustcurves/AMW_L1060_1.eng @@ -0,0 +1,33 @@ +; +;Animal Motor Works L1060 Green Gorilla +L1060GG 75 497 0 1.9188 3.9388 AMW +0.020 258.773 +0.024 368.235 +0.032 328.386 +0.076 427.96 +0.100 567.284 +0.116 751.352 +0.128 791.202 +0.169 816.071 +0.225 816.071 +0.309 875.795 +0.518 985.257 +0.763 1079.639 +1.024 1174.519 +1.308 1229.45 +1.606 1288.375 +1.782 1298.25 +1.983 1293.369 +2.256 1239.437 +2.525 1184.506 +2.822 1129.576 +3.038 1069.651 +3.111 1044.683 +3.135 995.145 +3.183 835.946 +3.239 552.303 +3.299 268.661 +3.327 164.193 +3.339 84.593 +3.360 44.783 +3.400 0 diff --git a/datafiles/thrustcurves/AMW_L1080.eng b/datafiles/thrustcurves/AMW_L1080.eng new file mode 100644 index 000000000..985fc4ec4 --- /dev/null +++ b/datafiles/thrustcurves/AMW_L1080.eng @@ -0,0 +1,35 @@ +; +;AMW L1080 RASP.ENG file made from NAR published data +;File produced April 19, 2004 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +L1080BB 75 497 100 1.717 3.5922 Animal_Motor_Works +0.024 406.295 +0.043 812.489 +0.052 895.202 +0.088 929.641 +0.314 991.55 +0.626 1087.69 +0.988 1163.44 +1.346 1218.99 +1.638 1246.25 +1.864 1257.91 +2.247 1254.84 +2.6 1218.99 +2.766 1211.92 +2.851 1197.78 +2.942 1204.85 +3.002 1226.06 +3.033 1204.85 +3.089 1040.23 +3.124 874.499 +3.15 660.999 +3.191 461.336 +3.232 275.408 +3.268 144.622 +3.303 75.744 +3.339 41.316 +3.39 0 diff --git a/datafiles/thrustcurves/AMW_L1100.eng b/datafiles/thrustcurves/AMW_L1100.eng new file mode 100644 index 000000000..7ab349dcf --- /dev/null +++ b/datafiles/thrustcurves/AMW_L1100.eng @@ -0,0 +1,33 @@ +; +;AMW L1100 RASP.ENG file made from NAR published data +;File produced April 19, 2004 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +L1100RR 75 728 100 1.346 2.5881 Animal_Motor_Works +0.013 681.489 +0.029 1116.88 +0.041 1196.3 +0.079 1210.38 +0.147 1203.34 +0.257 1218.42 +0.366 1225.45 +0.567 1254.61 +0.824 1282.76 +1.059 1311.91 +1.267 1340.23 +1.459 1311.91 +1.622 1297.84 +1.713 1290.8 +1.785 1268.68 +1.83 1218.42 +1.886 1080.69 +1.969 819.214 +2.048 558.24 +2.108 376.985 +2.156 246.498 +2.205 144.963 +2.269 72.501 +2.35 0 diff --git a/datafiles/thrustcurves/AMW_L1111.eng b/datafiles/thrustcurves/AMW_L1111.eng new file mode 100644 index 000000000..e8b36e861 --- /dev/null +++ b/datafiles/thrustcurves/AMW_L1111.eng @@ -0,0 +1,26 @@ +; +;L1111ST entered by Tim Van Milligan +;For RockSim - http://www.rocksim.com +;Based on TRA Certification paperwork from 06-01-2002 +;Initial Mass from Jim Robinson at AMW +;Not approved by TRA or AMW. +L1111ST 75 497 100 1.642 3.517 Animal_Motor_Works +0.015 1023.97 +0.1 924.878 +0.147 902.857 +0.502 1034.98 +0.75 1156.1 +1.005 1266.2 +1.229 1354.29 +1.492 1398.33 +1.739 1398.33 +2.009 1354.29 +2.272 1244.18 +2.504 1123.07 +2.728 968.92 +2.782 902.857 +2.836 770.732 +2.98 363.345 +3.053 99.094 +3.083 22.021 +3.14 0 diff --git a/datafiles/thrustcurves/AMW_L1300.eng b/datafiles/thrustcurves/AMW_L1300.eng new file mode 100644 index 000000000..f36b59f8d --- /dev/null +++ b/datafiles/thrustcurves/AMW_L1300.eng @@ -0,0 +1,35 @@ +; +;AMW L1300 RASP.ENG file made from NAR published data +;File produced April 19, 2004 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +L1300BB 75 728 100 1.314 2.5454 Animal_Motor_Works +0.014 710.467 +0.025 1247.64 +0.039 1384.13 +0.053 1447.83 +0.074 1420.53 +0.12 1447.83 +0.276 1474.12 +0.475 1519.61 +0.712 1555 +0.942 1586.74 +1.147 1562.08 +1.36 1534.78 +1.484 1551.97 +1.537 1551.97 +1.569 1497.37 +1.59 1406.38 +1.604 1451.87 +1.615 1333.58 +1.64 1168.78 +1.689 986.687 +1.753 767.749 +1.824 512.503 +1.891 275.512 +1.933 147.816 +1.987 74.737 +2.06 0 diff --git a/datafiles/thrustcurves/AMW_L1400.eng b/datafiles/thrustcurves/AMW_L1400.eng new file mode 100644 index 000000000..b83ca81c8 --- /dev/null +++ b/datafiles/thrustcurves/AMW_L1400.eng @@ -0,0 +1,35 @@ +; @File: SK-75-6000.txt, @Pts-I: 3609, @Pts-O: 31, @Sm: 6, @CO: 5% +; @TI: 4740.56, @TIa: 4732.91, @TIe: 0.0%, @ThMax: 1908.398, @ThAvg: 1382.678, @Tb: 3.423 +; Exported using ThrustCurveTool, www.ThrustGear.com, by John DeMar +L1400SK 75 785 P 2.8267 5.1985 AMW + 0.0 68.1234 + 0.0040 193.7893 + 0.016 690.259 + 0.021 814.579 + 0.027 900.741 + 0.045 997.475 + 0.076 1251.156 + 0.092 1354.553 + 0.107 1405.971 + 0.132 1440.082 + 0.169 1453.774 + 0.368 1397.446 + 0.525 1411.875 + 0.705 1488.288 + 1.082 1734.489 + 1.414 1906.629 + 1.556 1875.238 + 1.766 1882.261 + 1.899 1803.008 + 2.142 1745.497 + 2.34 1659.082 + 2.504 1522.458 + 2.58 1402.287 + 2.819 844.839 + 2.847 841.674 + 2.893 730.795 + 3.068 406.536 + 3.176 265.8 + 3.425 94.9644 + 3.608 0.874524 + 3.609 0 diff --git a/datafiles/thrustcurves/AMW_L666.eng b/datafiles/thrustcurves/AMW_L666.eng new file mode 100644 index 000000000..7379b8926 --- /dev/null +++ b/datafiles/thrustcurves/AMW_L666.eng @@ -0,0 +1,34 @@ +;Animal Motor Works 75-3500 +L666SK 75 497 0 1.8877 3.5344 AMW +0.096 105.880 +0.175 509.783 +0.312 549.481 +0.449 577.319 +0.586 602.900 +0.722 615.605 +0.859 632.540 +0.996 652.072 +1.133 671.418 +1.270 685.671 +1.407 701.286 +1.543 718.069 +1.680 734.116 +1.817 753.292 +1.954 771.589 +2.091 790.453 +2.228 819.222 +2.364 846.663 +2.501 874.629 +2.638 890.083 +2.775 898.271 +2.912 899.312 +3.049 881.683 +3.185 845.157 +3.322 768.451 +3.459 672.771 +3.596 525.466 +3.733 304.694 +3.870 86.663 +3.968 0.000 +; +; diff --git a/datafiles/thrustcurves/AMW_L700.eng b/datafiles/thrustcurves/AMW_L700.eng new file mode 100644 index 000000000..0f7f0ce38 --- /dev/null +++ b/datafiles/thrustcurves/AMW_L700.eng @@ -0,0 +1,29 @@ +; +; +L700BB 75.0 368.00 100 1.19310 2.73200 AMW + 0.02 221.87 + 0.03 399.33 + 0.05 467.56 + 0.08 494.89 + 0.13 498.41 + 0.24 535.99 + 0.48 614.67 + 0.77 683.20 + 1.23 755.25 + 1.62 789.72 + 1.92 810.42 + 2.26 821.14 + 2.58 817.85 + 2.91 801.07 + 3.14 773.94 + 3.25 750.13 + 3.32 743.39 + 3.37 729.83 + 3.42 688.83 + 3.46 593.37 + 3.50 484.14 + 3.53 368.18 + 3.57 248.80 + 3.62 149.82 + 3.66 61.13 + 3.72 0.00 diff --git a/datafiles/thrustcurves/AMW_L777.eng b/datafiles/thrustcurves/AMW_L777.eng new file mode 100644 index 000000000..0e6c65c56 --- /dev/null +++ b/datafiles/thrustcurves/AMW_L777.eng @@ -0,0 +1,40 @@ +; +; AMW L777 RASP.ENG file made from NAR published data +; File produced SEPT 4, 2002 +; The total impulse, peak thrust, average thrust and burn time are +; the same as the averaged static test data on the NAR web site in +; the certification file. The curve drawn with these data points is as +; close to the certification curve as can be with such a limited +; number of points (32) allowed with wRASP up to v1.6. +L777 75 497 P 1.7623 3.6987 AMW + 0.025 140.882 + 0.064 209.474 + 0.108 360.055 + 0.204 652.185 + 0.360 641.518 + 0.373 693.745 + 0.418 683.28 + 0.528 730.073 + 0.670 761.268 + 0.761 781.998 + 0.787 802.828 + 0.871 802.728 + 1.065 854.754 + 1.338 911.811 + 1.668 963.636 + 1.914 989.498 + 2.115 1000.16 + 2.368 962.831 + 2.647 926 + 2.985 878.603 + 3.303 805.143 + 3.472 752.815 + 3.550 705.72 + 3.602 648.26 + 3.647 611.631 + 3.693 512.409 + 3.779 334.897 + 3.857 178.216 + 3.935 89.379 + 3.981 26.687 + 4.050 0 diff --git a/datafiles/thrustcurves/AMW_L777_1.eng b/datafiles/thrustcurves/AMW_L777_1.eng new file mode 100644 index 000000000..a908a0d0d --- /dev/null +++ b/datafiles/thrustcurves/AMW_L777_1.eng @@ -0,0 +1,34 @@ +; +;Animal Motor Works L777 White Wolf +L777WW 75 497 0 1.7623 3.6987 AMW +0.025 140.882 +0.064 209.474 +0.108 360.055 +0.204 652.185 +0.360 641.518 +0.373 693.745 +0.418 683.28 +0.528 730.073 +0.670 761.268 +0.761 781.998 +0.787 802.828 +0.871 802.728 +1.065 854.754 +1.338 911.811 +1.668 963.636 +1.914 989.498 +2.115 1000.16 +2.368 962.831 +2.647 926 +2.985 878.603 +3.303 805.143 +3.472 752.815 +3.550 705.72 +3.602 648.26 +3.647 611.631 +3.693 512.409 +3.779 334.897 +3.857 178.216 +3.935 89.379 +3.981 26.687 +4.050 0 diff --git a/datafiles/thrustcurves/AMW_L900.eng b/datafiles/thrustcurves/AMW_L900.eng new file mode 100644 index 000000000..4299eed0b --- /dev/null +++ b/datafiles/thrustcurves/AMW_L900.eng @@ -0,0 +1,37 @@ +; +;AMW L900 RASP.ENG file made from NAR published data +;File produced April 19, 2004 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +L900RR 75 497 100 1.771 3.5888 Animal_Motor_Works +0.029 464.292 +0.053 630.937 +0.059 684.506 +0.096 702.328 +0.133 696.387 +0.201 714.311 +0.486 803.524 +0.777 910.661 +1.099 988.093 +1.26 1041.16 +1.284 1071.37 +1.378 1053.24 +1.607 1101.57 +1.917 1142.86 +2.208 1173.56 +2.413 1160.98 +2.624 1107.62 +2.866 976.211 +3.053 886.897 +3.208 839.27 +3.314 827.388 +3.382 809.465 +3.432 720.252 +3.495 547.564 +3.57 345.273 +3.627 214.273 +3.714 77.382 +3.79 0 diff --git a/datafiles/thrustcurves/AMW_M1350.eng b/datafiles/thrustcurves/AMW_M1350.eng new file mode 100644 index 000000000..c3799a6d2 --- /dev/null +++ b/datafiles/thrustcurves/AMW_M1350.eng @@ -0,0 +1,21 @@ +; +;Animal Motor Works M1350 White Wolf +M1350WW 75 781 0 2.92700 5.40300 AMW +0.03 1197.771588 +0.04 1465.181058 +0.07 1660.167131 +0.09 1665.738162 +0.16 1587.743733 +0.45 1587.743733 +0.61 1576.601671 +1.86 1649.02507 +2.27 1643.454039 +2.64 1598.885794 +3.18 1504.178273 +3.29 1353.760446 +3.41 991.643454 +3.49 841.2256267 +3.62 646.2395543 +3.74 428.9693593 +3.90 373.2590529 +4.22 0 diff --git a/datafiles/thrustcurves/AMW_M1480.eng b/datafiles/thrustcurves/AMW_M1480.eng new file mode 100644 index 000000000..10efe8a79 --- /dev/null +++ b/datafiles/thrustcurves/AMW_M1480.eng @@ -0,0 +1,37 @@ +; +;AMW M1480 RASP.ENG file made from NAR published data +;File produced April 19, 2004 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +M1480RR 75 785 100 3 5.5248 Animal_Motor_Works +0.022 713.002 +0.032 1254.68 +0.055 1473.37 +0.078 1569.11 +0.156 1569.11 +0.352 1559.03 +0.642 1597.33 +0.974 1644.69 +1.289 1702.13 +1.52 1739.42 +1.918 1796.87 +2.279 1814.83 +2.481 1796.87 +2.707 1739.42 +2.968 1644.69 +3.058 1616.47 +3.135 1520.73 +3.218 1378.64 +3.284 1217.39 +3.332 1065.22 +3.344 1140.8 +3.368 1016.85 +3.41 741.522 +3.5 522.935 +3.613 275.727 +3.691 171.12 +3.768 66.553 +3.85 0 diff --git a/datafiles/thrustcurves/AMW_M1730.eng b/datafiles/thrustcurves/AMW_M1730.eng new file mode 100644 index 000000000..5c636e224 --- /dev/null +++ b/datafiles/thrustcurves/AMW_M1730.eng @@ -0,0 +1,36 @@ +; +;Animal Motor Works 98-11000 +M1730SK 98 870 0 4.9452 9.8718 AMW +0.040 682.642 +0.064 1153.387 +0.221 1354.665 +0.269 1414.771 +0.381 1458.026 +0.541 1526.924 +0.701 1589.200 +0.861 1675.203 +1.021 1732.669 +1.181 1802.227 +1.341 1886.644 +1.500 1973.713 +1.660 2070.514 +1.820 2183.822 +1.980 2299.313 +2.140 2433.862 +2.300 2568.119 +2.460 2679.423 +2.620 2638.376 +2.780 2484.185 +2.940 2306.038 +3.099 2173.849 +3.259 2074.688 +3.419 1961.303 +3.579 1807.810 +3.739 1640.258 +3.899 1303.035 +4.059 940.600 +4.219 567.152 +4.379 309.143 +4.539 188.981 +4.637 0.000 +; diff --git a/datafiles/thrustcurves/AMW_M1850.eng b/datafiles/thrustcurves/AMW_M1850.eng new file mode 100644 index 000000000..72764e00b --- /dev/null +++ b/datafiles/thrustcurves/AMW_M1850.eng @@ -0,0 +1,18 @@ +; +; Animal Motor Works M1850GG +; estimated from TRA graph by John DeMar jsdemar@syr.edu +; motor mass is a guess based on similar types +M1850GG 75 781 0 3.3750 4.5000 AMW + 0.08 979.00 + 0.13 1180.00 + 0.28 1290.00 + 0.33 1468.00 + 0.73 1936.00 + 1.33 2202.00 + 1.73 2279.00 + 2.58 2105.00 + 2.83 2007.00 + 2.88 1860.00 + 3.08 538.00 + 3.20 174.00 + 3.30 0.00 diff --git a/datafiles/thrustcurves/AMW_M1850_1.eng b/datafiles/thrustcurves/AMW_M1850_1.eng new file mode 100644 index 000000000..48a109a31 --- /dev/null +++ b/datafiles/thrustcurves/AMW_M1850_1.eng @@ -0,0 +1,30 @@ +; +;Animal Motor Works M1850 Green Gorilla +M1850GG 75 781 0 3.37000 5.85100 AMW +0.12 1201.01994 +0.25 1321.121934 +0.37 1579.11881 +0.50 1699.220804 +0.62 1846.01213 +0.75 1930.528348 +0.87 1997.251678 +1.00 2059.526786 +1.12 2126.250116 +1.25 2192.973446 +1.37 2224.111 +1.50 2246.35211 +1.62 2268.59322 +1.75 2277.489664 +1.87 2268.59322 +2.00 2246.35211 +2.12 2224.111 +2.25 2192.973446 +2.37 2166.284114 +2.50 2144.043004 +2.62 2099.560784 +2.75 2046.18212 +2.87 1912.73546 +3.00 831.817514 +3.12 311.37554 +3.25 84.516218 +3.3 0.000 diff --git a/datafiles/thrustcurves/AMW_M1900.eng b/datafiles/thrustcurves/AMW_M1900.eng new file mode 100644 index 000000000..8b85d34e9 --- /dev/null +++ b/datafiles/thrustcurves/AMW_M1900.eng @@ -0,0 +1,38 @@ +; +;AMW M1900 RASP.ENG file made from NAR published data +;File produced April 19, 2004 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +M1900BB 75 785 100 2.733 5.4225 Animal_Motor_Works +0.018 1109.21 +0.044 1761.75 +0.061 1910.65 +0.085 1938.62 +0.159 1929.63 +0.29 1956.62 +0.409 2031.56 +0.438 1974.6 +0.569 2011.58 +0.815 2104.51 +1.073 2197.44 +1.401 2280.39 +1.688 2324.7 +1.905 2297.37 +2.073 2241.41 +2.254 2138.49 +2.397 2063.54 +2.479 2016.57 +2.54 2025.57 +2.581 2006.58 +2.63 1885.67 +2.716 1493.94 +2.805 1120.21 +2.887 840.605 +2.972 569.996 +3.046 299.488 +3.119 150.193 +3.168 56.829 +3.23 0 diff --git a/datafiles/thrustcurves/AMW_M2500.eng b/datafiles/thrustcurves/AMW_M2500.eng new file mode 100644 index 000000000..6634e3119 --- /dev/null +++ b/datafiles/thrustcurves/AMW_M2500.eng @@ -0,0 +1,33 @@ +; +;Animal Motor Works M2500 Green Gorilla +M2500GG 75 1039 0 4.248 7.5515 AMW +0.026 1288.791 +0.053 2021.398 +0.079 2140.011 +0.123 2105.125 +0.207 2117.086 +0.540 2309.458 +0.971 2560.637 +1.265 2727.094 +1.480 2836.736 +1.678 2920.462 +1.757 2980.267 +1.946 2995.51 +2.047 2959.335 +2.240 2889.563 +2.310 2854.677 +2.486 2820.788 +2.526 2880.593 +2.592 2773.941 +2.653 2821.785 +2.706 2752.012 +2.758 2752.012 +2.807 2763.973 +2.842 2504.82 +2.886 2115.092 +2.930 1630.674 +2.987 1051.565 +3.040 437.571 +3.057 284.072 +3.079 142.434 +3.110 0 diff --git a/datafiles/thrustcurves/AMW_M3000.eng b/datafiles/thrustcurves/AMW_M3000.eng new file mode 100644 index 000000000..c5b66ceda --- /dev/null +++ b/datafiles/thrustcurves/AMW_M3000.eng @@ -0,0 +1,32 @@ +; +; Animal Motor Works M3000ST +; estimated from TRA graph by Rob Bazinet rbazinet66@hotmail.com +; motor mass is a guess based on similar types +M3000ST 75 1038 0 3.8190 6.72 AMW + 0.032 2494.225 + 0.113 2621.05 + 0.242 2705.6 + 0.355 2811.288 + 0.435 2895.838 + 0.5 2959.25 + 0.645 3128.35 + 0.75 3297.45 + 0.871 3382 + 0.968 3551.1 + 1.032 3656.788 + 1.145 3804.75 + 1.355 3973.85 + 1.452 4037.263 + 1.629 4079.538 + 1.742 4142.95 + 1.903 4185.225 + 1.935 3847.025 + 2.081 3424.275 + 2.129 2959.25 + 2.177 2536.5 + 2.194 2113.75 + 2.226 1691 + 2.274 1268.25 + 2.323 845.5 + 2.403 422.75 + 2.5 0 diff --git a/datafiles/thrustcurves/AMW_N2020.eng b/datafiles/thrustcurves/AMW_N2020.eng new file mode 100644 index 000000000..d26216d18 --- /dev/null +++ b/datafiles/thrustcurves/AMW_N2020.eng @@ -0,0 +1,35 @@ +; +;Animal Motor Works 98-11000 +N2020WT 98 870 0 5.1609 9.9693 AMW +.106 1941.344 +0.221 2151.149 +0.381 2253.406 +0.541 2340.792 +0.701 2400.847 +0.861 2453.821 +1.021 2506.314 +1.181 2556.306 +1.341 2607.251 +1.500 2652.790 +1.660 2688.660 +1.820 2710.675 +1.980 2729.797 +2.140 2733.895 +2.300 2704.255 +2.460 2634.582 +2.620 2532.160 +2.780 2433.380 +2.940 2329.740 +3.099 2234.246 +3.259 2165.804 +3.419 2099.684 +3.579 2028.350 +3.739 1951.013 +3.899 1871.316 +4.059 1558.113 +4.219 1053.376 +4.379 890.506 +4.539 636.689 +4.998 0.000 + +; diff --git a/datafiles/thrustcurves/AMW_N2600.eng b/datafiles/thrustcurves/AMW_N2600.eng new file mode 100644 index 000000000..93afa680c --- /dev/null +++ b/datafiles/thrustcurves/AMW_N2600.eng @@ -0,0 +1,26 @@ +; +;Animal Motor Works 98-11000 +N2600GG 98 870 1000 4.8812 10.4726 Animal_Motor_Works +0.024 1674.37 +0.064 1949.62 +0.104 2039.52 +0.306 2189.98 +0.508 2334.45 +0.709 2491.23 +0.911 2668.93 +1.113 2874.7 +1.314 3038.83 +1.516 3191.29 +1.718 3266.01 +1.92 3318.98 +2.121 3336.18 +2.323 3229.26 +2.525 3089.68 +2.726 2943.98 +2.928 2847.69 +3.13 2751.68 +3.331 2682.22 +3.533 2463.48 +3.735 1339.63 +3.937 269.834 +4.034 0 diff --git a/datafiles/thrustcurves/AMW_N2700.eng b/datafiles/thrustcurves/AMW_N2700.eng new file mode 100644 index 000000000..56d7f9075 --- /dev/null +++ b/datafiles/thrustcurves/AMW_N2700.eng @@ -0,0 +1,26 @@ +; +;Animal Motor Works 98-11000 +N2700BB 98 870 1000 4.7837 9.9308 Animal_Motor_Works +0.027 2229.53 +0.069 2476.18 +0.111 2539.74 +0.36 2723.21 +0.527 2863.83 +0.735 3016.48 +0.943 3141.25 +1.151 3241.72 +1.359 3335.56 +1.567 3519.92 +1.775 3425.88 +1.983 3420.56 +2.191 3356.08 +2.399 3270.48 +2.607 3182.6 +2.815 3098.31 +3.023 3002.95 +3.231 2888.73 +3.439 2266.61 +3.647 1498.26 +3.855 780.04 +4.063 233.545 +4.16 0 diff --git a/datafiles/thrustcurves/AMW_N2800.eng b/datafiles/thrustcurves/AMW_N2800.eng new file mode 100644 index 000000000..1c5f9542b --- /dev/null +++ b/datafiles/thrustcurves/AMW_N2800.eng @@ -0,0 +1,35 @@ +; @File: N2800b.txt, @Pts-I: 5383, @Pts-O: 31, @Sm: 8, @CO: 5% +; @TI: 14810.26, @TIa: 14792.71, @TIe: 0.0%, @ThMax: 3650.74, @ThAvg: 2770.17, @Tb: 5.34 +; Exported using ThrustCurveTool, www.ThrustGear.com, by John DeMar +N2800 98 1213 100 7.6947 13.8 AMW + 0.0 93.0947 + 0.0020 168.347 + 0.0060 387.836 + 0.019 1271.166 + 0.029 1776.342 + 0.043 2298.6 + 0.062 2841.03 + 0.072 3021.31 + 0.084 3128.89 + 0.14 3296.17 + 0.277 3483.35 + 0.293 3431.67 + 0.369 3495.76 + 0.978 3598.65 + 1.973 3655.16 + 2.977 3534.8 + 3.3 3437.12 + 3.497 3308.46 + 3.583 3193.8 + 3.651 3015.65 + 3.748 2548.37 + 3.836 2223.91 + 4.109 1644.077 + 4.245 1443.685 + 4.272 1447.012 + 4.397 1163.584 + 4.489 1022.953 + 4.516 1057.203 + 4.574 883.885 + 4.647 776.407 + 5.569 0.0 diff --git a/datafiles/thrustcurves/AMW_N4000.eng b/datafiles/thrustcurves/AMW_N4000.eng new file mode 100644 index 000000000..e001573c9 --- /dev/null +++ b/datafiles/thrustcurves/AMW_N4000.eng @@ -0,0 +1,28 @@ +; +;Animal Motor Works 98-17500 +N4000BB 98 1213 0 6.1026 13.6683 AMW +0.029 4207.591 +0.071 4709.549 +0.113 4906.310 +0.155 5007.780 +0.239 5041.557 +0.323 4993.595 +0.534 5046.912 +0.744 5145.819 +0.954 5248.063 +1.165 5293.196 +1.375 5232.456 +1.585 5209.528 +1.796 5165.473 +2.006 5047.698 +2.216 4913.086 +2.427 4783.447 +2.637 4659.163 +2.847 4195.994 +3.058 2850.731 +3.268 1981.973 +3.478 1295.536 +3.689 907.699 +3.899 490.196 +4.110 316.338 +4.207 0.000 diff --git a/datafiles/thrustcurves/AeroTech_D13.eng b/datafiles/thrustcurves/AeroTech_D13.eng new file mode 100644 index 000000000..eec171aa8 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_D13.eng @@ -0,0 +1,40 @@ +; Aerotech D13 RASP.ENG file made from NAR published data +; File produced July 4, 2000 +; Submitted to ThrustCurve.org by Chris Kobel (3/29/07) +; The total impulse, peak thrust, average thrust and burn time are +; the same as the averaged static test data on the NAR web site in +; the certification file. The curve drawn with these data points is as +; close to the certification curve as can be with such a limited +; number of points (32) allowed with wRASP up to v1.6. +D13W 18 70 4-7-10 0.0098 0.0326 AT + 0.030 13.462 + 0.061 21.171 + 0.085 20.618 + 0.127 21.605 + 0.158 21.042 + 0.182 22.306 + 0.217 22.592 + 0.227 23.610 + 0.248 21.891 + 0.279 23.155 + 0.317 22.039 + 0.366 21.338 + 0.383 21.901 + 0.449 20.648 + 0.462 21.486 + 0.480 19.947 + 0.507 19.947 + 0.521 20.509 + 0.559 18.693 + 0.580 19.118 + 0.660 17.578 + 0.743 15.337 + 0.861 12.406 + 0.947 9.329 + 1.068 5.834 + 1.155 4.158 + 1.172 4.720 + 1.231 2.762 + 1.328 1.928 + 1.404 1.093 + 1.520 0.000 diff --git a/datafiles/thrustcurves/AeroTech_D15.eng b/datafiles/thrustcurves/AeroTech_D15.eng new file mode 100644 index 000000000..c863bf221 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_D15.eng @@ -0,0 +1,26 @@ +; Aerotech D15 RASP.ENG file made from NAR published data +; File produced July 4, 2000 +; Submitted to ThrustCurve.org by Chris Kobel (3/29/07) +; The total impulse, peak thrust, average thrust and burn time are +; the same as the averaged static test data on the NAR web site in +; the certification file. The curve drawn with these data points is as +; close to the certification curve as can be with such a limited +; number of points (32) allowed with wRASP up to v1.6. +D15T 24 70 4-6-8 .0089 .0440 AT + 0.014 11.480 + 0.049 26.272 + 0.081 30.087 + 0.107 31.261 + 0.121 31.249 + 0.159 31.360 + 0.208 31.249 + 0.283 29.583 + 0.439 23.353 + 0.551 18.484 + 0.675 13.430 + 0.863 6.422 + 0.938 3.892 + 1.010 2.335 + 1.085 0.778 + 1.142 0.389 + 1.150 0.000 diff --git a/datafiles/thrustcurves/AeroTech_D21.eng b/datafiles/thrustcurves/AeroTech_D21.eng new file mode 100644 index 000000000..7dcc85ac2 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_D21.eng @@ -0,0 +1,33 @@ +;Aerotech D21 RASP.ENG file made from NAR published data +;File produced July 4, 2000 +;Submitted to ThrustCurve.org by Chris Kobel (3/29/07) +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +D21T 18 70 4-7 0.0096 0.025 AT + 0.01 1.367 + 0.021 19.367 + 0.029 32.12 + 0.037 31.667 + 0.051 30.528 + 0.094 30.074 + 0.115 31.213 + 0.133 30.074 + 0.177 30.76 + 0.189 29.842 + 0.203 30.528 + 0.226 29.842 + 0.275 28.935 + 0.296 29.389 + 0.331 28.027 + 0.421 25.971 + 0.478 24.146 + 0.579 20.728 + 0.659 17.774 + 0.739 14.356 + 0.799 9.569 + 0.852 4.557 + 0.899 1.139 + 0.94 0 diff --git a/datafiles/thrustcurves/AeroTech_D24.eng b/datafiles/thrustcurves/AeroTech_D24.eng new file mode 100644 index 000000000..51f63ae04 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_D24.eng @@ -0,0 +1,48 @@ +; AeroTech D24T (Blue Thunder) RASP.ENG file made from +; manufacturers published data. +; +; File was produced May 07, 2004 by Stanley_Hemphill@Hotmail.com. +; +; The motor is listed in the www.thrustcurve.org database as an +; engine certified by NAR, but there is "no data" at the weblink +; to the NAR file database. +; +; The author has created this file by extracting the manufacturers +; Thrust-Time curve from The AeroTech-2002 Catalog, and then deploting +; 32 points using the distance measuring tools in Paint Shop Pro 8. +; The file was then created in RockSim 7 and the motor and static values +; were read from the RockSim Engine Editor. +; +; Motor Dia Len Delay Propellant Total Manufacturer +D24BT_CO_SU 18.00 70.00 4-7-10 0.00870 0.03200 AeroTech +0.0380 39.6000 +0.0550 36.5000 +0.0760 34.4000 +0.1220 32.4000 +0.1640 31.1000 +0.2190 30.3000 +0.2610 29.3000 +0.3080 28.7000 +0.3290 27.9000 +0.3580 26.9000 +0.3920 25.8000 +0.4340 25.0000 +0.4850 24.0000 +0.5390 23.2000 +0.5770 22.3000 +0.6200 20.9000 +0.6660 19.6000 +0.6950 18.0000 +0.7210 16.5000 +0.7500 15.5000 +0.7540 14.3000 +0.7590 12.4000 +0.7600 11.0000 +0.7630 09.1000 +0.7634 07.5000 +0.7710 05.9000 +0.7920 04.0000 +0.8300 02.6000 +0.8680 01.8000 +0.9000 01.1000 +0.9400 00.0000 diff --git a/datafiles/thrustcurves/AeroTech_D7.eng b/datafiles/thrustcurves/AeroTech_D7.eng new file mode 100644 index 000000000..3b46001d8 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_D7.eng @@ -0,0 +1,23 @@ +;Aerotech D7 RASP.ENG file made from NAR published data +D7 24 70 100 0.0105 0.0422 AT +0.036 3.336 +0.084 9.326 +0.101 10.281 +0.143 10.827 +0.213 10.99 +0.271 10.887 +0.359 10.685 +0.471 10.13 +0.506 10.342 +0.535 9.929 +0.81 8.697 +1.226 6.713 +1.589 5.138 +1.8 4.861 +2.151 4.581 +2.649 4.57 +2.696 3.887 +2.748 2.388 +2.807 0.889 +2.842 0.207 +2.87 0 diff --git a/datafiles/thrustcurves/AeroTech_D9.eng b/datafiles/thrustcurves/AeroTech_D9.eng new file mode 100644 index 000000000..9d3b607b8 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_D9.eng @@ -0,0 +1,23 @@ +; Aerotech D9 RASP.ENG file made from NAR published data +; File produced July 4, 2000 +; Submitted to ThrustCurve.org by Chris Kobel (3/29/07) +; The total impulse, peak thrust, average thrust and burn time are +; the same as the averaged static test data on the NAR web site in +; the certification file. The curve drawn with these data points is as +; close to the certification curve as can be with such a limited +; number of points (32) allowed with wRASP up to v1.6. +D9W 24 70 4-7 0.0101 0.045 AT + 0.1 13.7 + 0.15 15.4 + 0.2 16.3 + 0.25 16.8 + 0.35 17.2 + 0.40 17.2 + 0.50 16.8 + 0.65 15.9 + 0.80 14.5 + 1.10 9.2 + 1.25 7.0 + 1.40 4.8 + 1.60 2.5 + 1.90 0.0 diff --git a/datafiles/thrustcurves/AeroTech_E11.eng b/datafiles/thrustcurves/AeroTech_E11.eng new file mode 100644 index 000000000..4bdcce92b --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_E11.eng @@ -0,0 +1,13 @@ +; +;Based On NAR Test Data +;12/23/93 +E11J 24 70 4 0.025 0.0624 Aerotech +0.0725446 14.3704 +0.16183 17.6296 +0.206473 18.3704 +0.418527 19.2593 +0.731027 18.3704 +1.31696 14.2222 +1.91964 9.03704 +2.51116 2.22222 +2.83 0 diff --git a/datafiles/thrustcurves/AeroTech_E12.eng b/datafiles/thrustcurves/AeroTech_E12.eng new file mode 100644 index 000000000..7ba80db6d --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_E12.eng @@ -0,0 +1,42 @@ +; +; +;Aerotech E12JRC RASP.ENG file made from NAR published data +;File produced July 4, 2000 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +E12JRC 24 70 100 0.0303 0.0594 AT +0.054 16.764 +0.095 18.33 +0.197 16.545 +0.313 16.654 +0.36 17.211 +0.401 16.316 +0.442 17.55 +0.476 16.206 +0.578 16.316 +0.666 16.764 +0.7 15.649 +0.768 16.316 +0.89 16.097 +1.019 15.649 +1.162 14.983 +1.23 14.983 +1.25 13.968 +1.291 14.754 +1.332 13.749 +1.373 14.197 +1.434 13.53 +1.488 13.749 +1.597 12.635 +1.726 11.401 +1.828 10.615 +1.889 9.613 +1.957 9.613 +1.998 8.495 +2.093 8.607 +2.277 7.042 +2.487 5.813 +3.05 0 diff --git a/datafiles/thrustcurves/AeroTech_E15.eng b/datafiles/thrustcurves/AeroTech_E15.eng new file mode 100644 index 000000000..7cd6163c2 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_E15.eng @@ -0,0 +1,46 @@ +; E15W-4,7,P from NAR data +E15W 24 70 4-7-P 0.020100000000000003 0.0502 AT + 0.012 9.918 + 0.018 20.205 + 0.027 25.257 + 0.039 28.152 + 0.055 28.768 + 0.088 27.29 + 0.197 24.517 + 0.297 22.977 + 0.467 20.945 + 0.561 19.959 + 0.679 20.021 + 0.722 19.22 + 0.761 18.789 + 0.807 20.021 + 0.84 18.234 + 0.904 18.727 + 0.995 17.926 + 1.034 18.172 + 1.104 16.756 + 1.147 17.248 + 1.256 16.386 + 1.377 15.77 + 1.411 14.846 + 1.426 16.324 + 1.45 15.031 + 1.547 14.353 + 1.559 16.016 + 1.589 13.86 + 1.62 14.23 + 1.693 13.121 + 1.72 13.429 + 1.829 12.936 + 1.866 11.951 + 1.944 11.951 + 2.005 10.965 + 2.093 10.472 + 2.236 8.316 + 2.26 9.055 + 2.278 7.207 + 2.378 4.99 + 2.442 2.71 + 2.499 1.602 + 2.548 1.047 + 2.618 0.0 diff --git a/datafiles/thrustcurves/AeroTech_E15_1.eng b/datafiles/thrustcurves/AeroTech_E15_1.eng new file mode 100644 index 000000000..2bd38b28d --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_E15_1.eng @@ -0,0 +1,41 @@ +; Aerotech E15 RASP.ENG file made from NAR published data +; File produced July 4, 2000 +; Submitted to ThrustCurve.org by Chris Kobel (3/30/07) +; The total impulse, peak thrust, average thrust and burn time are +; the same as the averaged static test data on the NAR web site in +; the certification file. The curve drawn with these data points is as +; close to the certification curve as can be with such a limited +; number of points (32) allowed with wRASP up to v1.6. +E15W 24 70 4-7 .0201 .0501 AT + 0.020 23.330 + 0.036 27.318 + 0.058 28.840 + 0.079 27.171 + 0.139 25.638 + 0.183 24.263 + 0.237 24.106 + 0.297 22.426 + 0.373 21.964 + 0.400 20.894 + 0.443 21.355 + 0.487 20.442 + 0.617 19.833 + 0.742 18.457 + 0.812 20.000 + 0.850 18.006 + 0.899 18.467 + 1.035 17.711 + 1.100 16.945 + 1.160 16.945 + 1.377 15.736 + 1.426 14.656 + 1.436 16.198 + 1.463 14.813 + 1.550 14.361 + 1.572 15.432 + 1.610 13.752 + 1.827 12.839 + 2.126 10.098 + 2.337 6.116 + 2.538 1.369 + 2.600 0.000 diff --git a/datafiles/thrustcurves/AeroTech_E16.eng b/datafiles/thrustcurves/AeroTech_E16.eng new file mode 100644 index 000000000..95afca8fa --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_E16.eng @@ -0,0 +1,32 @@ +; Aerotech E16 RASP.ENG file made from NAR published data +; File produced July 4, 2000 +; Submitted to ThrustCurve.org by Chris Kobel (3/30/07) +; The total impulse, peak thrust, average thrust and burn time are +; the same as the averaged static test data on the NAR web site in +; the certification file. The curve drawn with these data points is as +; close to the certification curve as can be with such a limited +; number of points (32) allowed with wRASP up to v1.6. +E16W 29 124 4-7-10 .0190 .107 AT + 0.132 32.223 + 0.221 37.200 + 0.255 36.699 + 0.306 36.699 + 0.371 35.357 + 0.414 33.785 + 0.437 34.906 + 0.472 33.785 + 0.530 32.894 + 0.553 31.772 + 0.576 32.443 + 0.638 29.309 + 0.720 27.296 + 0.867 23.942 + 1.083 19.245 + 1.273 14.319 + 1.458 9.397 + 1.513 8.055 + 1.524 8.279 + 1.555 6.936 + 1.656 4.474 + 1.814 1.790 + 2.000 0.000 diff --git a/datafiles/thrustcurves/AeroTech_E18.eng b/datafiles/thrustcurves/AeroTech_E18.eng new file mode 100644 index 000000000..cb73dfb17 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_E18.eng @@ -0,0 +1,37 @@ +; Aerotech E18 RASP.ENG file made from NAR published data +; File produced July 4, 2000 +; Submitted to ThrustCurve.org by Chris Kobel (3/29/07) +; The total impulse, peak thrust, average thrust and burn time are +; the same as the averaged static test data on the NAR web site in +; the certification file. The curve drawn with these data points is as +; close to the certification curve as can be with such a limited +; number of points (32) allowed with wRASP up to v1.6. +E18W 24 70 4-8-10 .0207 .057 AT + 0.016 6.586 + 0.042 18.004 + 0.073 27.138 + 0.098 29.815 + 0.134 30.357 + 0.170 30.347 + 0.195 31.080 + 0.236 30.347 + 0.287 30.878 + 0.338 30.337 + 0.368 30.878 + 0.404 29.795 + 0.424 30.688 + 0.465 29.976 + 0.526 29.785 + 0.592 29.063 + 0.669 28.341 + 0.786 26.908 + 0.908 23.850 + 1.025 21.163 + 1.157 17.905 + 1.284 14.857 + 1.462 11.338 + 1.660 7.106 + 1.838 3.470 + 2.006 1.309 + 2.083 0.588 + 2.140 0.000 diff --git a/datafiles/thrustcurves/AeroTech_E23.eng b/datafiles/thrustcurves/AeroTech_E23.eng new file mode 100644 index 000000000..5417dd740 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_E23.eng @@ -0,0 +1,30 @@ +; Aerotech E23 RASP.ENG file made from NAR published data +; File produced July 4, 2000 +; Submitted to ThrustCurve.org by Chris Kobel (3/30/07) +; The total impulse, peak thrust, average thrust and burn time are +; the same as the averaged static test data on the NAR web site in +; the certification file. The curve drawn with these data points is as +; close to the certification curve as can be with such a limited +; number of points (32) allowed with wRASP up to v1.6. +E23T 29 124 5-8 .0174 .1039 AT + 0.024 16.299 + 0.035 21.959 + 0.067 30.785 + 0.090 35.774 + 0.153 37.577 + 0.200 38.220 + 0.240 37.357 + 0.322 37.577 + 0.393 35.093 + 0.534 32.378 + 0.727 27.168 + 0.766 26.938 + 0.798 25.125 + 0.908 21.729 + 1.057 16.980 + 1.187 12.682 + 1.336 7.471 + 1.450 3.169 + 1.497 1.584 + 1.532 0.679 + 1.570 0.000 diff --git a/datafiles/thrustcurves/AeroTech_E28.eng b/datafiles/thrustcurves/AeroTech_E28.eng new file mode 100644 index 000000000..8ded5e2f9 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_E28.eng @@ -0,0 +1,36 @@ +; Aerotech E28 RASP.ENG file made from NAR published data +; File produced July 4, 2000 +; Submitted to ThrustCurve.org by Chris Kobel (3/29/07) +; The total impulse, peak thrust, average thrust and burn time are +; the same as the averaged static test data on the NAR web site in +; the certification file. The curve drawn with these data points is as +; close to the certification curve as can be with such a limited +; number of points (32) allowed with wRASP up to v1.6. +E28T 24 70 2-5-8 .0184 .0545 A + 0.010 29.8620 + 0.018 45.1390 + 0.038 47.5620 + 0.081 50.5200 + 0.106 48.9530 + 0.146 48.2630 + 0.161 48.9530 + 0.197 48.9530 + 0.242 47.5620 + 0.313 46.1800 + 0.411 43.0570 + 0.494 40.6240 + 0.527 39.5830 + 0.542 40.2740 + 0.562 38.5420 + 0.633 36.4600 + 0.683 34.3770 + 0.743 31.2440 + 0.799 29.1620 + 0.877 26.0380 + 0.970 20.8320 + 1.006 17.3590 + 1.046 11.4620 + 1.089 6.9430 + 1.132 3.8190 + 1.172 1.7350 + 1.220 0.0000 diff --git a/datafiles/thrustcurves/AeroTech_E30.eng b/datafiles/thrustcurves/AeroTech_E30.eng new file mode 100644 index 000000000..e0a8ce892 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_E30.eng @@ -0,0 +1,34 @@ +; Aerotech E30 RASP.ENG file made from NAR published data +; File produced July 4, 2000 +; Submitted to ThrustCurve.org by Chris Kobel (3/30/07) +; The total impulse, peak thrust, average thrust and burn time are +; the same as the averaged static test data on the NAR web site in +; the certification file. The curve drawn with these data points is as +; close to the certification curve as can be with such a limited +; number of points (32) allowed with wRASP up to v1.6. +E30T 24 70 4-7 .0193 .0433 AT + 0.013 38.8470 + 0.020 45.6210 + 0.041 48.2700 + 0.059 46.5020 + 0.110 46.5020 + 0.166 45.9120 + 0.184 46.7920 + 0.217 45.9120 + 0.265 45.9120 + 0.319 45.0310 + 0.383 44.1500 + 0.482 42.0890 + 0.594 38.8470 + 0.615 39.4370 + 0.628 37.3760 + 0.684 35.3140 + 0.742 33.2630 + 0.804 30.0210 + 0.880 25.6070 + 0.962 20.0140 + 1.038 12.9490 + 1.089 7.3580 + 1.151 3.2370 + 1.186 1.1760 + 1.200 0.0000 diff --git a/datafiles/thrustcurves/AeroTech_E6.eng b/datafiles/thrustcurves/AeroTech_E6.eng new file mode 100644 index 000000000..9a8ef48c9 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_E6.eng @@ -0,0 +1,20 @@ +; Aerotech E6T single use from NAR cert data +E6T 24 70 2-4-8-P 0.021500000000000002 0.0463 AT + 0.011 18.085 + 0.109 19.681 + 0.217 16.312 + 0.315 13.475 + 0.457 11.348 + 0.63 9.043 + 0.804 7.801 + 0.989 6.738 + 1.272 6.028 + 2.0 5.851 + 3.0 5.496 + 4.0 5.496 + 4.446 4.965 + 5.011 4.965 + 5.533 4.787 + 5.609 6.56 + 5.707 4.255 + 6.033 0.0 diff --git a/datafiles/thrustcurves/AeroTech_E7.eng b/datafiles/thrustcurves/AeroTech_E7.eng new file mode 100644 index 000000000..a8f009752 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_E7.eng @@ -0,0 +1,34 @@ +; +;Aerotech E7TRC RASP.ENG file made from NAR published data +;File produced July 4, 2000 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +E7RC 24 70 100 0.0171 0.0484 AT +0.038 6.636 +0.063 10.056 +0.087 11.019 +0.134 11.42 +0.206 11.58 +0.312 11.149 +0.466 10.738 +0.667 9.777 +0.94 8.132 +1.223 6.281 +1.484 5.182 +1.709 4.701 +2.112 4.423 +2.776 4.279 +3.31 4.205 +3.926 4.266 +4.401 4.192 +4.638 4.258 +4.744 4.119 +5.124 3.979 +5.219 3.977 +5.266 3.156 +5.313 1.992 +5.36 0.965 +5.43 0 diff --git a/datafiles/thrustcurves/AeroTech_F10.eng b/datafiles/thrustcurves/AeroTech_F10.eng new file mode 100644 index 000000000..879934c07 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_F10.eng @@ -0,0 +1,30 @@ +; +F10 29.0 92.00 4-6-8 0.04000 0.08300 Aerotech + 0.01 16.81 + 0.03 22.34 + 0.11 22.23 + 0.26 21.49 + 0.37 20.00 + 0.47 20.21 + 0.67 18.09 + 0.99 15.74 + 1.31 13.40 + 1.81 10.85 + 2.49 10.21 + 3.13 8.94 + 3.60 8.83 + 4.11 8.62 + 4.95 8.62 + 5.45 8.62 + 5.58 8.51 + 5.88 8.72 + 6.22 8.51 + 6.46 8.51 + 6.60 7.77 + 6.71 7.02 + 6.79 5.64 + 6.91 3.83 + 6.95 2.23 + 7.00 0.96 + 7.05 0.00 +; diff --git a/datafiles/thrustcurves/AeroTech_F12.eng b/datafiles/thrustcurves/AeroTech_F12.eng new file mode 100644 index 000000000..cd06f44b4 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_F12.eng @@ -0,0 +1,40 @@ +; Aerotech F12 RASP.ENG file made from NAR published data +; File produced July 4, 2000 +; Submitted to ThrustCurve.org by Chris Kobel (4/6/07) +; The total impulse, peak thrust, average thrust and burn time are +; the same as the averaged static test data on the NAR web site in +; the certification file. The curve drawn with these data points is as +; close to the certification curve as can be with such a limited +; number of points (32) allowed with wRASP up to v1.6. +F12J 24 70 2-5 .0303 .0667 AT + 0.037 20.894 + 0.054 22.152 + 0.101 22.152 + 0.148 22.571 + 0.165 23.409 + 0.200 22.421 + 0.281 22.142 + 0.369 22.132 + 0.474 22.271 + 0.526 23.540 + 0.549 21.982 + 0.637 22.122 + 0.724 21.842 + 0.800 21.413 + 0.823 22.251 + 0.846 20.714 + 0.881 21.553 + 0.945 21.123 + 1.021 20.704 + 1.114 20.554 + 1.213 19.296 + 1.382 18.298 + 1.481 18.019 + 1.737 15.343 + 1.790 17.300 + 1.883 13.936 + 2.051 11.260 + 2.220 7.468 + 2.447 3.671 + 2.709 1.135 + 2.930 0.000 diff --git a/datafiles/thrustcurves/AeroTech_F13.eng b/datafiles/thrustcurves/AeroTech_F13.eng new file mode 100644 index 000000000..7de998881 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_F13.eng @@ -0,0 +1,38 @@ +; +;Aerotech F13RCJ RASP.ENG file made from NAR published data +;File produced July 4, 2000 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +F13RCJ 32 107 100 0.0323 0.1105 AT +0.048 15.309 +0.084 18.629 +0.143 19.98 +0.311 18.968 +0.538 18.172 +0.729 17.138 +0.992 15.428 +1.279 13.828 +1.673 12.456 +1.984 11.879 +2.044 12.227 +2.139 11.313 +2.378 11.193 +2.51 11.084 +2.558 12.108 +2.641 10.855 +2.976 10.736 +3.49 10.627 +3.873 10.507 +3.992 10.965 +4.028 10.627 +4.41 10.507 +4.625 10.736 +4.769 9.941 +4.829 8.684 +4.865 6.742 +4.96 3.199 +5.02 1.485 +5.1 0 diff --git a/datafiles/thrustcurves/AeroTech_F16.eng b/datafiles/thrustcurves/AeroTech_F16.eng new file mode 100644 index 000000000..898c01790 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_F16.eng @@ -0,0 +1,41 @@ +; +;Aerotech F16RCJ RASP.ENG file made from NAR published data +;File produced July 4, 2000 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +F16RCJ 32 107 100 0.0625 0.1404 AT +0.046 26.35 +0.116 22.388 +0.139 21.374 +0.185 21.886 +0.22 20.54 +0.301 19.696 +0.498 18.35 +0.579 19.194 +0.637 16.492 +0.718 18.35 +0.834 18.35 +0.95 18.35 +1.054 19.194 +1.147 17.848 +1.181 18.853 +1.263 17.336 +1.436 18.009 +1.633 17.165 +1.784 17.336 +1.865 18.682 +1.934 16.834 +1.981 17.336 +2.178 16.332 +2.375 16.332 +2.502 18.18 +2.664 15.659 +2.896 15.488 +3.29 13.8 +3.718 11.611 +4.181 9.426 +4.888 5.891 +5.69 0 diff --git a/datafiles/thrustcurves/AeroTech_F20.eng b/datafiles/thrustcurves/AeroTech_F20.eng new file mode 100644 index 000000000..18edb2748 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_F20.eng @@ -0,0 +1,35 @@ +; +; +F20EJ 29 83 4-7 0.03 0.0746 AeroTech +0.01 52.08 +0.03 49.81 +0.06 46.98 +0.1 45.56 +0.15 44.49 +0.18 45.55 +0.21 43.42 +0.24 43.78 +0.32 43.77 +0.36 44.11 +0.44 43.04 +0.45 40.58 +0.53 39.86 +0.62 38.08 +0.76 36.3 +0.8 37.35 +0.84 34.88 +0.89 36.99 +0.9 33.46 +1.03 30.61 +1.06 32.02 +1.09 29.55 +1.23 26 +1.32 22.45 +1.35 23.16 +1.36 21.39 +1.58 16.42 +1.8 11.1 +2.01 6.48 +2.19 3.63 +2.39 1.13 +2.68 0 diff --git a/datafiles/thrustcurves/AeroTech_F21.eng b/datafiles/thrustcurves/AeroTech_F21.eng new file mode 100644 index 000000000..c4cdedcf2 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_F21.eng @@ -0,0 +1,60 @@ +; Aerotech F21W (White Lightning) RASP.ENG file. +; File produced Jun 22 2004. +; The file was produced by scaling data points off the +; thrust curve in the Tripoli.org motor pdf file. +; +; The F21W cannot be found on thrustcurve.org. +; Hence the amateur file production. +; The file was created by Stan Hemphill +; Contact at stanley_hemphill@hotmail.com +; +; Motor ## Dia Len Delays Prop Motor Company +F21WL_CO_SU 24 96 6-8 0.0300 0.064 AeroTech +0.0045 037.2266 +0.0090 042.1474 +0.0180 042.5040 +0.0270 040.5071 +0.0337 038.8669 +0.0427 038.2250 +0.0517 037.7258 +0.0607 036.8700 +0.0720 036.4422 +0.0877 036.4422 +0.1102 035.3724 +0.1350 035.8716 +0.1552 035.4437 +0.1732 036.2282 +0.2025 035.6577 +0.2452 037.2979 +0.2835 036.5848 +0.3195 038.0111 +0.3375 037.4406 +0.3757 038.5816 +0.3960 038.2250 +0.4297 039.3661 +0.4454 038.0111 +0.4747 038.7242 +0.4882 037.7971 +0.5084 038.1537 +0.5354 038.5103 +0.5647 037.9398 +0.5849 037.2266 +0.6007 037.8685 +0.6389 036.8700 +0.6704 037.1553 +0.7649 035.9429 +0.9201 032.9477 +1.0056 030.3090 +1.0709 029.7385 +1.2643 024.0333 +1.2868 024.0333 +1.3723 020.8241 +1.3926 020.8241 +1.5883 015.4754 +1.6108 015.4754 +2.0112 005.0634 +2.1192 003.4231 +2.2407 002.4247 +2.3780 001.4976 +2.4927 000.9271 +2.5152 000.0000 diff --git a/datafiles/thrustcurves/AeroTech_F22.eng b/datafiles/thrustcurves/AeroTech_F22.eng new file mode 100644 index 000000000..8cd24a5bb --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_F22.eng @@ -0,0 +1,38 @@ +; +;Aerotech F22 RASP.ENG file made from NAR published data +;File produced July 4, 2000 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +F22 29 125 4-7 0.0463 0.1342 AT +0.014 11.527 +0.075 20.126 +0.157 26.572 +0.293 29.113 +0.382 30.278 +0.45 29.69 +0.539 30.667 +0.614 30.089 +0.662 31.15 +0.771 30.478 +0.948 29.89 +0.996 28.714 +1.078 28.136 +1.187 27.738 +1.289 26.761 +1.337 26.96 +1.412 25.984 +1.474 25.008 +1.515 26.173 +1.542 24.808 +1.706 22.856 +1.938 20.903 +2.101 18.173 +2.129 19.338 +2.251 16.21 +2.402 13.48 +2.64 8.791 +2.961 3.32 +3.31 0 diff --git a/datafiles/thrustcurves/AeroTech_F23.eng b/datafiles/thrustcurves/AeroTech_F23.eng new file mode 100644 index 000000000..e6c5c2bc7 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_F23.eng @@ -0,0 +1,36 @@ +; +;F23FJ Motor Thrust Curve created by Tim Van Milligan +;for RockSim Users - www.rocksim.com +;file produced March 2, 2005 +;Based on data supplied by Aerotech for the newer molded case F23 econojet. +F23FJ 29 83 4-7 0.033 0.0839 AeroTech +0.03 48.7 +0.05 43.11 +0.08 41.41 +0.1 42.26 +0.13 40.84 +0.17 39.42 +0.23 38.85 +0.27 38.85 +0.3 37.44 +0.31 38.57 +0.36 37.72 +0.43 36.59 +0.5 36.02 +0.56 36.02 +0.59 34.6 +0.69 33.18 +0.77 32.61 +0.85 31.2 +0.94 29.5 +1.04 27.79 +1.18 24.39 +1.2 25.24 +1.25 22.97 +1.37 20.98 +1.53 16.73 +1.69 12.48 +1.83 9.07 +1.95 5.11 +2.07 2.27 +2.22 0 diff --git a/datafiles/thrustcurves/AeroTech_F23_1.eng b/datafiles/thrustcurves/AeroTech_F23_1.eng new file mode 100644 index 000000000..ae21a8cff --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_F23_1.eng @@ -0,0 +1,41 @@ +; +;Aerotech F23RCWSK RASP.ENG file made from NAR published data +;File produced July 4, 2000 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +F23-RC-SK 32 107 100 0.0378 0.1287 AT +0.042 22.644 +0.133 28.191 +0.161 27.261 +0.189 29.57 +0.252 31.419 +0.343 32.578 +0.399 32.348 +0.441 33.737 +0.476 30.729 +0.539 33.507 +0.609 34.197 +0.777 34.886 +0.826 34.656 +0.896 36 +0.938 34.656 +1.015 34.656 +1.071 34.197 +1.12 33.038 +1.218 32.578 +1.267 29.81 +1.351 29.34 +1.393 27.731 +1.54 26.802 +1.645 24.263 +1.799 21.255 +1.862 19.866 +2.051 15.479 +2.317 11.552 +2.618 6.7 +2.884 3.234 +3.185 1.386 +3.47 0 diff --git a/datafiles/thrustcurves/AeroTech_F24.eng b/datafiles/thrustcurves/AeroTech_F24.eng new file mode 100644 index 000000000..81698fc9b --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_F24.eng @@ -0,0 +1,34 @@ +; Aerotech F24 RASP.ENG file made from NAR published data +; File produced July 4, 2000 +; Submitted to ThrustCurve.org by Chris Kobel (4/6/07) +; The total impulse, peak thrust, average thrust and burn time are +; the same as the averaged static test data on the NAR web site in +; the certification file. The curve drawn with these data points is as +; close to the certification curve as can be with such a limited +; number of points (32) allowed with wRASP up to v1.6. +F24W 24 70 4-7-10 .0253 .062 AT + 0.033 16.442 + 0.112 40.646 + 0.125 41.450 + 0.180 40.927 + 0.245 40.626 + 0.281 41.017 + 0.355 40.024 + 0.438 39.713 + 0.543 38.227 + 0.603 37.032 + 0.658 33.779 + 0.685 34.663 + 0.726 29.934 + 0.772 30.216 + 0.951 26.953 + 1.071 25.166 + 1.107 23.088 + 1.185 21.311 + 1.383 17.144 + 1.649 10.910 + 1.828 5.869 + 1.938 2.903 + 1.988 2.306 + 2.048 1.412 + 2.130 0.000 diff --git a/datafiles/thrustcurves/AeroTech_F25.eng b/datafiles/thrustcurves/AeroTech_F25.eng new file mode 100644 index 000000000..b6e3f77a2 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_F25.eng @@ -0,0 +1,15 @@ +; +;F25 Motor Thrust Curve created by Tim Van Milligan +;for RockSim Users - www.rocksim.com +;file produced March 2, 2005 +;Based on data supplied by Aerotech for the newer molded case F25. +F25 29 98 4-6-9 0.0388 0.0972 Aerotech +0.039 57.631 +0.187 53.491 +0.342 51.239 +0.5 47.86 +1 33.806 +1.5 22.94 +2 10.135 +2.207 4.504 +2.69 0 diff --git a/datafiles/thrustcurves/AeroTech_F26.eng b/datafiles/thrustcurves/AeroTech_F26.eng new file mode 100644 index 000000000..53613c0f0 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_F26.eng @@ -0,0 +1,20 @@ +; +;F26FJ Motor Thrust Curve created by Tim Van Milligan +;for RockSim Users - www.rocksim.com +;File created March 2, 2005 +;Based on data supplied by Aerotech prior to NAR certification. +F26FJ 29 98 6-9 0.0431 0.1007 Aerotech +0.041 38.289 +0.114 36.318 +0.293 34.347 +0.497 32.939 +0.774 32.376 +1 31.25 +1.254 28.716 +1.498 25.338 +1.743 22.241 +2.003 17.737 +2.077 15.484 +2.304 5.349 +2.484 1.689 +2.61 0 diff --git a/datafiles/thrustcurves/AeroTech_F27.eng b/datafiles/thrustcurves/AeroTech_F27.eng new file mode 100644 index 000000000..88339abc8 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_F27.eng @@ -0,0 +1,36 @@ +; Exported using ThrustCurveTool, www.ThrustGear.com +; @File: 060603WF27Composite.txt, @Pts-I: 1001, @Pts-O: 32, @Sm: 5, @CO: 5% +; @TI: 49.5446, @TIa: 49.299, @TIe: 0.0%, @ThMax: 36.2491, @ThAvg: 24.1799, @Tb: 2.049 +F27 29 83 4,8 0.0284 0.08 Aerotech + 0.0 4.84718 + 0.0125 15.5374 + 0.0175 18.81827 + 0.025 22.5311 + 0.0325 25.2547 + 0.04 27.3204 + 0.0575 30.3657 + 0.0725 31.4597 + 0.0975 32.2507 + 0.265 35.0238 + 0.295 35.9203 + 0.4675 35.9684 + 0.59 35.065 + 0.8 32.0145 + 0.825 31.2773 + 0.8575 31.1102 + 0.9025 29.9308 + 0.955 29.7244 + 1.045 27.2951 + 1.085 27.2663 + 1.1175 25.9881 + 1.1475 26.0014 + 1.235 23.2853 + 1.28 22.9001 + 1.3425 21.0771 + 1.52 17.256 + 1.8075 6.85478 + 1.9175 4.16387 + 2.05 1.783863 + 2.1775 0.488016 + 2.4225 0.44385 + 2.425 0.0 diff --git a/datafiles/thrustcurves/AeroTech_F32.eng b/datafiles/thrustcurves/AeroTech_F32.eng new file mode 100644 index 000000000..d4b5b650b --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_F32.eng @@ -0,0 +1,40 @@ +; +;Aerotech F32 RASP.ENG file made from NAR published data +;File produced July 4, 2000 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +F32 24 124 5-10-15 0.0377 0.0814 AeroTech +0.025 46.699 +0.031 51.846 +0.061 55.64 +0.085 52.868 +0.126 47.37 +0.245 45.637 +0.34 44.946 +0.394 42.873 +0.447 42.873 +0.572 41.14 +0.72 39.408 +0.744 40.78 +0.786 38.026 +1.041 35.592 +1.136 33.179 +1.177 34.541 +1.225 32.818 +1.379 31.436 +1.474 30.394 +1.635 28.311 +1.676 27.28 +1.694 29.683 +1.712 26.929 +1.854 25.537 +1.943 23.815 +2.092 21.051 +2.187 18.287 +2.276 13.82 +2.382 7.281 +2.525 2.457 +2.72 0 diff --git a/datafiles/thrustcurves/AeroTech_F35.eng b/datafiles/thrustcurves/AeroTech_F35.eng new file mode 100644 index 000000000..6f00123dd --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_F35.eng @@ -0,0 +1,34 @@ +; Curve fit of AT Instruction Sheet by C. Kobel 7/29/08 +F35W 24 95 5-8-11 0.03 0.085 AT + 0.007 39.452 + 0.012 51.842 + 0.019 49.885 + 0.034 57.873 + 0.048 58.363 + 0.058 57.221 + 0.077 54.45 + 0.098 54.939 + 0.106 53.961 + 0.201 53.472 + 0.299 53.309 + 0.398 52.005 + 0.498 52.005 + 0.549 49.559 + 0.601 48.907 + 0.653 47.277 + 0.702 45.647 + 0.752 44.669 + 0.802 43.201 + 0.898 39.778 + 0.946 39.615 + 0.984 36.843 + 1.003 37.332 + 1.102 33.583 + 1.144 30.159 + 1.200 22.334 + 1.298 10.923 + 1.346 6.521 + 1.398 3.260 + 1.448 1.793 + 1.497 0.978 + 1.600 0.0 diff --git a/datafiles/thrustcurves/AeroTech_F37.eng b/datafiles/thrustcurves/AeroTech_F37.eng new file mode 100644 index 000000000..89c087f05 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_F37.eng @@ -0,0 +1,31 @@ +; +;Aerotech F37 RASP.ENG file made from NAR published data +;File produced July 4, 2000 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +F37 29 99 6-10-14 0.0282 0.1086 AT +0.018 7.251 +0.053 13.626 +0.088 22.331 +0.106 25.227 +0.141 26.385 +0.183 28.411 +0.26 37.685 +0.31 41.449 +0.422 44.035 +0.524 45.183 +0.59 46.47 +0.682 45.153 +0.864 43.386 +0.934 40.471 +1.042 35.23 +1.151 29.699 +1.246 25.037 +1.354 19.796 +1.445 13.397 +1.498 7.586 +1.54 3.226 +1.6 0 diff --git a/datafiles/thrustcurves/AeroTech_F39.eng b/datafiles/thrustcurves/AeroTech_F39.eng new file mode 100644 index 000000000..8ea0d81d1 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_F39.eng @@ -0,0 +1,40 @@ +; Aerotech F39 RASP.ENG file made from NAR published data +; File produced July 4, 2000 +; Submitted to ThrustCurve.org by Chris Kobel (4/6/07) +; The total impulse, peak thrust, average thrust and burn time are +; the same as the averaged static test data on the NAR web site in +; the certification file. The curve drawn with these data points is as +; close to the certification curve as can be with such a limited +; number of points (32) allowed with wRASP up to v1.6. +F39T 24 70 3-6-9 .0227 .059 AT + 0.010 45.057 + 0.016 54.131 + 0.046 58.321 + 0.079 59.470 + 0.103 58.311 + 0.130 57.253 + 0.172 55.491 + 0.235 53.738 + 0.321 51.271 + 0.363 50.566 + 0.387 49.509 + 0.408 50.203 + 0.426 48.804 + 0.453 47.746 + 0.480 47.041 + 0.680 41.059 + 0.716 39.649 + 0.752 38.944 + 0.809 36.487 + 0.860 34.382 + 0.893 33.324 + 0.917 32.619 + 1.000 28.752 + 1.075 25.247 + 1.105 22.095 + 1.126 17.201 + 1.144 13.001 + 1.174 8.109 + 1.219 4.606 + 1.261 2.500 + 1.330 0.000 diff --git a/datafiles/thrustcurves/AeroTech_F40.eng b/datafiles/thrustcurves/AeroTech_F40.eng new file mode 100644 index 000000000..ec729dea4 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_F40.eng @@ -0,0 +1,31 @@ +; +;Aerotech F40 RASP.ENG file made from NAR published data +;File produced July 4, 2000 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +F40 29 124 4-7-10 0.04 0.126 AT +0.015 17.776 +0.049 41.016 +0.089 58.793 +0.124 62.9 +0.148 65.173 +0.183 62.442 +0.242 68.07 +0.292 60.617 +0.321 61.524 +0.415 60.617 +0.524 58.334 +0.741 52.412 +0.87 48.314 +0.889 49.221 +0.914 47.397 +1.102 40.109 +1.285 33.728 +1.492 25.064 +1.665 15.952 +1.808 8.659 +1.942 3.19 +2.06 0 diff --git a/datafiles/thrustcurves/AeroTech_F42.eng b/datafiles/thrustcurves/AeroTech_F42.eng new file mode 100644 index 000000000..092f2ee28 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_F42.eng @@ -0,0 +1,17 @@ +; +;F42T Motor Thrust Curve created by Tim Van Milligan +;for RockSim Users - www.rocksim.com +;Based on data supplied by Aerotech prior to NAR certification. +F42T 29 83 4-8 0.027 0.076 Aerotech +0.01 68.694 +0.029 65.879 +0.202 62.5 +0.511 51.802 +0.739 43.356 +0.993 31.532 +1.02 29.279 +1.072 23.086 +1.199 9.572 +1.262 4.505 +1.319 2.815 +1.47 0 diff --git a/datafiles/thrustcurves/AeroTech_F50.eng b/datafiles/thrustcurves/AeroTech_F50.eng new file mode 100644 index 000000000..9b33bfbc3 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_F50.eng @@ -0,0 +1,18 @@ +; +;Aerotech F50 RASP.ENG file made by Tim Van Milligan +;For RockSim www.RockSim.com +;File Created March 2, 2005 +;Thrust curve supplied by Aerotech for the molded case F50T motors. +F50T 29 98 4-6-9 0.0336 0.0898 AeroTech +0.013 73.762 +0.0326 70.383 +0.267 69.82 +0.518 67.005 +0.792 56.87 +0.906 50.676 +1 44.482 +1.036 39.978 +1.107 23.649 +1.199 6.194 +1.316 1.126 +1.43 0 diff --git a/datafiles/thrustcurves/AeroTech_F52.eng b/datafiles/thrustcurves/AeroTech_F52.eng new file mode 100644 index 000000000..e808de9f8 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_F52.eng @@ -0,0 +1,39 @@ +; +;Aerotech F52 RASP.ENG file made from NAR published data +;File produced July 4, 2000 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +F52 29 124 5-8-11 0.0366 0.1214 AT +0.012 46.899 +0.033 61.778 +0.056 69.441 +0.097 73.483 +0.115 76.636 +0.13 74.381 +0.153 74.82 +0.168 78.422 +0.182 78.95 +0.206 77.963 +0.238 77.504 +0.258 73.892 +0.314 72.974 +0.39 72.046 +0.428 70.679 +0.501 65.699 +0.565 62.975 +0.688 58.874 +0.749 56.15 +0.837 52.517 +0.901 49.793 +0.971 46.161 +1.088 39.365 +1.144 34.386 +1.173 29.417 +1.222 20.376 +1.275 13.151 +1.339 5.461 +1.389 1.838 +1.42 0 diff --git a/datafiles/thrustcurves/AeroTech_F62.eng b/datafiles/thrustcurves/AeroTech_F62.eng new file mode 100644 index 000000000..f00317cf6 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_F62.eng @@ -0,0 +1,33 @@ +; Aerotech F62T (Blue Thunder) +; +; AeroTech RMS-29/60 Easy Access Reloadable Motor Hardware. +; +; RASP.ENG file made from manufacturers catalog data. +; +; File produced May, 17 2004. +; +; The file was produced by scaling 16 data points off +; the thrust curves in the manufacturers catalog. +; +; The F62T cannot be found on thrustcurve.org. +; Hence the amateur file production. +; The file was created by Stan Hemphill. +; Contact at stanley_hemphill@hotmail.com. +; +; Motor Dia Len Delay Prop Gross Mfg +F62T 29 99 6-8-9-10-11-13-14-16-18 0.025 0.109 AT +0.0046 053.6364 +0.0416 055.2727 +0.0909 058.3636 +0.1356 061.6364 +0.1649 064.9091 +0.1864 067.6364 +0.5085 067.6364 +0.5701 064.7273 +0.6687 060.0000 +0.7427 055.0909 +0.7982 049.6364 +0.9029 048.7273 +0.9492 024.7273 +0.9661 020.1818 +0.9985 000.0000 diff --git a/datafiles/thrustcurves/AeroTech_F72.eng b/datafiles/thrustcurves/AeroTech_F72.eng new file mode 100644 index 000000000..25d9203bb --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_F72.eng @@ -0,0 +1,41 @@ +; +;Aerotech F72 RASP.ENG file made from NAR published data +;File produced July 4, 2000 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +F72 24 124 5-10-15 0.0368 0.0742 AeroTech +0.012 62.586 +0.017 84.986 +0.02 98.78 +0.03 94.748 +0.05 90.152 +0.069 82.688 +0.089 85.556 +0.104 80.39 +0.136 83.255 +0.146 80.96 +0.176 82.688 +0.198 78.672 +0.213 80.96 +0.253 80.39 +0.315 80.96 +0.38 79.821 +0.429 79.241 +0.489 78.092 +0.523 78.672 +0.536 75.225 +0.675 73.496 +0.699 67.182 +0.719 68.331 +0.747 64.884 +0.769 66.033 +0.858 60.867 +0.923 52.824 +0.98 40.195 +1.012 29.864 +1.034 20.092 +1.089 11.48 +1.21 0 diff --git a/datafiles/thrustcurves/AeroTech_G101.eng b/datafiles/thrustcurves/AeroTech_G101.eng new file mode 100644 index 000000000..9b306f15b --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_G101.eng @@ -0,0 +1,33 @@ +; +G101T 29.0 114.30 5-8-12 0.04600 0.13600 AT + 0.01 35.29 + 0.02 63.97 + 0.02 78.09 + 0.03 84.71 + 0.04 89.96 + 0.06 93.96 + 0.12 97.26 + 0.17 99.14 + 0.21 101.73 + 0.27 104.09 + 0.31 104.56 + 0.36 103.62 + 0.40 103.38 + 0.45 101.03 + 0.51 99.27 + 0.53 94.41 + 0.56 93.09 + 0.64 87.35 + 0.76 78.53 + 0.80 75.88 + 0.88 68.82 + 0.89 65.73 + 0.90 61.32 + 0.91 55.15 + 0.93 35.73 + 0.94 32.65 + 0.95 22.50 + 0.98 10.59 + 1.00 5.29 + 1.05 0.00 +; diff --git a/datafiles/thrustcurves/AeroTech_G104.eng b/datafiles/thrustcurves/AeroTech_G104.eng new file mode 100644 index 000000000..8045a0565 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_G104.eng @@ -0,0 +1,45 @@ +; +; Aerotech G104T (Blue Thunder) +; +; AeroTech RMS-29/100 EZ Access Reloadable Motors. +; +; File produced 28 Feb 2005. +; +; The file was produced by scaling data points off the +; thrust curve in the manufacturers catalog sheet. +; +; The motor is not yet on www.thrustcurve.org. +; Hence the amateur file production. +; The file was created by Stan Hemphill. +; Contact at stanley_hemphill@hotmail.com. +; +; Motor Dia Len Delay Prop Gross Mfg +G104T 29 124 6-8-9-10-11-13-14-16-18 0.0408 0.136 AT +0.0067 125.3426 +0.0471 123.5424 +0.0856 121.9671 +0.1019 121.4046 +0.1462 121.1795 +0.1837 120.8420 +0.2029 120.5044 +0.2385 118.8167 +0.2644 117.2415 +0.2798 116.9039 +0.3279 116.6789 +0.3923 116.6789 +0.4298 116.1163 +0.4615 114.0910 +0.5067 110.1530 +0.5404 104.6397 +0.5760 096.2010 +0.6067 089.5626 +0.6817 078.8736 +0.7692 067.9595 +0.7865 064.9216 +0.7990 062.5588 +0.8058 058.0582 +0.8192 050.5196 +0.8385 039.0430 +0.8625 027.5664 +0.8769 016.6523 +0.9019 000.0000 diff --git a/datafiles/thrustcurves/AeroTech_G12.eng b/datafiles/thrustcurves/AeroTech_G12.eng new file mode 100644 index 000000000..aac501cd4 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_G12.eng @@ -0,0 +1,33 @@ +; +;Aerotech G12RC RASP.ENG file made from NAR published data +;File produced July 4, 2000 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +G12RC 32 107 100 0.0511 0.131 AT +0.03 18.549 +0.117 19.96 +0.239 20.64 +0.362 20.111 +0.519 18.982 +0.694 17.138 +0.886 15.02 +1.131 13.186 +1.375 11.915 +1.689 11.069 +2.021 10.363 +2.422 10.232 +3.172 9.677 +4.114 9.267 +5.039 8.857 +6.137 8.733 +7.132 8.607 +7.795 8.335 +7.952 8.196 +8.074 8.055 +8.179 6.924 +8.319 4.661 +8.476 1.973 +8.55 0 diff --git a/datafiles/thrustcurves/AeroTech_G25.eng b/datafiles/thrustcurves/AeroTech_G25.eng new file mode 100644 index 000000000..02d639e1b --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_G25.eng @@ -0,0 +1,41 @@ +; +;Aerotech G25 RASP.ENG file made from NAR published data +;File produced July 4, 2000 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +G25 29 124 5-10-15 0.0625 0.1197 AeroTech +0.035 30.499 +0.047 36.712 +0.059 41.18 +0.13 40.669 +0.177 38.969 +0.295 38.969 +0.343 40.947 +0.413 40.38 +0.437 38.69 +0.484 39.824 +0.532 37.845 +0.65 37.557 +0.721 38.969 +0.803 38.69 +0.85 37.279 +0.98 39.535 +1.063 36.434 +1.098 38.124 +1.252 37.845 +1.37 37.279 +1.583 37 +1.819 35.3 +1.984 33.61 +2.185 31.344 +2.315 28.809 +2.622 24.286 +3.024 18.917 +3.39 13.838 +3.839 7.624 +4.323 4.518 +4.783 2.541 +5.3 0 diff --git a/datafiles/thrustcurves/AeroTech_G33.eng b/datafiles/thrustcurves/AeroTech_G33.eng new file mode 100644 index 000000000..b0fd50e6a --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_G33.eng @@ -0,0 +1,40 @@ +; +;Aerotech G33 RASP.ENG file made from NAR published data +;File produced July 4, 2000 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +G33 29 124 5-7 0.0722 0.1593 AT +0.027 22.642 +0.061 42.201 +0.117 47.354 +0.243 46.678 +0.34 46.339 +0.438 47.384 +0.48 50.92 +0.508 46.359 +0.543 47.732 +0.662 45.693 +0.851 42.28 +1.039 41.266 +1.116 42.987 +1.193 39.226 +1.221 42.31 +1.312 38.888 +1.326 40.609 +1.479 38.221 +1.675 35.157 +1.843 32.77 +1.878 36.888 +1.899 32.093 +1.997 30.382 +2.13 26.622 +2.263 23.547 +2.444 19.11 +2.591 13.977 +2.752 8.502 +2.892 4.743 +3.053 2.014 +3.27 0 diff --git a/datafiles/thrustcurves/AeroTech_G339.eng b/datafiles/thrustcurves/AeroTech_G339.eng new file mode 100644 index 000000000..5bf815974 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_G339.eng @@ -0,0 +1,17 @@ +; +; 38-120 +; Created from TRA Certification Record issued 23 Nov 2006 +; Bill Wagstaff - 04/30/07 +G339N 38 97 0 0.049 0.190 AT +0.009 371 +0.05 375 +0.10 375 +0.15 364 +0.20 349 +0.25 310 +0.30 264 +0.324 257 +0.342 39 +0.359 0 +; +; diff --git a/datafiles/thrustcurves/AeroTech_G35.eng b/datafiles/thrustcurves/AeroTech_G35.eng new file mode 100644 index 000000000..91dac9a48 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_G35.eng @@ -0,0 +1,33 @@ +; +; +G35EJ 29 98 4-7 0.05 0.1005 AeroTech +0.01 39.14 +0.02 76.22 +0.05 64.46 +0.13 57.54 +0.21 57.53 +0.24 64.43 +0.25 57.06 +0.35 56.12 +0.43 55.2 +0.48 57.49 +0.51 52.41 +0.55 53.33 +0.76 50.54 +0.91 50.06 +1.11 44.96 +1.32 41.24 +1.55 35.68 +1.6 36.13 +1.63 33.36 +1.67 34.28 +1.8 30.12 +2 25.02 +2.14 21.32 +2.23 19.46 +2.3 15.77 +2.41 9.76 +2.53 6.52 +2.65 3.74 +2.74 1.88 +2.91 0 diff --git a/datafiles/thrustcurves/AeroTech_G38.eng b/datafiles/thrustcurves/AeroTech_G38.eng new file mode 100644 index 000000000..18fc774bf --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_G38.eng @@ -0,0 +1,17 @@ +; +;Aerotech G38FJ RASP.ENG file made by Tim Van Milligan +;For RockSim www.RockSim.com +;File Created March 2, 2005 +;Thrust curve supplied by Aerotech for the molded case G38FJ motors. +G38FJ 29 124 4-7 0.0597 0.1264 Aerotech +0.024 52.928 +0.171 48.424 +0.497 45.045 +1 42.23 +1.279 39.978 +1.498 36.599 +1.783 30.406 +2.011 23.086 +2.272 10.135 +2.467 3.941 +2.64 0 diff --git a/datafiles/thrustcurves/AeroTech_G40.eng b/datafiles/thrustcurves/AeroTech_G40.eng new file mode 100644 index 000000000..cd2f4c3a6 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_G40.eng @@ -0,0 +1,18 @@ +; +;Aerotech G40W RASP.ENG file made by Tim Van Milligan +;For RockSim www.RockSim.com +;File Created March 2, 2005 +;Thrust curve supplied by Aerotech for the molded case G40W motors. +G40W 29 124 4-7-10 0.0538 0.123 AeroTech +0.024 74.325 +0.057 67.005 +0.252 65.879 +0.5 63.063 +0.765 60.248 +1 54.054 +1.25 47.298 +1.502 36.599 +1.751 25.338 +1.999 12.951 +2.121 3.941 +2.3 0 diff --git a/datafiles/thrustcurves/AeroTech_G53.eng b/datafiles/thrustcurves/AeroTech_G53.eng new file mode 100644 index 000000000..ac94658f3 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_G53.eng @@ -0,0 +1,26 @@ +; G53FJ based on Aerotech instruction sheet by C. Kobel 12/9/07 +G53FJ 29 124 5-7-10 0.060 0.152 AT + 0.012 44.898 + 0.031 71.504 + 0.064 80.234 + 0.081 83.976 + 0.100 86.47 + 0.150 84.599 + 0.200 81.897 + 0.300 78.571 + 0.400 76.493 + 0.500 73.583 + 0.600 70.881 + 0.700 67.347 + 0.800 63.813 + 0.900 60.072 + 1.000 54.667 + 1.100 47.392 + 1.200 39.909 + 1.300 32.426 + 1.400 25.983 + 1.500 20.578 + 1.600 10.601 + 1.700 3.949 + 1.800 1.247 + 1.850 0.0 diff --git a/datafiles/thrustcurves/AeroTech_G54.eng b/datafiles/thrustcurves/AeroTech_G54.eng new file mode 100644 index 000000000..8b0dd0033 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_G54.eng @@ -0,0 +1,39 @@ +; +;Aerotech G54 RASP.ENG file made from NAR published data +;File produced July 4, 2000 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +G54 29 124 6-10-14 0.046 0.1365 AT +0.018 10.953 +0.042 39.215 +0.083 66.888 +0.14 72.075 +0.223 74.958 +0.25 76.694 +0.282 80.156 +0.315 79.577 +0.336 79.577 +0.354 81.64 +0.365 77.841 +0.374 80.724 +0.389 76.694 +0.455 76.116 +0.523 74.39 +0.639 70.928 +0.722 67.467 +0.82 64.005 +0.897 58.817 +0.992 51.894 +1.084 43.824 +1.197 34.017 +1.268 28.251 +1.283 29.987 +1.295 27.104 +1.328 23.642 +1.366 16.719 +1.399 9.803 +1.435 4.612 +1.51 0 diff --git a/datafiles/thrustcurves/AeroTech_G55.eng b/datafiles/thrustcurves/AeroTech_G55.eng new file mode 100644 index 000000000..94f21d781 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_G55.eng @@ -0,0 +1,41 @@ +; +;Aerotech G55 RASP.ENG file made from NAR published data +;File produced July 4, 2000 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +G55 24 117 5-10-15 0.0625 0.1148 AeroTech +0.009 81.136 +0.014 84.65 +0.034 80.557 +0.084 77.064 +0.13 71.823 +0.18 72.422 +0.206 68.919 +0.342 69.538 +0.483 68.989 +0.513 66.663 +0.543 68.42 +0.664 66.114 +0.876 66.164 +0.901 64.418 +0.997 65.026 +1.062 66.793 +1.088 63.879 +1.148 63.889 +1.158 66.813 +1.173 63.31 +1.209 62.741 +1.325 61.593 +1.395 59.277 +1.456 57.541 +1.486 58.129 +1.587 52.32 +1.708 40.094 +1.824 26.11 +1.95 15.63 +2.112 7.498 +2.258 3.446 +2.44 0 diff --git a/datafiles/thrustcurves/AeroTech_G61.eng b/datafiles/thrustcurves/AeroTech_G61.eng new file mode 100644 index 000000000..6301a6e4e --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_G61.eng @@ -0,0 +1,21 @@ +; +;G61W Data Entered by Tim Van Milligan +;For RockSim: www.RockSim.com +;Based on TRA Certification Test date: June 13, 2004 +;Not Approved by TRA or Aerotech +G61W 38 106.7 6-10-14 0.0613 0.1904 AT +0.008 3.083 +0.054 71.348 +0.089 72.229 +0.174 75.312 +0.216 78.394 +0.247 79.716 +0.502 81.037 +0.753 77.073 +1.001 72.669 +1.132 66.944 +1.252 55.933 +1.503 38.316 +1.754 10.13 +1.905 3.523 +2.04 0 diff --git a/datafiles/thrustcurves/AeroTech_G64.eng b/datafiles/thrustcurves/AeroTech_G64.eng new file mode 100644 index 000000000..d3f077a39 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_G64.eng @@ -0,0 +1,38 @@ +; +;Aerotech G64 RASP.ENG file made from NAR published data +;File produced July 4, 2000 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +G64 29 124 4-8-10 0.0625 0.1512 AT +0.014 54.325 +0.032 81.488 +0.059 98.31 +0.101 85.021 +0.165 83.847 +0.274 85.614 +0.37 87.39 +0.476 86.798 +0.503 91.516 +0.517 85.614 +0.585 83.847 +0.723 80.896 +0.745 82.07 +0.773 77.945 +0.883 75.576 +0.988 74.401 +1.093 69.673 +1.262 61.412 +1.28 61.994 +1.326 58.451 +1.372 54.907 +1.422 47.238 +1.505 34.841 +1.591 23.027 +1.701 13.581 +1.829 7.085 +1.902 4.133 +1.966 1.771 +2.09 0 diff --git a/datafiles/thrustcurves/AeroTech_G67.eng b/datafiles/thrustcurves/AeroTech_G67.eng new file mode 100644 index 000000000..4c6e79063 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_G67.eng @@ -0,0 +1,49 @@ +; +; Aerotech G67R (Redline) +; +; AeroTech RMS-38/120 EZ Access Reloadable Motors (New! Hardware). +; New AeroTech Redline Motor. Just announced on AeroTech's Website! +; File produced 28 Feb 2005. +; +; The file was produced by scaling data points off the +; thrust curve in the manufacturers catalog sheet. +; +; The motor is not yet on www.thrustcurve.org. +; Hence the amateur file production. +; The file was created by Stan Hemphill. +; Contact at stanley_hemphill@hotmail.com. +; +; Motor Dia Len Delay Prop Gross Mfg +G67R 38 106 4-6-8-9-10-12-13-15-17 0.0576 0.191 AT +0.0400 004.9200 +0.0500 006.5600 +0.0600 009.8400 +0.0700 016.4100 +0.0800 032.8100 +0.1000 049.2200 +0.1300 068.0800 +0.1500 076.2900 +0.1800 080.3900 +0.2400 082.8500 +0.2600 085.3100 +0.3100 087.7700 +0.5100 089.4100 +0.5300 091.0500 +0.5600 087.7700 +0.6000 086.9500 +0.6100 088.5900 +0.6700 086.9500 +0.6900 085.3100 +0.7100 086.9500 +0.7200 085.3100 +0.7400 086.1300 +0.7700 085.3100 +0.8100 082.0300 +0.9500 078.7500 +1.0500 073.8200 +1.4300 053.3200 +1.5000 052.5000 +1.5200 050.8600 +1.5400 045.9400 +1.6200 011.4800 +1.6400 000.0000 diff --git a/datafiles/thrustcurves/AeroTech_G69.eng b/datafiles/thrustcurves/AeroTech_G69.eng new file mode 100644 index 000000000..19cffae78 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_G69.eng @@ -0,0 +1,28 @@ +; Submitted to ThrustCurve.org by Chris Kobel (4/13/07) +; G69N based on Aerotech instruction sheet by C. Kobel 3/29/07 +G69N 38 106 0 0.0622 0.195 AT + 0.020 51.972 + 0.050 75.574 + 0.100 76.709 + 0.200 77.617 + 0.300 79.206 + 0.400 81.475 + 0.500 84.425 + 0.600 86.922 + 0.700 88.737 + 0.800 89.645 + 0.900 91.688 + 1.000 93.503 + 1.100 94.411 + 1.200 94.638 + 1.300 93.957 + 1.350 93.05 + 1.400 89.418 + 1.500 62.865 + 1.600 33.362 + 1.650 19.518 + 1.700 12.028 + 1.750 7.489 + 1.800 4.539 + 1.900 1.816 + 2.000 0.0 diff --git a/datafiles/thrustcurves/AeroTech_G71.eng b/datafiles/thrustcurves/AeroTech_G71.eng new file mode 100644 index 000000000..0fc460792 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_G71.eng @@ -0,0 +1,21 @@ +; G71R based on Aerotech instruction sheet by C. Kobel 3/29/07 +G71R 29 124 4-7-10 0.0569 0.147 AT + 0.000 0.389 + 0.050 109.714 + 0.100 117.884 + 0.200 113.216 + 0.300 109.714 + 0.400 105.045 + 0.500 99.21 + 0.600 92.207 + 0.700 83.258 + 0.800 75.477 + 0.900 68.085 + 1.000 57.97 + 1.100 47.465 + 1.200 33.848 + 1.300 21.009 + 1.400 11.283 + 1.500 5.447 + 1.600 2.334 + 1.700 0.0 diff --git a/datafiles/thrustcurves/AeroTech_G71_1.eng b/datafiles/thrustcurves/AeroTech_G71_1.eng new file mode 100644 index 000000000..348e7dfa0 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_G71_1.eng @@ -0,0 +1,72 @@ +; RMS-29/40-120 Reload, 2 grain design, G71-XR (Redline propellent), with 4, 7, +; 10 second delays +G71-R 29 120 7 0.0569 0.147 AT + 0.0080 46.656 + 0.015 74.248 + 0.023 85.787 + 0.031 100.336 + 0.05 110.871 + 0.062 116.891 + 0.085 120.403 + 0.116 119.901 + 0.139 118.898 + 0.158 116.891 + 0.181 115.386 + 0.216 114.383 + 0.251 113.379 + 0.278 111.373 + 0.297 111.373 + 0.309 114.383 + 0.328 112.376 + 0.355 109.366 + 0.39 107.359 + 0.432 104.851 + 0.463 103.848 + 0.494 100.837 + 0.525 98.831 + 0.552 95.821 + 0.583 94.316 + 0.606 92.309 + 0.633 89.299 + 0.653 87.292 + 0.676 85.787 + 0.699 81.272 + 0.714 84.282 + 0.734 81.272 + 0.749 88.797 + 0.772 80.269 + 0.799 76.255 + 0.826 73.747 + 0.861 70.737 + 0.876 73.747 + 0.892 69.232 + 0.915 69.733 + 0.923 65.72 + 0.942 63.713 + 0.977 60.703 + 1.008 58.195 + 1.039 54.181 + 1.077 50.168 + 1.108 46.154 + 1.12 50.168 + 1.127 46.154 + 1.143 43.144 + 1.178 37.626 + 1.212 32.609 + 1.232 30.101 + 1.255 26.589 + 1.274 23.579 + 1.301 20.569 + 1.317 18.06 + 1.344 15.552 + 1.382 11.539 + 1.417 10.034 + 1.448 7.024 + 1.486 6.02 + 1.517 3.512 + 1.552 3.01 + 1.587 2.508 + 1.618 1.003 + 1.668 0.502 + 1.707 0.502 + 1.734 0.0 diff --git a/datafiles/thrustcurves/AeroTech_G75.eng b/datafiles/thrustcurves/AeroTech_G75.eng new file mode 100644 index 000000000..c85f277c8 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_G75.eng @@ -0,0 +1,30 @@ +; AeroTech G75J +; Copyright Tripoli Motor Testing 1997 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +G75J 29 194 10 0.112 0.23296 AT + 0.047 65.701 + 0.143 68.564 + 0.239 72.143 + 0.334 73.261 + 0.430 73.960 + 0.526 75.036 + 0.622 75.705 + 0.718 75.030 + 0.814 77.886 + 0.909 76.183 + 1.005 76.852 + 1.101 75.729 + 1.197 78.854 + 1.293 78.669 + 1.389 76.464 + 1.484 76.440 + 1.580 74.976 + 1.676 72.657 + 1.772 69.460 + 1.868 62.121 + 1.964 39.090 + 2.059 19.703 + 2.155 7.554 + 2.251 2.062 + 2.347 0.382 + 2.443 0.000 diff --git a/datafiles/thrustcurves/AeroTech_G75_1.eng b/datafiles/thrustcurves/AeroTech_G75_1.eng new file mode 100644 index 000000000..e3e4aa5df --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_G75_1.eng @@ -0,0 +1,83 @@ +; +; Aerotech G75J (Black Jack) +; +; AeroTech RMS-29/180 Easy Access Reloadable Motor Hardware. +; +; RASP.ENG file made from made from NAR or TMT published data. +; +; File produced May, 17 2004. +; +; The total impulse, peak thrust, average thrust and burn time are +; the same as the averaged static test data from NAR or TMT files. +; +; The curve drawn with these data points is as accurate as could +; could be made scaling the data from the curve on the TMT html +; page. The file is 63 data points. NOT wRASP v1.6 compatible. +; +; The file was created by Stan Hemphill. +; Contact at stanley_hemphill@hotmail.com. +; +; Motor Dia Len Delay Prop Gross Mfg +G75J 29 194 1-3--4-6-7-9-10 0.114 0.236 AT +0.0281 068.8604 +0.0380 078.6517 +0.0561 075.9230 +0.0660 073.0337 +0.0776 070.4655 +0.1139 069.3419 +0.1403 068.6998 +0.1667 067.5762 +0.1881 070.3050 +0.2013 069.0209 +0.2294 072.5522 +0.2541 076.4045 +0.2723 071.4286 +0.3102 076.2440 +0.3350 071.9101 +0.4208 075.6019 +0.4604 072.3917 +0.5215 079.2937 +0.5941 073.3547 +0.6436 080.0963 +0.7013 073.8363 +0.7393 076.4045 +0.7541 074.6388 +0.7657 077.3676 +0.7937 078.6517 +0.8036 077.0465 +0.8168 080.2568 +0.8267 075.2809 +0.8383 081.5409 +0.8581 075.7624 +0.8795 077.8491 +0.9340 074.1573 +0.9868 079.9358 +1.0380 076.7255 +1.0561 072.2311 +1.0941 078.8122 +1.1221 075.6019 +1.1502 080.8989 +1.1617 076.8860 +1.1848 080.0963 +1.1997 076.7255 +1.2327 078.8122 +1.2508 076.4045 +1.2871 082.5040 +1.3102 077.5281 +1.3267 081.8620 +1.3564 075.2809 +1.3729 080.0963 +1.4076 075.7624 +1.4884 079.9358 +1.5116 073.6758 +1.5297 081.0594 +1.5512 074.6388 +1.5611 080.8989 +1.6040 072.8732 +1.7211 075.6019 +1.7607 068.6998 +1.7789 070.1445 +1.8119 066.9342 +1.8267 070.7865 +1.8482 070.7865 +2.3878 000.0000 diff --git a/datafiles/thrustcurves/AeroTech_G76.eng b/datafiles/thrustcurves/AeroTech_G76.eng new file mode 100644 index 000000000..c70eca0b5 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_G76.eng @@ -0,0 +1,41 @@ +; Curve fit of AT Instruction sheet by C. Kobel 7/29/08 +G76G 29 124 4-7-10 0.06 0.147 AT + 0.025 89.368 + 0.042 133.581 + 0.052 144.87 + 0.067 154.277 + 0.098 144.399 + 0.117 136.873 + 0.150 132.64 + 0.196 129.348 + 0.255 123.233 + 0.299 118.059 + 0.349 112.885 + 0.399 108.652 + 0.449 101.126 + 0.486 101.597 + 0.511 105.36 + 0.516 118.53 + 0.543 100.186 + 0.601 95.482 + 0.656 88.897 + 0.720 81.842 + 0.737 93.601 + 0.754 80.431 + 0.797 70.553 + 0.856 63.498 + 0.898 58.794 + 0.948 51.739 + 1.000 47.976 + 1.063 43.273 + 1.102 41.391 + 1.152 39.04 + 1.200 36.688 + 1.301 30.103 + 1.347 25.399 + 1.401 19.755 + 1.499 12.229 + 1.547 7.996 + 1.599 5.644 + 1.699 2.352 + 1.750 0.0 diff --git a/datafiles/thrustcurves/AeroTech_G76_1.eng b/datafiles/thrustcurves/AeroTech_G76_1.eng new file mode 100644 index 000000000..37b1c4004 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_G76_1.eng @@ -0,0 +1,34 @@ +; Exported using ThrustCurveTool, www.ThrustGear.com +; NAR S&T Data, contributed by John DeMar +G72 29 124 4-7-10 0.06 0.144 Aerotech + 0.0040 4.2403 + 0.0060 9.6831 + 0.016 54.397 + 0.03 98.17 + 0.034 108.509 + 0.04 121.159 + 0.048 133.047 + 0.058 142.348 + 0.068 147.209 + 0.082 149.393 + 0.112 146.89 + 0.15 138.783 + 0.17 136.039 + 0.376 112.222 + 0.466 103.079 + 0.482 104.438 + 0.56 92.6523 + 0.606 92.8669 + 0.644 84.2058 + 0.748 73.9389 + 0.76 74.2841 + 0.79 69.704 + 0.804 70.6 + 0.822 66.7591 + 0.856 62.6853 + 1.154 39.9972 + 1.374 19.9105 + 1.474 12.5198 + 1.574 7.41771 + 1.78 1.19026 + 2.00 0.0 diff --git a/datafiles/thrustcurves/AeroTech_G77.eng b/datafiles/thrustcurves/AeroTech_G77.eng new file mode 100644 index 000000000..097b72183 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_G77.eng @@ -0,0 +1,40 @@ +; +; Aerotech G77R (Redline) +; +; AeroTech RMS-29/120 EZ Access Reloadable Motors (New! Hardware). +; New AeroTech Redline Motor. Just announced on AeroTech's Website! +; File produced 28 Feb 2005. +; +; The file was produced by scaling data points off the +; thrust curve in the manufacturers catalog sheet. +; +; The motor is not yet on www.thrustcurve.org. +; Hence the amateur file production. +; The file was created by Stan Hemphill. +; Contact at stanley_hemphill@hotmail.com. +; +; Motor Dia Len Delay Prop Gross Mfg +G77R 29 150 4-6-8-9-10-12-13-15-17 0.0554 0.155 AT +0.0132 014.8333 +0.0243 032.4479 +0.0331 046.3542 +0.0375 052.8438 +0.0463 056.5521 +0.0617 059.3333 +0.2580 073.2396 +0.6548 087.1458 +0.8709 089.0000 +0.8885 085.2917 +1.0252 086.2188 +1.0472 084.3646 +1.0715 086.2188 +1.1002 084.3646 +1.1332 085.2917 +1.1950 076.9479 +1.2104 076.0208 +1.2369 065.8229 +1.2611 043.5729 +1.2898 027.8125 +1.3317 012.0521 +1.3625 004.6354 +1.4000 000.0000 diff --git a/datafiles/thrustcurves/AeroTech_G78.eng b/datafiles/thrustcurves/AeroTech_G78.eng new file mode 100644 index 000000000..fff4c4876 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_G78.eng @@ -0,0 +1,35 @@ +; @File: G78G_Typ.txt, @Pts-I: 1001, @Pts-O: 31, @Sm: 0, @CO: 5% +; @TI: 109.776, @TIa: 109.5639, @TIe: 0.0%, @ThMax: 102.242, @ThAvg: 79.5671, @Tb: 1.377 +; Exported using ThrustCurveTool, www.ThrustGear.com +G78G 29 146 4-7-10, 0.0597 0.125 AT/RCS +0.0040 2.76203 +0.0060 34.7707 +0.0080 44.326 +0.01 41.2789 +0.012 28.5933 +0.014 27.3926 +0.016 38.0574 +0.02 47.7036 +0.022 48.2012 +0.03 56.4344 +0.042 61.4034 +0.06 65.883 +0.212 84.526 +0.266 88.9218 +0.404 95.5932 +0.43 98.9746 +0.466 100.5362 +0.58 102.242 +0.694 101.3187 +0.86 95.4526 +1.1 90.4186 +1.132 82.5618 +1.156 72.6383 +1.168 64.903 +1.194 42.3389 +1.216 28.3424 +1.226 24.9802 +1.2559 17.40051 +1.2859 13.04651 +1.3819 5.05295 +1.4719 0.0 diff --git a/datafiles/thrustcurves/AeroTech_G79.eng b/datafiles/thrustcurves/AeroTech_G79.eng new file mode 100644 index 000000000..508cb9d55 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_G79.eng @@ -0,0 +1,25 @@ +; +;G79W Data Entered by Tim Van Milligan +;For RockSim: www.RockSim.com +;Based on TRA Certification Test date: June 13, 2004 +;Not Approved by TRA or Aerotech +G79W 29 149.86 6-10-14 0.0609 0.154 AT +0.015 7.157 +0.074 91.937 +0.09 91.387 +0.114 84.781 +0.145 84.23 +0.201 89.185 +0.291 94.69 +0.4 98.544 +0.6 99.645 +0.708 96.892 +0.8 93.038 +0.915 85.331 +1 77.624 +1.085 71.017 +1.175 68.265 +1.199 44.59 +1.28 22.021 +1.36 4.955 +1.42 0 diff --git a/datafiles/thrustcurves/AeroTech_G80.eng b/datafiles/thrustcurves/AeroTech_G80.eng new file mode 100644 index 000000000..0df93f6e0 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_G80.eng @@ -0,0 +1,37 @@ +; 136 N-sec G80, Certified Nov. 2007. As published by NAR S&T. +; +; @File: NewATG80.txt, @Pts-I: 905, @Pts-O: 31, @Sm: 0, @CO: 5% +; @TI: 133.2377, @TIa: 133.1309, @TIe: 0.0%, @ThMax: 102.2, @ThAvg: 77.9911, @Tb: 1.707 +; Exported using ThrustCurveTool, www.ThrustGear.com +G78 29 128 7,10,13 0.0625 0.1282 RCS/Aerotech +0.0060 1.158086 +0.0080 7.48984 +0.01 33.7575 +0.012 64.5955 +0.014 62.9316 +0.016 58.8272 +0.018 74.9118 +0.02 85.0062 +0.022 91.1072 +0.026 93.9913 +0.028 98.4284 +0.032 97.652 +0.038 102.2 +0.074 97.3192 +0.124 95.4334 +0.376 99.3159 +0.68 99.4268 +0.994 91.6619 +1.2459 83.0095 +1.2819 77.3522 +1.3159 61.9332 +1.3599 44.6285 +1.4239 29.0986 +1.5039 21.2227 +1.5979 19.33693 +1.6559 16.34188 +1.6759 13.90147 +1.6779 11.79384 +1.7139 5.0938 +1.7339 1.388816 +1.8079 0.0 diff --git a/datafiles/thrustcurves/AeroTech_G80_1.eng b/datafiles/thrustcurves/AeroTech_G80_1.eng new file mode 100644 index 000000000..37179e0df --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_G80_1.eng @@ -0,0 +1,28 @@ +; AEROTECH G80 RASP.ENG FILE +; Note: this is for the 94 N-sec G80T certified in Sept. 2006 +G80 29 124 4-7-10 0.0479 0.1129998 AERO + 0.0060 84.371 + 0.018 118.23 + 0.027 109.378 + 0.037 101.35 + 0.042 105.565 + 0.059 98.37 + 0.113 95.821 + 0.185 96.252 + 0.277 94.556 + 0.404 94.978 + 0.526 91.165 + 0.671 87.1 + 0.792 82.675 + 0.885 77.166 + 0.943 73.353 + 0.971 68.266 + 0.997 57.237 + 1.03 48.337 + 1.059 40.279 + 1.085 27.986 + 1.112 17.811 + 1.144 10.175 + 1.168 5.512 + 1.182 2.968 + 1.21 0.0 diff --git a/datafiles/thrustcurves/AeroTech_G80_2.eng b/datafiles/thrustcurves/AeroTech_G80_2.eng new file mode 100644 index 000000000..794363221 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_G80_2.eng @@ -0,0 +1,34 @@ +; Aerotech G80 RASP.ENG file made from NAR published data +; File produced July 4, 2000 +; Note: This is for the 116N-sec G80T produced before Sept. 2006 +G80 29 124 4-7-10 0.0574 0.1049 A + 0.0060 101.291 + 0.013 105.18 + 0.031 103.473 + 0.038 104.069 + 0.067 99.803 + 0.103 96.906 + 0.181 94.733 + 0.271 94.039 + 0.303 96.985 + 0.367 95.547 + 0.428 94.842 + 0.456 97.055 + 0.463 92.65 + 0.51 94.872 + 0.596 93.444 + 0.606 95.646 + 0.624 91.985 + 0.635 95.656 + 0.646 91.995 + 0.696 90.547 + 0.846 85.477 + 0.96 80.388 + 1.071 74.564 + 1.207 62.878 + 1.296 52.639 + 1.35 37.252 + 1.382 20.397 + 1.418 10.139 + 1.457 4.281 + 1.5 0.0 diff --git a/datafiles/thrustcurves/AeroTech_H112.eng b/datafiles/thrustcurves/AeroTech_H112.eng new file mode 100644 index 000000000..0663f32ab --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_H112.eng @@ -0,0 +1,30 @@ +; AeroTech H112J +; Copyright Tripoli Motor Testing 1999 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +H112J 38 202 0 0.187712 0.379456 AT + 0.064 85.431 + 0.194 101.938 + 0.324 101.897 + 0.454 102.839 + 0.584 104.479 + 0.715 103.845 + 0.845 103.439 + 0.975 104.286 + 1.106 104.922 + 1.236 104.390 + 1.367 102.768 + 1.497 102.237 + 1.627 100.032 + 1.757 98.345 + 1.888 94.560 + 2.018 89.018 + 2.148 82.857 + 2.279 77.685 + 2.409 72.373 + 2.540 67.041 + 2.670 59.764 + 2.800 37.616 + 2.930 14.457 + 3.060 4.642 + 3.192 1.818 + 3.323 0.000 diff --git a/datafiles/thrustcurves/AeroTech_H123.eng b/datafiles/thrustcurves/AeroTech_H123.eng new file mode 100644 index 000000000..32cff0f07 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_H123.eng @@ -0,0 +1,30 @@ +; AeroTech H123W +; Copyright Tripoli Motor Testing 1999 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +H123W 38 154 0 0.126336 0.278656 AT + 0.047 96.764 + 0.143 146.256 + 0.239 150.699 + 0.334 152.496 + 0.430 151.248 + 0.526 149.875 + 0.622 150.200 + 0.718 149.176 + 0.814 144.858 + 0.909 143.536 + 1.005 141.414 + 1.101 135.125 + 1.198 125.288 + 1.295 114.035 + 1.391 101.556 + 1.486 90.175 + 1.582 78.694 + 1.678 66.364 + 1.774 54.260 + 1.870 46.872 + 1.966 38.186 + 2.061 22.737 + 2.157 13.478 + 2.253 7.587 + 2.350 5.252 + 2.447 0.000 diff --git a/datafiles/thrustcurves/AeroTech_H125.eng b/datafiles/thrustcurves/AeroTech_H125.eng new file mode 100644 index 000000000..c7951e460 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_H125.eng @@ -0,0 +1,30 @@ +; AeroTech H125W +; Copyright Tripoli Motor Testing 1997 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +H125W 29 330 14 0.18816 0.32256 AT + 0.053 275.994 + 0.161 241.473 + 0.270 216.283 + 0.378 199.482 + 0.488 188.758 + 0.597 182.349 + 0.705 175.862 + 0.814 169.365 + 0.922 162.281 + 1.031 154.276 + 1.141 143.915 + 1.249 133.480 + 1.357 123.007 + 1.466 113.058 + 1.575 101.801 + 1.684 88.423 + 1.793 73.530 + 1.901 60.425 + 2.009 46.643 + 2.119 36.785 + 2.228 29.546 + 2.336 23.641 + 2.445 18.794 + 2.553 14.728 + 2.663 10.970 + 2.772 0.000 diff --git a/datafiles/thrustcurves/AeroTech_H128.eng b/datafiles/thrustcurves/AeroTech_H128.eng new file mode 100644 index 000000000..8fbfbb9d3 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_H128.eng @@ -0,0 +1,30 @@ +; AeroTech H128W +; Copyright Tripoli Motor Testing 1997 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +H128W 29 194 14 0.09408 0.2016 AT + 0.024 102.423 + 0.074 175.203 + 0.125 181.379 + 0.176 179.281 + 0.226 181.423 + 0.277 186.074 + 0.328 190.483 + 0.378 189.509 + 0.429 186.162 + 0.480 184.114 + 0.530 180.300 + 0.581 174.144 + 0.632 172.019 + 0.683 169.360 + 0.734 166.017 + 0.784 161.882 + 0.835 157.331 + 0.886 153.073 + 0.936 151.985 + 0.988 139.902 + 1.039 87.865 + 1.089 40.857 + 1.140 14.328 + 1.191 4.330 + 1.242 1.550 + 1.293 0.000 diff --git a/datafiles/thrustcurves/AeroTech_H148.eng b/datafiles/thrustcurves/AeroTech_H148.eng new file mode 100644 index 000000000..af5e0ad67 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_H148.eng @@ -0,0 +1,29 @@ +; AeroTech H148R +; provided by ThrustCurve.org (www.thrustcurve.org) +H148R 38 152 0 0.14784 0.30912 AT + 0.027 77.232 + 0.088 174.296 + 0.148 185.046 + 0.208 190.458 + 0.268 192.497 + 0.327 191.996 + 0.388 188.790 + 0.448 187.548 + 0.509 182.697 + 0.570 178.151 + 0.630 172.906 + 0.690 169.607 + 0.750 164.510 + 0.810 158.375 + 0.870 153.019 + 0.930 146.810 + 0.991 139.443 + 1.053 132.001 + 1.112 123.271 + 1.173 112.559 + 1.233 104.737 + 1.292 97.657 + 1.353 94.932 + 1.413 60.644 + 1.474 13.007 + 1.535 0.000 diff --git a/datafiles/thrustcurves/AeroTech_H165.eng b/datafiles/thrustcurves/AeroTech_H165.eng new file mode 100644 index 000000000..be080117b --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_H165.eng @@ -0,0 +1,29 @@ +; AeroTech H165R +; provided by ThrustCurve.org (www.thrustcurve.org) +H165R 29 194 0 0.0896 0.2016 AT + 0.018 55.047 + 0.059 157.258 + 0.101 168.509 + 0.144 173.219 + 0.186 179.237 + 0.229 183.947 + 0.271 187.872 + 0.314 188.134 + 0.356 188.919 + 0.399 190.488 + 0.441 187.349 + 0.484 189.180 + 0.525 186.547 + 0.566 185.517 + 0.609 180.807 + 0.651 177.667 + 0.694 170.602 + 0.736 167.201 + 0.779 158.828 + 0.821 155.688 + 0.864 153.333 + 0.906 136.325 + 0.949 73.526 + 0.991 20.671 + 1.034 4.448 + 1.076 0.000 diff --git a/datafiles/thrustcurves/AeroTech_H180.eng b/datafiles/thrustcurves/AeroTech_H180.eng new file mode 100644 index 000000000..7b261279f --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_H180.eng @@ -0,0 +1,30 @@ +; AeroTech H180W +; Copyright Tripoli Motor Testing 1997 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +H180W 29 238 6 0.12096 0.2464 AT + 0.024 149.374 + 0.075 222.273 + 0.127 222.339 + 0.178 227.835 + 0.229 234.963 + 0.281 238.162 + 0.333 240.252 + 0.384 243.126 + 0.435 240.757 + 0.487 240.724 + 0.539 236.311 + 0.590 236.799 + 0.642 234.897 + 0.694 232.763 + 0.745 229.198 + 0.796 228.816 + 0.848 231.906 + 0.899 225.853 + 0.950 188.285 + 1.002 134.679 + 1.054 78.940 + 1.105 34.557 + 1.156 15.482 + 1.208 7.279 + 1.260 3.585 + 1.313 0.000 diff --git a/datafiles/thrustcurves/AeroTech_H210.eng b/datafiles/thrustcurves/AeroTech_H210.eng new file mode 100644 index 000000000..fbbc08e6b --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_H210.eng @@ -0,0 +1,29 @@ +; AeroTech H210R +; provided by ThrustCurve.org (www.thrustcurve.org) +H210R 29 238 0 0.12096 0.2464 AT + 0.019 105.923 + 0.059 211.290 + 0.099 219.770 + 0.139 229.639 + 0.179 235.082 + 0.220 241.594 + 0.260 242.706 + 0.300 245.347 + 0.341 249.100 + 0.381 253.410 + 0.421 258.553 + 0.461 260.221 + 0.502 257.997 + 0.543 259.248 + 0.583 256.607 + 0.623 252.436 + 0.663 245.056 + 0.704 219.909 + 0.744 209.344 + 0.784 200.587 + 0.824 193.565 + 0.865 184.323 + 0.905 153.881 + 0.945 58.244 + 0.986 13.210 + 1.027 0.000 diff --git a/datafiles/thrustcurves/AeroTech_H220.eng b/datafiles/thrustcurves/AeroTech_H220.eng new file mode 100644 index 000000000..6c3483856 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_H220.eng @@ -0,0 +1,12 @@ +; +; +H220T 29 239 6-10-14 0.1064 0.2386 AT +0 314.1 +0.1 236.61 +0.2 269.23 +0.3 261.06 +0.4 252.9 +0.72 252.9 +0.8 112.58 +0.9 9.78 +0.96 0 diff --git a/datafiles/thrustcurves/AeroTech_H238.eng b/datafiles/thrustcurves/AeroTech_H238.eng new file mode 100644 index 000000000..66810d4a2 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_H238.eng @@ -0,0 +1,30 @@ +; AeroTech H238T +; Copyright Tripoli Motor Testing 1997 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +H238T 29 194 6 0.08064 0.18816 AT + 0.019 173.100 + 0.059 206.876 + 0.100 211.036 + 0.141 214.353 + 0.181 217.113 + 0.222 219.343 + 0.263 221.034 + 0.303 224.951 + 0.344 229.324 + 0.384 229.308 + 0.425 228.558 + 0.466 226.573 + 0.506 222.365 + 0.547 219.205 + 0.588 217.750 + 0.628 213.350 + 0.669 208.070 + 0.709 200.966 + 0.750 196.988 + 0.791 151.387 + 0.831 96.273 + 0.872 59.475 + 0.912 18.621 + 0.953 7.986 + 0.995 3.697 + 1.036 0.000 diff --git a/datafiles/thrustcurves/AeroTech_H242.eng b/datafiles/thrustcurves/AeroTech_H242.eng new file mode 100644 index 000000000..ca4affae9 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_H242.eng @@ -0,0 +1,30 @@ +; AeroTech H242T +; Copyright Tripoli Motor Testing 1997 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +H242T 38 152 10 0.11648 0.2688 AT + 0.030 164.060 + 0.093 197.516 + 0.155 204.324 + 0.218 208.970 + 0.280 211.481 + 0.343 211.261 + 0.405 209.291 + 0.468 208.438 + 0.531 206.707 + 0.595 203.967 + 0.657 198.175 + 0.720 192.137 + 0.782 186.840 + 0.845 180.802 + 0.907 174.635 + 0.970 165.581 + 1.033 159.726 + 1.097 151.690 + 1.159 144.167 + 1.222 138.550 + 1.284 119.114 + 1.347 69.055 + 1.409 21.396 + 1.472 3.473 + 1.535 0.594 + 1.599 0.000 diff --git a/datafiles/thrustcurves/AeroTech_H242_1.eng b/datafiles/thrustcurves/AeroTech_H242_1.eng new file mode 100644 index 000000000..a59878ebd --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_H242_1.eng @@ -0,0 +1,30 @@ +; AeroTech H242T +; Copyright Tripoli Motor Testing 1999 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +H242T 38 154 0 0.114688 0.264768 AT + 0.025 207.058 + 0.077 237.941 + 0.129 240.171 + 0.181 241.906 + 0.234 246.425 + 0.287 245.971 + 0.340 247.210 + 0.392 246.516 + 0.445 245.710 + 0.498 244.881 + 0.550 242.997 + 0.602 240.518 + 0.655 235.271 + 0.708 229.464 + 0.760 222.871 + 0.813 216.278 + 0.866 206.959 + 0.919 195.458 + 0.971 184.255 + 1.023 174.490 + 1.076 170.067 + 1.129 99.588 + 1.181 25.281 + 1.233 12.839 + 1.286 7.769 + 1.340 0.000 diff --git a/datafiles/thrustcurves/AeroTech_H250.eng b/datafiles/thrustcurves/AeroTech_H250.eng new file mode 100644 index 000000000..24c0e9992 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_H250.eng @@ -0,0 +1,26 @@ +;I don't know that +;these ejection +;delays are correct. +;This was made +;using the Aerotech +;test thrust curves. +;By Tobin Yehle, +;11/11/07. +H250G 29 228.93 0-6-10-14 0.1163 0.256 Aerotech +0.00250627 88.6915 +0.0125313 177.383 +0.0300752 279.719 +0.0726817 311.103 +0.145363 320.654 +0.24812 311.103 +0.308271 297.458 +0.398496 282.448 +0.45614 270.168 +0.593985 238.785 +0.691729 221.047 +0.799499 218.318 +0.83208 210.131 +0.844612 189.663 +0.907268 13.6449 +0.92 0 +; diff --git a/datafiles/thrustcurves/AeroTech_H268.eng b/datafiles/thrustcurves/AeroTech_H268.eng new file mode 100644 index 000000000..a20086467 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_H268.eng @@ -0,0 +1,29 @@ +; AeroTech H268R +; provided by ThrustCurve.org (www.thrustcurve.org) +H268R 29 333 0 0.18368 0.3584 AT + 0.022 268.095 + 0.069 332.446 + 0.116 312.429 + 0.164 306.810 + 0.211 305.757 + 0.259 306.576 + 0.306 312.546 + 0.354 319.687 + 0.401 321.234 + 0.448 320.974 + 0.495 321.208 + 0.542 321.794 + 0.590 323.315 + 0.638 322.847 + 0.685 307.044 + 0.732 291.593 + 0.779 277.713 + 0.826 267.127 + 0.874 257.529 + 0.921 252.846 + 0.969 222.645 + 1.016 159.668 + 1.064 108.747 + 1.111 52.091 + 1.159 15.569 + 1.207 0.000 diff --git a/datafiles/thrustcurves/AeroTech_H45.eng b/datafiles/thrustcurves/AeroTech_H45.eng new file mode 100644 index 000000000..2b98b870e --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_H45.eng @@ -0,0 +1,30 @@ +; AeroTech H45W +; Copyright Tripoli Motor Testing 1998 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +H45W 38 194 0 0.193984 0.294784 AT + 0.141 62.554 + 0.424 63.504 + 0.707 65.913 + 0.992 68.370 + 1.276 69.315 + 1.559 68.523 + 1.843 67.231 + 2.127 65.705 + 2.411 63.154 + 2.695 59.210 + 2.979 55.600 + 3.264 50.790 + 3.547 45.237 + 3.830 39.835 + 4.115 34.562 + 4.399 29.213 + 4.682 24.720 + 4.967 20.616 + 5.251 17.475 + 5.534 14.498 + 5.818 12.697 + 6.102 10.792 + 6.386 9.229 + 6.670 7.754 + 6.954 6.075 + 7.239 0.000 diff --git a/datafiles/thrustcurves/AeroTech_H55.eng b/datafiles/thrustcurves/AeroTech_H55.eng new file mode 100644 index 000000000..579bd6a4b --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_H55.eng @@ -0,0 +1,30 @@ +; AeroTech H55W +; Copyright Tripoli Motor Testing 1998 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +H55W 29 191 0 0.09856 0.18816 AT + 0.052 92.752 + 0.159 98.019 + 0.268 95.821 + 0.375 96.162 + 0.482 97.146 + 0.591 96.927 + 0.699 95.915 + 0.806 94.447 + 0.914 92.001 + 1.022 88.756 + 1.129 86.970 + 1.236 84.072 + 1.345 80.172 + 1.453 74.343 + 1.560 64.990 + 1.668 46.380 + 1.776 32.835 + 1.883 25.734 + 1.991 19.920 + 2.099 16.229 + 2.207 13.059 + 2.315 10.451 + 2.422 7.700 + 2.530 5.696 + 2.639 3.979 + 2.747 0.000 diff --git a/datafiles/thrustcurves/AeroTech_H669.eng b/datafiles/thrustcurves/AeroTech_H669.eng new file mode 100644 index 000000000..2bddd5e65 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_H669.eng @@ -0,0 +1,42 @@ +; +; 38-240 +; Greg Gardner - 09/15/06 +H669N 38 152 0 0.096 0.252 AT +0.003 141 +0.006 523 +0.009 934 +0.012 1178 +0.016 926 +0.019 684 +0.022 487 +0.025 415 +0.028 622 +0.031 801 +0.0325 906 +0.034 866 +0.037 755 +0.04 737 +0.043 666 +0.047 737 +0.0485 802 +0.05 755 +0.053 791 +0.056 765 +0.059 755 +0.062 747 +0.069 737 +0.075 761 +0.082 755 +0.088 729 +0.093 741 +0.1 751 +0.2 703 +0.25 640 +0.3 586 +0.306 584 +0.309 576 +0.312 506 +0.318 292 +0.325 93 +0.329 0 +; diff --git a/datafiles/thrustcurves/AeroTech_H70.eng b/datafiles/thrustcurves/AeroTech_H70.eng new file mode 100644 index 000000000..2b714e9db --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_H70.eng @@ -0,0 +1,30 @@ +; AeroTech H70W +; Copyright Tripoli Motor Testing 1997 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +H70W 29 229 0 0.11648 0.224 AT + 0.055 114.847 + 0.169 131.427 + 0.283 126.879 + 0.397 127.136 + 0.510 127.254 + 0.625 125.894 + 0.739 124.917 + 0.852 122.031 + 0.967 119.032 + 1.080 115.071 + 1.194 108.446 + 1.308 102.273 + 1.422 96.098 + 1.535 86.953 + 1.650 75.702 + 1.764 62.402 + 1.877 48.132 + 1.992 36.862 + 2.105 28.065 + 2.219 21.592 + 2.333 16.894 + 2.447 12.686 + 2.560 9.681 + 2.675 6.818 + 2.790 4.488 + 2.904 0.000 diff --git a/datafiles/thrustcurves/AeroTech_H73.eng b/datafiles/thrustcurves/AeroTech_H73.eng new file mode 100644 index 000000000..fba06878f --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_H73.eng @@ -0,0 +1,30 @@ +; AeroTech H73J +; Copyright Tripoli Motor Testing 1997 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +H73J 38 152 6 0.14784 0.30912 AT + 0.056 49.252 + 0.172 82.004 + 0.287 82.130 + 0.403 84.596 + 0.520 86.883 + 0.635 88.888 + 0.751 89.652 + 0.867 91.342 + 0.982 92.980 + 1.099 94.571 + 1.215 94.641 + 1.330 93.549 + 1.446 91.447 + 1.561 88.189 + 1.678 82.436 + 1.794 77.397 + 1.909 70.772 + 2.025 61.173 + 2.141 51.161 + 2.257 38.540 + 2.373 21.562 + 2.489 12.213 + 2.604 7.327 + 2.720 3.706 + 2.836 1.777 + 2.953 0.000 diff --git a/datafiles/thrustcurves/AeroTech_H97.eng b/datafiles/thrustcurves/AeroTech_H97.eng new file mode 100644 index 000000000..3504d3343 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_H97.eng @@ -0,0 +1,30 @@ +; AeroTech H97J +; Copyright Tripoli Motor Testing 1997 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +H97J 29 238 6 0.1344 0.27776 AT + 0.045 89.405 + 0.136 100.289 + 0.228 100.463 + 0.320 102.019 + 0.411 102.813 + 0.503 103.550 + 0.595 101.701 + 0.686 103.056 + 0.778 103.331 + 0.870 102.613 + 0.961 103.394 + 1.053 100.963 + 1.145 101.226 + 1.236 99.864 + 1.328 98.420 + 1.420 96.827 + 1.511 95.034 + 1.603 93.241 + 1.695 93.485 + 1.786 88.068 + 1.878 64.358 + 1.970 30.264 + 2.061 8.691 + 2.153 1.399 + 2.245 0.525 + 2.336 0.000 diff --git a/datafiles/thrustcurves/AeroTech_H999.eng b/datafiles/thrustcurves/AeroTech_H999.eng new file mode 100644 index 000000000..15d7e2bdb --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_H999.eng @@ -0,0 +1,42 @@ +; +; 38-360 +; Greg Gardner - 09/15/06 +H999N 38 203 0 0.144 0.331 AT +0.003 204 +0.006 757 +0.009 1357 +0.012 1710 +0.016 1345 +0.019 995 +0.022 710 +0.025 606 +0.028 905 +0.031 1165 +0.0325 1311 +0.034 1258 +0.037 1098 +0.04 1072 +0.043 969 +0.047 1072 +0.0485 1166 +0.05 1098 +0.053 1160 +0.056 1117 +0.059 1103 +0.062 1093 +0.069 1076 +0.075 1110 +0.082 1105 +0.088 1065 +0.093 1082 +0.1 1092 +0.2 1022 +0.25 931 +0.3 853 +0.306 850 +0.309 838 +0.312 735 +0.318 435 +0.325 161 +0.329 0 +; diff --git a/datafiles/thrustcurves/AeroTech_I115.eng b/datafiles/thrustcurves/AeroTech_I115.eng new file mode 100644 index 000000000..08157b00e --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_I115.eng @@ -0,0 +1,26 @@ +; Aerotech I115W from TRA Cert Data +I115W 54 156 6-10-14-P 0.229 0.58 AT + 0.034 12.095 + 0.177 105.225 + 0.206 113.087 + 1.017 163.281 + 1.166 161.466 + 1.257 162.676 + 1.343 166.909 + 1.417 160.862 + 1.514 162.676 + 1.617 163.885 + 1.686 160.257 + 1.977 142.719 + 2.497 106.435 + 2.68 91.316 + 2.994 72.569 + 3.103 67.126 + 3.189 65.917 + 3.24 59.265 + 3.291 42.937 + 3.331 30.237 + 3.377 20.561 + 3.429 12.7 + 3.491 7.257 + 3.514 0.0 diff --git a/datafiles/thrustcurves/AeroTech_I117.eng b/datafiles/thrustcurves/AeroTech_I117.eng new file mode 100644 index 000000000..21068506e --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_I117.eng @@ -0,0 +1,33 @@ +; Aerotech I117FJ from TRA Cert Data +I117FJ 54 156 6-10-14-P 0.253 0.58 AT + 0.014 65.9 + 0.021 94.456 + 0.055 120.816 + 0.089 107.636 + 0.179 107.636 + 0.344 124.111 + 0.385 133.996 + 0.447 123.013 + 0.509 138.389 + 0.564 131.799 + 0.646 146.077 + 0.708 144.979 + 0.736 127.406 + 0.75 149.372 + 0.798 141.684 + 0.825 164.749 + 0.866 152.667 + 1.004 158.159 + 1.141 155.962 + 1.224 154.864 + 1.492 155.962 + 2.304 121.914 + 2.407 118.619 + 2.482 117.521 + 2.544 128.504 + 2.599 112.029 + 2.654 75.784 + 2.695 45.031 + 2.75 16.475 + 2.771 8.787 + 2.806 0.0 diff --git a/datafiles/thrustcurves/AeroTech_I1299.eng b/datafiles/thrustcurves/AeroTech_I1299.eng new file mode 100644 index 000000000..f698f302e --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_I1299.eng @@ -0,0 +1,24 @@ +;Entered by Jim Yehle +;from TRA cert document +I1299N 38 249 1000 0.192 0.422 AT-RMS +0 15.7171 +0.00361 222.5 +0.0115 1112 +0.0134228 1237.11 +0.02 1287 +0.04 1359 +0.1 1451 +0.12 1470 +0.18 1491 +0.2 1483 +0.22 1462 +0.24 1399 +0.28 1208 +0.294743 1131.63 +0.3 1065 +0.304251 974.46 +0.32 305 +0.330537 55.0098 +0.333893 11.7878 +0.34 0 +; diff --git a/datafiles/thrustcurves/AeroTech_I132.eng b/datafiles/thrustcurves/AeroTech_I132.eng new file mode 100644 index 000000000..453304c69 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_I132.eng @@ -0,0 +1,30 @@ +; AeroTech I132W +; Copyright Tripoli Motor Testing 1998 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +I132W 38 335 0 0.365568 0.512064 AT + 0.096 204.011 + 0.290 174.236 + 0.484 168.865 + 0.679 170.783 + 0.874 173.028 + 1.069 174.287 + 1.264 174.647 + 1.458 174.364 + 1.652 174.645 + 1.847 173.002 + 2.042 169.209 + 2.236 164.309 + 2.431 157.149 + 2.626 149.580 + 2.821 138.360 + 3.016 124.171 + 3.210 107.626 + 3.404 89.785 + 3.599 71.747 + 3.794 55.124 + 3.989 42.264 + 4.183 31.373 + 4.378 21.980 + 4.573 14.389 + 4.768 8.794 + 4.962 0.000 diff --git a/datafiles/thrustcurves/AeroTech_I154.eng b/datafiles/thrustcurves/AeroTech_I154.eng new file mode 100644 index 000000000..9624eaa62 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_I154.eng @@ -0,0 +1,30 @@ +; AeroTech I154J +; Copyright Tripoli Motor Testing 1999 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +I154J 38 250 0 0.25088 0.491904 AT + 0.066 120.409 + 0.199 150.638 + 0.332 151.666 + 0.466 156.806 + 0.599 150.331 + 0.732 150.602 + 0.866 145.101 + 0.999 144.469 + 1.133 145.159 + 1.268 145.912 + 1.401 141.710 + 1.534 142.828 + 1.668 141.187 + 1.801 140.970 + 1.934 137.832 + 2.068 128.417 + 2.202 122.339 + 2.336 111.986 + 2.470 105.295 + 2.603 96.602 + 2.736 90.469 + 2.870 57.427 + 3.003 20.489 + 3.136 4.707 + 3.271 2.966 + 3.405 0.000 diff --git a/datafiles/thrustcurves/AeroTech_I161.eng b/datafiles/thrustcurves/AeroTech_I161.eng new file mode 100644 index 000000000..948fbaed6 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_I161.eng @@ -0,0 +1,30 @@ +; AeroTech I161W +; Copyright Tripoli Motor Testing 1998 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +I161W 38 191 0 0.189952 0.370048 AT + 0.043 178.900 + 0.131 206.770 + 0.221 206.101 + 0.310 205.175 + 0.400 206.924 + 0.490 210.603 + 0.579 210.475 + 0.669 211.555 + 0.758 212.379 + 0.848 212.096 + 0.938 209.060 + 1.027 202.345 + 1.116 192.439 + 1.204 179.499 + 1.294 162.159 + 1.383 148.446 + 1.473 135.222 + 1.563 120.095 + 1.652 104.041 + 1.742 87.962 + 1.831 74.789 + 1.921 54.362 + 2.010 23.386 + 2.100 7.332 + 2.190 5.171 + 2.279 0.000 diff --git a/datafiles/thrustcurves/AeroTech_I195.eng b/datafiles/thrustcurves/AeroTech_I195.eng new file mode 100644 index 000000000..1033f0b82 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_I195.eng @@ -0,0 +1,30 @@ +; AeroTech I195J +; Copyright Tripoli Motor Testing 1996 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +I195J 38 298 10 0.3136 0.59136 AT + 0.050 258.670 + 0.152 353.638 + 0.254 300.655 + 0.356 265.354 + 0.458 266.338 + 0.560 283.233 + 0.662 332.442 + 0.765 283.040 + 0.867 230.795 + 0.969 222.867 + 1.071 217.091 + 1.173 210.600 + 1.275 202.722 + 1.377 192.671 + 1.479 182.571 + 1.581 171.964 + 1.683 162.238 + 1.785 148.138 + 1.888 130.259 + 1.990 107.022 + 2.092 80.230 + 2.194 51.074 + 2.296 26.313 + 2.398 10.397 + 2.500 3.977 + 2.602 0.000 diff --git a/datafiles/thrustcurves/AeroTech_I195_1.eng b/datafiles/thrustcurves/AeroTech_I195_1.eng new file mode 100644 index 000000000..2e8e0039a --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_I195_1.eng @@ -0,0 +1,30 @@ +; AeroTech I195J +; Copyright Tripoli Motor Testing 1999 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +I195J 38 297 0 0.296576 0.563136 AT + 0.033 190.099 + 0.103 354.046 + 0.173 393.473 + 0.243 414.842 + 0.314 379.747 + 0.383 364.640 + 0.453 364.776 + 0.524 357.242 + 0.594 355.802 + 0.664 355.644 + 0.734 353.557 + 0.804 339.941 + 0.874 309.753 + 0.944 275.017 + 1.014 243.739 + 1.084 218.135 + 1.154 197.291 + 1.224 173.680 + 1.295 147.000 + 1.365 116.506 + 1.434 83.105 + 1.505 51.011 + 1.575 26.480 + 1.645 13.927 + 1.716 7.273 + 1.786 0.000 diff --git a/datafiles/thrustcurves/AeroTech_I200.eng b/datafiles/thrustcurves/AeroTech_I200.eng new file mode 100644 index 000000000..bd7545fc8 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_I200.eng @@ -0,0 +1,30 @@ +; AeroTech I200W +; Copyright Tripoli Motor Testing 1999 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +I200W 29 333 0 0.181888 0.357504 AT + 0.033 303.951 + 0.103 273.452 + 0.174 276.061 + 0.245 271.625 + 0.316 268.233 + 0.386 258.449 + 0.457 252.480 + 0.528 246.642 + 0.599 242.304 + 0.670 237.737 + 0.741 234.769 + 0.811 233.171 + 0.882 230.660 + 0.953 224.985 + 1.024 221.658 + 1.095 214.548 + 1.166 177.365 + 1.236 154.208 + 1.307 119.146 + 1.378 91.586 + 1.449 65.330 + 1.520 32.877 + 1.591 28.702 + 1.661 22.211 + 1.732 15.558 + 1.803 0.000 diff --git a/datafiles/thrustcurves/AeroTech_I211.eng b/datafiles/thrustcurves/AeroTech_I211.eng new file mode 100644 index 000000000..238614cc4 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_I211.eng @@ -0,0 +1,30 @@ +; AeroTech I211W +; Copyright Tripoli Motor Testing 1999 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +I211W 38 240 0 0.247296 0.466368 AT + 0.044 257.326 + 0.134 295.533 + 0.226 296.087 + 0.318 298.204 + 0.408 295.082 + 0.499 287.669 + 0.591 282.578 + 0.682 272.875 + 0.773 266.997 + 0.864 257.602 + 0.955 250.495 + 1.047 238.574 + 1.138 228.571 + 1.228 215.135 + 1.320 198.047 + 1.411 180.631 + 1.502 161.261 + 1.593 146.708 + 1.684 134.484 + 1.776 101.241 + 1.867 52.688 + 1.957 35.461 + 2.049 24.321 + 2.141 11.165 + 2.232 4.587 + 2.324 0.000 diff --git a/datafiles/thrustcurves/AeroTech_I215.eng b/datafiles/thrustcurves/AeroTech_I215.eng new file mode 100644 index 000000000..3fa60574b --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_I215.eng @@ -0,0 +1,22 @@ +; Aerotech I215R from TRA Cert Data +I215R 54 156 6-10-14-P 0.20800000000000002 0.527 AT + 0.049 88.39 + 0.089 154.683 + 0.094 206.914 + 0.178 245.083 + 0.251 255.127 + 0.325 259.145 + 0.404 249.1 + 0.582 257.136 + 0.631 253.118 + 0.7 253.118 + 0.774 245.083 + 1.001 239.056 + 1.509 192.852 + 1.681 178.79 + 1.716 180.799 + 1.746 190.843 + 1.775 178.79 + 1.8 90.399 + 1.82 34.151 + 1.859 0.0 diff --git a/datafiles/thrustcurves/AeroTech_I218.eng b/datafiles/thrustcurves/AeroTech_I218.eng new file mode 100644 index 000000000..f9bf8c98b --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_I218.eng @@ -0,0 +1,29 @@ +; AeroTech I218R +; provided by ThrustCurve.org (www.thrustcurve.org) +I218R 38 191 0 0.19264 0.37184 AT + 0.027 136.078 + 0.088 275.030 + 0.148 280.998 + 0.208 284.371 + 0.268 284.037 + 0.327 279.311 + 0.388 277.791 + 0.448 276.309 + 0.509 269.384 + 0.570 266.041 + 0.630 261.907 + 0.690 256.366 + 0.750 250.565 + 0.810 242.206 + 0.870 234.607 + 0.930 225.488 + 0.991 216.166 + 1.053 205.415 + 1.112 193.238 + 1.173 177.206 + 1.233 161.304 + 1.292 139.118 + 1.353 96.082 + 1.413 38.848 + 1.474 5.978 + 1.535 0.000 diff --git a/datafiles/thrustcurves/AeroTech_I225.eng b/datafiles/thrustcurves/AeroTech_I225.eng new file mode 100644 index 000000000..ca0504263 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_I225.eng @@ -0,0 +1,28 @@ +; AeroTech I225FJ +; Curvefit to instruction sheet on Aerotech website (12/27/06) +; by Chris Kobel +; burn time: 1.8 seconds +; total impulse: 350.5 newton-seconds +; average thrust: 43.8 pounds +I225FJ 38 202 6-10-14 0.2417 0.486 AT + 0.04 213.6 + 0.10 213.6 + 0.20 218.1 + 0.28 235.9 + 0.30 249.2 + 0.40 262.6 + 0.50 267.0 + 0.60 271.5 + 0.70 275.9 + 0.80 275.9 + 0.87 271.5 + 0.90 258.1 + 1.00 240.3 + 1.10 218.1 + 1.20 200.3 + 1.30 178.0 + 1.40 160.2 + 1.50 97.9 + 1.60 40.1 + 1.70 13.4 + 1.80 0.0 diff --git a/datafiles/thrustcurves/AeroTech_I229.eng b/datafiles/thrustcurves/AeroTech_I229.eng new file mode 100644 index 000000000..5596f768a --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_I229.eng @@ -0,0 +1,25 @@ +; +I229T 54.0 156.00 6-10-14 0.20600 0.52000 AT + 0.01 44.73 + 0.02 216.65 + 0.19 244.72 + 0.40 266.64 + 0.49 266.64 + 0.52 273.66 + 0.59 271.90 + 0.75 272.78 + 0.84 268.40 + 0.88 271.90 + 0.97 262.26 + 1.00 265.76 + 1.07 255.24 + 1.21 249.10 + 1.51 219.28 + 1.60 230.68 + 1.63 191.21 + 1.64 132.44 + 1.66 86.83 + 1.70 44.73 + 1.71 21.05 + 1.73 0.00 +; diff --git a/datafiles/thrustcurves/AeroTech_I245.eng b/datafiles/thrustcurves/AeroTech_I245.eng new file mode 100644 index 000000000..6c0c3934b --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_I245.eng @@ -0,0 +1,30 @@ +;Ejection delays may not be corrrect. +;From Aerotech pre-cert data. +;Created 11/11/07 by Jim Yehle. +I245G 38 192.532 0-6-10-14 0.1813 0.365 Aerotech +0.0244989 234.061 +0.0550162 257.888 +0.0868597 368.567 +0.106904 382.335 +0.13363 390.808 +0.200445 405.635 +0.262806 410.931 +0.302895 411.99 +0.363029 408.813 +0.401294 398.43 +0.501114 363.271 +0.594655 320.907 +0.68932 278.355 +0.797327 212.879 +0.893204 181.477 +1.00647 154.187 +1.09061 133.72 +1.16036 120.737 +1.1804 122.856 +1.23625 106.43 +1.30421 75.0467 +1.3608 36.0094 +1.40312 19.0638 +1.43875 5.2955 +1.46325 0 +; diff --git a/datafiles/thrustcurves/AeroTech_I284.eng b/datafiles/thrustcurves/AeroTech_I284.eng new file mode 100644 index 000000000..54ffc690c --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_I284.eng @@ -0,0 +1,30 @@ +; AeroTech I284W +; Copyright Tripoli Motor Testing 1997 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +I284W 38 298 10 0.3136 0.55552 AT + 0.033 370.682 + 0.103 483.606 + 0.174 483.282 + 0.245 486.856 + 0.316 490.842 + 0.386 499.428 + 0.457 508.800 + 0.528 506.326 + 0.599 485.287 + 0.670 481.043 + 0.741 455.776 + 0.811 426.920 + 0.882 393.422 + 0.953 367.404 + 1.024 347.490 + 1.095 325.191 + 1.166 304.064 + 1.236 284.158 + 1.307 271.165 + 1.378 228.579 + 1.449 130.521 + 1.520 57.212 + 1.591 29.552 + 1.661 16.413 + 1.732 10.365 + 1.803 0.000 diff --git a/datafiles/thrustcurves/AeroTech_I284_1.eng b/datafiles/thrustcurves/AeroTech_I284_1.eng new file mode 100644 index 000000000..ee1909e29 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_I284_1.eng @@ -0,0 +1,30 @@ +; AeroTech I284W +; Copyright Tripoli Motor Testing 1999 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +I284W 38 297 0 0.310016 0.555072 AT + 0.041 422.031 + 0.125 448.597 + 0.210 459.029 + 0.295 451.940 + 0.379 439.556 + 0.465 427.370 + 0.549 407.558 + 0.633 399.734 + 0.719 380.049 + 0.803 368.042 + 0.887 352.020 + 0.973 342.102 + 1.057 325.767 + 1.142 306.936 + 1.227 292.029 + 1.311 267.283 + 1.396 251.784 + 1.481 227.534 + 1.566 210.504 + 1.650 168.299 + 1.735 110.789 + 1.820 71.036 + 1.904 32.505 + 1.990 17.537 + 2.075 7.317 + 2.160 0.000 diff --git a/datafiles/thrustcurves/AeroTech_I285.eng b/datafiles/thrustcurves/AeroTech_I285.eng new file mode 100644 index 000000000..306ebff09 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_I285.eng @@ -0,0 +1,29 @@ +; AeroTech I285R +; provided by ThrustCurve.org (www.thrustcurve.org) +I285R 38 250 0 0.25088 0.4928 AT + 0.027 171.405 + 0.088 325.573 + 0.148 341.697 + 0.208 358.916 + 0.268 373.706 + 0.327 373.966 + 0.388 368.442 + 0.448 367.497 + 0.507 361.900 + 0.568 351.928 + 0.628 346.109 + 0.687 340.993 + 0.749 329.382 + 0.810 321.625 + 0.870 310.856 + 0.930 295.955 + 0.990 283.704 + 1.050 269.655 + 1.110 253.419 + 1.170 240.222 + 1.230 224.116 + 1.290 204.118 + 1.350 118.730 + 1.410 23.483 + 1.471 2.046 + 1.532 0.000 diff --git a/datafiles/thrustcurves/AeroTech_I300.eng b/datafiles/thrustcurves/AeroTech_I300.eng new file mode 100644 index 000000000..b0249ef6b --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_I300.eng @@ -0,0 +1,20 @@ +; +; +I300T 38 250 6-10-14 0.2216 0.4405 AT +0 473.17 +0.1 395.68 +0.2 375.31 +0.3 367.14 +0.4 358.97 +0.5 346.72 +0.6 338.56 +0.7 318.19 +0.8 305.94 +0.9 295.35 +1.07 269.23 +1.1 258.01 +1.2 246.79 +1.3 179.49 +1.4 48.95 +1.5 13.91 +1.6 0 diff --git a/datafiles/thrustcurves/AeroTech_I305.eng b/datafiles/thrustcurves/AeroTech_I305.eng new file mode 100644 index 000000000..ac446a212 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_I305.eng @@ -0,0 +1,21 @@ +; I305FJ based on Aerotech instruction sheet by C. Kobel 3/30/07 +I305FJ 38 298 6-10-14 0.302 0.581 AT + 0.020 341.398 + 0.100 365.497 + 0.200 383.571 + 0.300 403.653 + 0.400 405.662 + 0.500 405.662 + 0.600 404.657 + 0.700 374.534 + 0.800 342.402 + 0.900 309.267 + 1.000 272.115 + 1.100 238.979 + 1.150 224.921 + 1.200 194.798 + 1.300 119.489 + 1.400 62.255 + 1.450 33.136 + 1.500 23.095 + 1.600 0.0 diff --git a/datafiles/thrustcurves/AeroTech_I357.eng b/datafiles/thrustcurves/AeroTech_I357.eng new file mode 100644 index 000000000..0115e4e24 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_I357.eng @@ -0,0 +1,30 @@ +; AeroTech I357T +; Copyright Tripoli Motor Testing 1997 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +I357T 38 203 14 0.1792 0.34944 AT + 0.028 311.629 + 0.087 351.768 + 0.147 349.074 + 0.206 346.175 + 0.266 341.229 + 0.325 336.857 + 0.384 333.748 + 0.444 326.960 + 0.503 319.679 + 0.563 312.533 + 0.622 300.790 + 0.681 292.787 + 0.741 283.766 + 0.800 274.578 + 0.859 264.915 + 0.919 254.273 + 0.978 241.755 + 1.037 229.020 + 1.097 216.238 + 1.156 187.776 + 1.216 109.940 + 1.275 56.459 + 1.334 24.476 + 1.394 10.977 + 1.454 3.450 + 1.515 0.000 diff --git a/datafiles/thrustcurves/AeroTech_I364.eng b/datafiles/thrustcurves/AeroTech_I364.eng new file mode 100644 index 000000000..c2fffb83a --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_I364.eng @@ -0,0 +1,25 @@ +; AeroTech I364FJ +; Curvefit to instruction sheet on Aerotech website (12/27/06) +; by Chris Kobel +; burn time: 1.7 seconds +; total impulse: 551.2 newton-seconds +; average thrust: 72.9 pounds +I364FJ 38 345 6-10-14 0.3625 0.678 AT + 0.02 356.0 + 0.10 373.8 + 0.20 387.2 + 0.30 400.5 + 0.40 400.5 + 0.50 409.4 + 0.60 413.9 + 0.70 409.4 + 0.80 382.7 + 0.90 373.8 + 1.00 351.6 + 1.10 333.8 + 1.20 320.4 + 1.30 311.5 + 1.40 244.8 + 1.50 178.0 + 1.60 80.1 + 1.70 0.0 diff --git a/datafiles/thrustcurves/AeroTech_I366.eng b/datafiles/thrustcurves/AeroTech_I366.eng new file mode 100644 index 000000000..324ddf140 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_I366.eng @@ -0,0 +1,29 @@ +; AeroTech I366R +; provided by ThrustCurve.org (www.thrustcurve.org) +I366R 38 298 0 0.3136 0.55552 AT + 0.027 323.256 + 0.088 485.393 + 0.148 483.744 + 0.208 479.926 + 0.268 473.365 + 0.327 466.192 + 0.388 457.444 + 0.448 448.751 + 0.509 441.477 + 0.570 430.236 + 0.630 421.524 + 0.690 411.757 + 0.750 398.876 + 0.810 387.496 + 0.870 375.430 + 0.930 361.325 + 0.991 345.057 + 1.053 330.392 + 1.112 312.636 + 1.173 293.508 + 1.233 275.085 + 1.292 262.408 + 1.353 230.881 + 1.413 118.008 + 1.474 23.611 + 1.535 0.000 diff --git a/datafiles/thrustcurves/AeroTech_I435.eng b/datafiles/thrustcurves/AeroTech_I435.eng new file mode 100644 index 000000000..7ae4a5427 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_I435.eng @@ -0,0 +1,30 @@ +; AeroTech I435T +; Copyright Tripoli Motor Testing 1996 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +I435T 38 298 6 0.28672 0.52864 AT + 0.026 684.626 + 0.080 702.334 + 0.134 655.130 + 0.190 638.942 + 0.245 624.098 + 0.299 611.802 + 0.354 602.601 + 0.409 590.237 + 0.464 575.712 + 0.519 563.654 + 0.574 548.912 + 0.628 527.885 + 0.683 504.211 + 0.739 480.412 + 0.793 459.219 + 0.848 436.771 + 0.903 414.493 + 0.957 392.151 + 1.012 366.634 + 1.068 299.670 + 1.122 182.639 + 1.177 106.457 + 1.232 55.447 + 1.286 23.628 + 1.342 11.052 + 1.397 0.000 diff --git a/datafiles/thrustcurves/AeroTech_I435_1.eng b/datafiles/thrustcurves/AeroTech_I435_1.eng new file mode 100644 index 000000000..54b43b152 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_I435_1.eng @@ -0,0 +1,30 @@ +; AeroTech I435T +; Copyright Tripoli Motor Testing 1999 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +I435T 38 297 0 0.26656 0.513408 AT + 0.024 808.049 + 0.074 749.691 + 0.124 709.215 + 0.174 656.216 + 0.224 636.578 + 0.274 621.839 + 0.324 592.267 + 0.374 584.551 + 0.424 573.277 + 0.474 547.725 + 0.524 539.962 + 0.574 525.268 + 0.624 500.456 + 0.674 484.978 + 0.724 464.323 + 0.774 442.837 + 0.824 424.540 + 0.874 405.872 + 0.924 393.443 + 0.974 317.157 + 1.024 217.630 + 1.074 126.188 + 1.124 74.391 + 1.174 30.034 + 1.224 9.380 + 1.274 0.000 diff --git a/datafiles/thrustcurves/AeroTech_I599.eng b/datafiles/thrustcurves/AeroTech_I599.eng new file mode 100644 index 000000000..f306c521e --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_I599.eng @@ -0,0 +1,37 @@ +; Aerotech I599N from TRA Cert Data +I599N 54 156 100 0.195 0.5200512 AT + 0.0070 179.424 + 0.01 495.908 + 0.012 578.144 + 0.014 647.92 + 0.017 797.44 + 0.024 593.096 + 0.032 647.92 + 0.045 677.824 + 0.051 702.744 + 0.055 677.824 + 0.062 720.188 + 0.076 707.728 + 0.12 752.584 + 0.202 757.568 + 0.225 755.076 + 0.241 735.14 + 0.25 720.188 + 0.263 730.156 + 0.329 722.68 + 0.399 697.76 + 0.45 667.856 + 0.501 618.016 + 0.536 585.62 + 0.55 580.636 + 0.564 585.62 + 0.578 632.968 + 0.581 555.716 + 0.584 426.132 + 0.589 299.04 + 0.595 176.932 + 0.598 112.14 + 0.604 57.316 + 0.608 24.92 + 0.619 12.46 + 0.623 0.0 diff --git a/datafiles/thrustcurves/AeroTech_I600.eng b/datafiles/thrustcurves/AeroTech_I600.eng new file mode 100644 index 000000000..8499b0c9a --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_I600.eng @@ -0,0 +1,23 @@ +; +;I600R Data Entered by Tim Van Milligan +;For RockSim: www.RockSim.com +;Based on Aerotech's Reload Kit Instruction Sheet. +;Not Officially Approved by TRA or Aerotech +I600R 38 344.68 6-10-14 0.3237 0.617 AT +0.005 40.438 +0.046 817.754 +0.059 813.261 +0.1 772.822 +0.2 736.877 +0.4 696.439 +0.5 669.48 +0.6 620.055 +0.796 539.178 +0.894 485.261 +0.951 453.809 +0.964 435.836 +1 274.082 +1.052 152.767 +1.106 62.904 +1.144 13.48 +1.18 0 diff --git a/datafiles/thrustcurves/AeroTech_I65.eng b/datafiles/thrustcurves/AeroTech_I65.eng new file mode 100644 index 000000000..3413084bb --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_I65.eng @@ -0,0 +1,30 @@ +; AeroTech I65W +; Copyright Tripoli Motor Testing 1997 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +I65W 54 235 0 0.41216 0.7616 AT + 0.180 125.414 + 0.544 139.304 + 0.908 145.369 + 1.273 148.283 + 1.638 146.745 + 2.002 139.049 + 2.367 131.200 + 2.731 123.276 + 3.096 113.454 + 3.460 102.368 + 3.825 90.210 + 4.190 78.084 + 4.554 66.812 + 4.919 55.780 + 5.283 47.281 + 5.648 39.154 + 6.012 32.528 + 6.377 27.069 + 6.742 22.099 + 7.106 18.095 + 7.471 14.819 + 7.835 12.097 + 8.200 9.763 + 8.565 7.875 + 8.929 5.999 + 9.294 0.000 diff --git a/datafiles/thrustcurves/AeroTech_J125.eng b/datafiles/thrustcurves/AeroTech_J125.eng new file mode 100644 index 000000000..fc4fac32e --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_J125.eng @@ -0,0 +1,30 @@ +; AeroTech J125 +; Copyright Tripoli Motor Testing 1997 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +J125 54 368 0 0.63392 1.288 AT + 0.174 223.931 + 0.525 254.842 + 0.877 275.347 + 1.229 285.163 + 1.581 280.333 + 1.933 264.476 + 2.285 244.373 + 2.638 223.774 + 2.990 204.720 + 3.342 185.434 + 3.694 166.807 + 4.046 147.653 + 4.398 127.914 + 4.750 108.483 + 5.102 92.582 + 5.454 77.817 + 5.806 63.844 + 6.158 53.017 + 6.510 44.507 + 6.862 37.543 + 7.215 32.205 + 7.567 27.212 + 7.919 22.847 + 8.271 18.596 + 8.623 14.790 + 8.975 0.000 diff --git a/datafiles/thrustcurves/AeroTech_J1299.eng b/datafiles/thrustcurves/AeroTech_J1299.eng new file mode 100644 index 000000000..cf0b52321 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_J1299.eng @@ -0,0 +1,39 @@ +; +; AT 54-852 +; Greg Gardner - 09/15/06 +J1299N 54 230 0 0.3716 0.834 AT +0.01 548 +0.02 1152 +0.03 1232 +0.04 1277 +0.05 1272 +0.06 1288 +0.07 1333 +0.08 1347 +0.09 1378 +0.10 1383 +0.12 1405 +0.14 1410 +0.16 1440 +0.18 1444 +0.20 1446 +0.25 1449 +0.30 1452 +0.35 1448 +0.40 1440 +0.45 1405 +0.50 1320 +0.55 1248 +0.57 1224 +0.59 1210 +0.60 1180 +0.61 1188 +0.615 1195 +0.62 1188 +0.63 510 +0.64 220 +0.65 96 +0.66 46 +0.67 26 +0.678 0 +; diff --git a/datafiles/thrustcurves/AeroTech_J135.eng b/datafiles/thrustcurves/AeroTech_J135.eng new file mode 100644 index 000000000..290b5e1b6 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_J135.eng @@ -0,0 +1,30 @@ +; AeroTech J135W +; Copyright Tripoli Motor Testing 1998 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +J135W 54 368 0 0.62272 1.14106 AT + 0.147 226.295 + 0.444 243.688 + 0.742 250.916 + 1.040 257.345 + 1.338 259.308 + 1.635 253.727 + 1.933 246.071 + 2.231 235.780 + 2.529 221.775 + 2.827 205.143 + 3.125 183.570 + 3.423 161.103 + 3.720 140.983 + 4.017 122.984 + 4.315 106.605 + 4.612 91.959 + 4.910 77.693 + 5.208 65.304 + 5.506 54.347 + 5.804 44.246 + 6.102 35.395 + 6.400 27.716 + 6.698 21.121 + 6.996 14.939 + 7.294 9.737 + 7.592 0.000 diff --git a/datafiles/thrustcurves/AeroTech_J145.eng b/datafiles/thrustcurves/AeroTech_J145.eng new file mode 100644 index 000000000..190eb139a --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_J145.eng @@ -0,0 +1,30 @@ +; AeroTech J145H +; Copyright Tripoli Motor Testing 1999 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +J145H 54 709 0 0.410816 1.79738 AT + 0.113 253.118 + 0.340 293.672 + 0.567 300.149 + 0.794 289.519 + 1.021 253.366 + 1.248 251.809 + 1.476 246.042 + 1.704 236.553 + 1.931 229.907 + 2.158 222.550 + 2.385 211.120 + 2.612 201.066 + 2.841 191.143 + 3.069 139.197 + 3.296 79.889 + 3.523 63.900 + 3.750 51.048 + 3.977 40.565 + 4.205 31.710 + 4.433 24.429 + 4.660 19.950 + 4.887 15.256 + 5.115 12.412 + 5.342 10.212 + 5.570 9.135 + 5.798 0.000 diff --git a/datafiles/thrustcurves/AeroTech_J180.eng b/datafiles/thrustcurves/AeroTech_J180.eng new file mode 100644 index 000000000..362f73bdf --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_J180.eng @@ -0,0 +1,30 @@ +; AeroTech J180T +; Copyright Tripoli Motor Testing 1998 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +J180T 54 230 0 0.429184 0.809088 AT + 0.093 301.634 + 0.281 313.236 + 0.470 313.710 + 0.658 308.334 + 0.847 300.100 + 1.035 290.743 + 1.224 278.867 + 1.412 263.823 + 1.601 245.974 + 1.790 226.651 + 1.978 207.345 + 2.167 187.053 + 2.355 168.339 + 2.544 149.993 + 2.732 133.094 + 2.921 116.330 + 3.109 100.088 + 3.298 84.507 + 3.486 70.453 + 3.675 57.263 + 3.864 44.453 + 4.052 33.340 + 4.241 24.654 + 4.429 17.964 + 4.619 12.391 + 4.808 0.000 diff --git a/datafiles/thrustcurves/AeroTech_J1999.eng b/datafiles/thrustcurves/AeroTech_J1999.eng new file mode 100644 index 000000000..b5777469b --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_J1999.eng @@ -0,0 +1,37 @@ +; +; AT 54-1280 +; Greg Gardner - 09/15/06 +J1999N 54 314 0 0.5574 1.111 AT +0.01 830 +0.02 1716 +0.03 1787 +0.04 1873 +0.05 1896 +0.06 1918 +0.07 1984 +0.08 2007 +0.09 2051 +0.10 2058 +0.12 2090 +0.14 2098 +0.16 2135 +0.18 2138 +0.20 2142 +0.25 2146 +0.30 2150 +0.35 2146 +0.40 2138 +0.45 2096 +0.50 1974 +0.55 1864 +0.57 1829 +0.59 1815 +0.60 1762 +0.61 1673 +0.62 1085 +0.63 490 +0.64 190 +0.65 81 +0.66 31 +0.67 0 +; diff --git a/datafiles/thrustcurves/AeroTech_J210.eng b/datafiles/thrustcurves/AeroTech_J210.eng new file mode 100644 index 000000000..2182361a8 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_J210.eng @@ -0,0 +1,16 @@ +; +; +J210H 54 609.6 100 0.471 1.497 Aerotech +0.00772798 651.819 +0.0695518 528.502 +0.200927 488.864 +0.502318 409.589 +0.996909 374.355 +1.4915 312.697 +1.59196 286.272 +2.00927 167.359 +2.43431 88.0836 +2.50386 101.296 +2.55023 74.8711 +3.02164 57.2543 +4 0 diff --git a/datafiles/thrustcurves/AeroTech_J250.eng b/datafiles/thrustcurves/AeroTech_J250.eng new file mode 100644 index 000000000..d39891cbb --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_J250.eng @@ -0,0 +1,24 @@ +; Aerotech J250FJ from TRA Cert Data +J250FJ 54 241 6-10-14-18 0.511 0.92 AT + 0.011 132.176 + 0.021 263.335 + 0.084 236.899 + 0.168 252.151 + 0.294 238.933 + 0.494 261.301 + 0.715 285.703 + 0.993 295.87 + 1.177 306.038 + 1.267 305.021 + 1.498 301.971 + 1.64 292.82 + 2.002 253.167 + 2.344 224.699 + 2.391 225.715 + 2.502 233.849 + 2.57 185.046 + 2.659 116.925 + 2.685 76.255 + 2.738 32.536 + 2.77 17.285 + 2.796 0.0 diff --git a/datafiles/thrustcurves/AeroTech_J260.eng b/datafiles/thrustcurves/AeroTech_J260.eng new file mode 100644 index 000000000..5f07d7f7e --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_J260.eng @@ -0,0 +1,14 @@ +; +; +J260HW 54 708.66 100 0.558 1.574 AT +0.00772798 598.969 +0.0386399 475.651 +0.108192 506.481 +0.463679 493.268 +0.780526 475.651 +1.01236 427.205 +2.00155 330.314 +2.48841 193.784 +2.99073 114.509 +4.01082 57.2543 +4.5 0 diff --git a/datafiles/thrustcurves/AeroTech_J275.eng b/datafiles/thrustcurves/AeroTech_J275.eng new file mode 100644 index 000000000..11aaa2b0a --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_J275.eng @@ -0,0 +1,30 @@ +; AeroTech J275W +; Copyright Tripoli Motor Testing 1998 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +J275W 54 230 0 0.468608 0.864192 AT + 0.075 239.740 + 0.227 289.133 + 0.380 299.773 + 0.533 312.721 + 0.686 323.878 + 0.840 332.165 + 0.992 336.422 + 1.145 335.110 + 1.298 329.538 + 1.451 325.343 + 1.604 309.980 + 1.756 292.901 + 1.909 275.732 + 2.063 257.341 + 2.216 234.891 + 2.369 213.102 + 2.521 182.501 + 2.674 167.853 + 2.827 153.041 + 2.980 138.115 + 3.133 105.605 + 3.285 67.369 + 3.439 29.239 + 3.592 14.599 + 3.745 6.662 + 3.898 0.000 diff --git a/datafiles/thrustcurves/AeroTech_J315.eng b/datafiles/thrustcurves/AeroTech_J315.eng new file mode 100644 index 000000000..594786403 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_J315.eng @@ -0,0 +1,29 @@ +; AeroTech J315R +; provided by ThrustCurve.org (www.thrustcurve.org) +J315R 54 243 0 0.42112 0.8512 AT + 0.051 189.719 + 0.154 337.529 + 0.259 354.534 + 0.363 364.111 + 0.468 371.479 + 0.572 373.222 + 0.676 376.062 + 0.780 372.962 + 0.884 368.988 + 0.989 366.978 + 1.093 358.752 + 1.197 351.302 + 1.301 339.336 + 1.406 325.202 + 1.510 311.322 + 1.614 300.496 + 1.718 288.598 + 1.822 278.279 + 1.927 270.538 + 2.031 262.127 + 2.136 245.027 + 2.239 236.238 + 2.344 188.308 + 2.448 63.668 + 2.552 18.746 + 2.657 0.000 diff --git a/datafiles/thrustcurves/AeroTech_J350.eng b/datafiles/thrustcurves/AeroTech_J350.eng new file mode 100644 index 000000000..59076b3ad --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_J350.eng @@ -0,0 +1,21 @@ +J350W-L 38 337 P 0.361 0.651 AT + 0.041 841.443 + 0.051 767.077 + 0.088 698.219 + 0.173 644.51 + 0.256 621.098 + 0.298 564.635 + 0.547 543.977 + 0.783 487.514 + 0.989 418.656 + 1.16 359.438 + 1.192 340.158 + 1.213 320.878 + 1.287 216.214 + 1.319 179.031 + 1.342 126.699 + 1.386 84.007 + 1.427 53.709 + 1.48 45.446 + 1.591 20.657 + 1.695 0.0 diff --git a/datafiles/thrustcurves/AeroTech_J350_1.eng b/datafiles/thrustcurves/AeroTech_J350_1.eng new file mode 100644 index 000000000..9fed7dbe5 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_J350_1.eng @@ -0,0 +1,31 @@ +; AeroTech J350W +; Copyright Tripoli Motor Testing 1998 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +J350W 38 337 0 0.375872 0.650944 AT + 0.038 706.781 + 0.115 669.055 + 0.192 602.539 + 0.270 565.084 + 0.348 539.143 + 0.425 514.910 + 0.503 483.098 + 0.581 449.128 + 0.658 437.256 + 0.736 424.199 + 0.815 414.461 + 0.892 402.956 + 0.970 393.604 + 1.048 377.837 + 1.125 359.785 + 1.203 341.916 + 1.281 324.721 + 1.358 305.935 + 1.436 264.279 + 1.515 175.471 + 1.592 110.912 + 1.670 77.100 + 1.748 55.472 + 1.825 39.990 + 1.903 26.276 + 1.981 0.000 +; diff --git a/datafiles/thrustcurves/AeroTech_J390.eng b/datafiles/thrustcurves/AeroTech_J390.eng new file mode 100644 index 000000000..7dee40e30 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_J390.eng @@ -0,0 +1,16 @@ +; +; +J390HW-TURBO 54 708.66 100 0.69 1.74 AT +0.015456 440.418 +0.100464 550.523 +0.193199 546.118 +0.301391 656.223 +0.502318 647.414 +0.973725 581.352 +1.48377 471.247 +1.98609 378.759 +2.17929 334.718 +2.30294 255.442 +2.49614 158.55 +3.01391 57.2543 +3.5 0 diff --git a/datafiles/thrustcurves/AeroTech_J415.eng b/datafiles/thrustcurves/AeroTech_J415.eng new file mode 100644 index 000000000..07aafc473 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_J415.eng @@ -0,0 +1,30 @@ +; AeroTech J415W +; Copyright Tripoli Motor Testing 1998 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +J415W 54 314 0 0.686336 1.15718 AT + 0.065 431.300 + 0.196 452.427 + 0.327 489.904 + 0.458 513.542 + 0.591 523.192 + 0.723 531.440 + 0.854 542.165 + 0.985 542.731 + 1.118 549.788 + 1.250 553.889 + 1.381 537.331 + 1.512 512.126 + 1.645 517.338 + 1.777 498.098 + 1.908 473.365 + 2.040 444.157 + 2.172 413.187 + 2.304 384.854 + 2.435 360.556 + 2.567 297.571 + 2.699 178.288 + 2.831 89.889 + 2.962 43.066 + 3.094 19.126 + 3.226 8.995 + 3.358 0.000 diff --git a/datafiles/thrustcurves/AeroTech_J420.eng b/datafiles/thrustcurves/AeroTech_J420.eng new file mode 100644 index 000000000..10da3b6af --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_J420.eng @@ -0,0 +1,29 @@ +; AeroTech J420R +; provided by ThrustCurve.org (www.thrustcurve.org) +J420R 38 337 0 0.37632 0.6496 AT + 0.031 61.083 + 0.095 563.470 + 0.160 525.283 + 0.224 521.242 + 0.288 527.371 + 0.352 537.088 + 0.418 535.138 + 0.481 534.623 + 0.545 530.245 + 0.610 526.447 + 0.674 517.203 + 0.738 510.279 + 0.802 500.887 + 0.868 479.450 + 0.931 460.675 + 0.995 438.594 + 1.060 409.647 + 1.124 383.454 + 1.188 361.024 + 1.252 339.741 + 1.318 319.194 + 1.381 296.714 + 1.445 195.191 + 1.510 61.984 + 1.575 7.220 + 1.640 0.000 diff --git a/datafiles/thrustcurves/AeroTech_J460.eng b/datafiles/thrustcurves/AeroTech_J460.eng new file mode 100644 index 000000000..55b27198b --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_J460.eng @@ -0,0 +1,30 @@ +; AeroTech J460T +; Copyright Tripoli Motor Testing 1998 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +J460T 54 230 0 0.413504 0.801024 AT + 0.041 500.927 + 0.125 509.423 + 0.209 516.357 + 0.294 527.752 + 0.379 535.135 + 0.464 541.858 + 0.548 545.793 + 0.633 545.678 + 0.718 544.832 + 0.802 540.278 + 0.887 533.698 + 0.972 526.340 + 1.056 511.003 + 1.141 492.475 + 1.225 474.977 + 1.310 457.021 + 1.395 437.203 + 1.479 418.093 + 1.565 403.240 + 1.649 339.173 + 1.733 203.861 + 1.819 102.620 + 1.903 49.295 + 1.987 9.538 + 2.073 2.155 + 2.158 0.000 diff --git a/datafiles/thrustcurves/AeroTech_J500.eng b/datafiles/thrustcurves/AeroTech_J500.eng new file mode 100644 index 000000000..4358a4be6 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_J500.eng @@ -0,0 +1,21 @@ +;Delays are speculation. +;Taken from Aerotech curves, not cert docs. +;Jim Yehle 15 Nov 07 +J500G 38 335.407 0-6-10-14 0.3626 0.654 Aerotech +0.0134378 40.2458 +0.0335946 724.425 +0.0403135 781.616 +0.0604703 787.971 +0.0895857 711.716 +0.134378 686.297 +0.394177 637.578 +0.575588 588.86 +0.606943 622.751 +0.633819 620.633 +1.20045 360.094 +1.24076 345.267 +1.31019 182.165 +1.38186 65.6642 +1.43337 23.3002 +1.45 0 +; diff --git a/datafiles/thrustcurves/AeroTech_J540.eng b/datafiles/thrustcurves/AeroTech_J540.eng new file mode 100644 index 000000000..58cd385d4 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_J540.eng @@ -0,0 +1,29 @@ +; AeroTech J540R +; provided by ThrustCurve.org (www.thrustcurve.org) +J540R 54 314 0 0.61376 1.08416 AT + 0.044 498.757 + 0.134 639.617 + 0.224 649.317 + 0.314 657.966 + 0.404 664.020 + 0.494 666.924 + 0.584 663.699 + 0.675 658.398 + 0.765 651.232 + 0.855 638.505 + 0.945 626.396 + 1.035 612.557 + 1.126 590.090 + 1.216 562.391 + 1.306 536.875 + 1.396 511.607 + 1.486 490.354 + 1.576 468.978 + 1.667 451.342 + 1.758 430.180 + 1.847 414.549 + 1.937 398.116 + 2.027 305.877 + 2.118 55.541 + 2.208 1.523 + 2.299 0.000 diff --git a/datafiles/thrustcurves/AeroTech_J570.eng b/datafiles/thrustcurves/AeroTech_J570.eng new file mode 100644 index 000000000..aa796a99a --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_J570.eng @@ -0,0 +1,30 @@ +; AeroTech J570W +; Copyright Tripoli Motor Testing 1998 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +J570W 38 479 0 0.547904 0.886144 AT + 0.039 1149.795 + 0.119 1042.846 + 0.199 960.891 + 0.279 900.020 + 0.360 837.772 + 0.441 792.834 + 0.521 735.510 + 0.602 685.857 + 0.682 649.599 + 0.762 608.757 + 0.844 597.350 + 0.924 568.934 + 1.004 548.552 + 1.084 505.080 + 1.165 484.626 + 1.246 452.328 + 1.326 362.439 + 1.406 297.973 + 1.487 262.381 + 1.568 195.696 + 1.648 156.733 + 1.729 124.649 + 1.809 113.749 + 1.890 69.812 + 1.971 46.023 + 2.052 0.000 diff --git a/datafiles/thrustcurves/AeroTech_J575.eng b/datafiles/thrustcurves/AeroTech_J575.eng new file mode 100644 index 000000000..e059cc390 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_J575.eng @@ -0,0 +1,26 @@ +; +J575FJ 38 478.79 6-10-14 0.576 0.91424 Aerotech +0.0156556 656.682 +0.0195695 840.689 +0.037182 840.689 +0.0606654 839.001 +0.101761 839.001 +0.162427 839.001 +0.228963 839.001 +0.315068 839.001 +0.399217 837.312 +0.459883 837.312 +0.547945 822.119 +0.60274 801.862 +0.700587 742.777 +0.802348 685.381 +0.841487 646.554 +0.902153 573.964 +0.949791 483.69 +1 319.581 +1.05365 220.66 +1.12916 153.62 +1.19961 99.5997 +1.27593 43.8914 +1.34 0 +; diff --git a/datafiles/thrustcurves/AeroTech_J800.eng b/datafiles/thrustcurves/AeroTech_J800.eng new file mode 100644 index 000000000..9e6bed3c5 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_J800.eng @@ -0,0 +1,30 @@ +; AeroTech J800T +; Copyright Tripoli Motor Testing 1998 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +J800T 54 314 0 0.613312 1.08595 AT + 0.040 841.341 + 0.121 818.497 + 0.203 776.386 + 0.285 784.308 + 0.367 785.314 + 0.449 783.315 + 0.531 782.539 + 0.612 779.977 + 0.695 773.680 + 0.777 765.307 + 0.858 755.517 + 0.941 744.777 + 1.023 733.131 + 1.105 719.947 + 1.187 702.235 + 1.269 685.369 + 1.351 668.265 + 1.433 650.327 + 1.515 630.472 + 1.597 615.483 + 1.679 470.262 + 1.760 256.617 + 1.843 108.716 + 1.925 15.005 + 2.007 1.249 + 2.090 0.000 diff --git a/datafiles/thrustcurves/AeroTech_J825.eng b/datafiles/thrustcurves/AeroTech_J825.eng new file mode 100644 index 000000000..ef08bdf24 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_J825.eng @@ -0,0 +1,15 @@ +; +;Aerotech J825R +J825R 38 479 10 0.53 .88 AT +0.0 11.504 +0.048913 1069.87 +0.100155 977.842 +0.118789 1035.36 +0.652174 897.314 +0.801242 839.794 +0.899068 782.274 +0.999224 586.705 +1.09938 103.536 +1.14363 23.008 +1.18 0.0 +; diff --git a/datafiles/thrustcurves/AeroTech_J90.eng b/datafiles/thrustcurves/AeroTech_J90.eng new file mode 100644 index 000000000..ee8767e4a --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_J90.eng @@ -0,0 +1,30 @@ +; AeroTech J90W +; Copyright Tripoli Motor Testing 1998 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +J90W 54 243 0 0.427392 0.852544 AT + 0.143 116.187 + 0.430 165.444 + 0.718 176.536 + 1.005 184.645 + 1.293 187.242 + 1.580 183.651 + 1.868 175.492 + 2.155 167.687 + 2.443 156.858 + 2.730 143.514 + 3.018 128.856 + 3.305 110.879 + 3.593 94.003 + 3.880 79.657 + 4.168 67.472 + 4.455 57.268 + 4.743 48.008 + 5.030 40.523 + 5.318 33.901 + 5.605 28.248 + 5.893 23.334 + 6.180 19.275 + 6.468 15.923 + 6.755 12.727 + 7.044 9.903 + 7.332 0.000 diff --git a/datafiles/thrustcurves/AeroTech_K1050.eng b/datafiles/thrustcurves/AeroTech_K1050.eng new file mode 100644 index 000000000..2b394c591 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_K1050.eng @@ -0,0 +1,30 @@ +; AeroTech K1050W +; Copyright Tripoli Motor Testing 1998 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +K1050W 54 676 0 1.34714 2.12845 AT + 0.049 1305.649 + 0.149 1270.386 + 0.249 1288.922 + 0.349 1327.059 + 0.449 1345.719 + 0.549 1359.794 + 0.649 1364.452 + 0.749 1365.493 + 0.849 1377.189 + 0.949 1379.519 + 1.049 1346.586 + 1.149 1286.742 + 1.249 1232.101 + 1.349 1186.480 + 1.449 1156.521 + 1.549 1120.045 + 1.649 1098.708 + 1.749 1070.186 + 1.849 889.885 + 1.949 646.691 + 2.049 441.213 + 2.149 302.245 + 2.249 155.001 + 2.349 52.187 + 2.449 43.415 + 2.549 0.000 diff --git a/datafiles/thrustcurves/AeroTech_K1100.eng b/datafiles/thrustcurves/AeroTech_K1100.eng new file mode 100644 index 000000000..0a0920b8a --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_K1100.eng @@ -0,0 +1,29 @@ +; AeroTech K1100T +; Copyright Tripoli Motor Testing 1998 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +K1100T 54 398 0 0.7616 1.32518 AT + 0.034 1234.653 + 0.105 1233.429 + 0.176 1192.393 + 0.247 1163.041 + 0.318 1147.963 + 0.389 1146.319 + 0.460 1140.958 + 0.532 1132.640 + 0.603 1123.824 + 0.674 1108.921 + 0.745 1090.974 + 0.816 1073.937 + 0.887 1049.133 + 0.959 1021.216 + 1.030 994.559 + 1.101 966.571 + 1.172 940.194 + 1.243 909.792 + 1.315 880.264 + 1.386 844.477 + 1.457 643.599 + 1.528 401.861 + 1.599 145.498 + 1.670 28.372 + 1.742 0.000 diff --git a/datafiles/thrustcurves/AeroTech_K1275.eng b/datafiles/thrustcurves/AeroTech_K1275.eng new file mode 100644 index 000000000..490f512dd --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_K1275.eng @@ -0,0 +1,29 @@ +; AeroTech K1275R +; provided by ThrustCurve.org (www.thrustcurve.org) +K1275R 54 569 0 1.29024 2.03392 AT + 0.039 1282.616 + 0.119 1557.989 + 0.199 1540.196 + 0.279 1526.782 + 0.359 1500.693 + 0.440 1456.584 + 0.520 1425.794 + 0.600 1390.416 + 0.680 1355.109 + 0.761 1323.311 + 0.841 1282.825 + 0.921 1247.795 + 1.001 1194.417 + 1.081 1150.977 + 1.162 1108.223 + 1.242 1068.754 + 1.322 1036.922 + 1.403 997.444 + 1.482 964.569 + 1.563 933.305 + 1.644 889.992 + 1.724 599.467 + 1.804 134.559 + 1.884 5.630 + 1.964 0.205 + 2.045 0.000 diff --git a/datafiles/thrustcurves/AeroTech_K1499.eng b/datafiles/thrustcurves/AeroTech_K1499.eng new file mode 100644 index 000000000..49ebe4c2d --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_K1499.eng @@ -0,0 +1,13 @@ +;Entered by Jim Yehle +;from TRA cert document +K1499N 75 260 1000 0.604 1.741 AT-RMS +0.01 1450 +0.2 1720.12 +0.35 1700 +0.5 1600 +0.6 1575 +0.7 1500 +0.82 1400 +0.84 250 +0.88 0 +; diff --git a/datafiles/thrustcurves/AeroTech_K185.eng b/datafiles/thrustcurves/AeroTech_K185.eng new file mode 100644 index 000000000..08793ec9e --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_K185.eng @@ -0,0 +1,30 @@ +; AeroTech K185W +; Copyright Tripoli Motor Testing 1998 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +K185W 54 437 0 0.827008 1.43405 AT + 0.150 279.128 + 0.452 308.220 + 0.754 328.435 + 1.056 338.929 + 1.359 339.677 + 1.663 333.166 + 1.965 321.891 + 2.267 309.687 + 2.570 293.260 + 2.873 271.536 + 3.175 247.174 + 3.477 216.883 + 3.780 186.951 + 4.083 161.096 + 4.385 138.113 + 4.688 117.749 + 4.991 99.372 + 5.294 82.759 + 5.596 68.426 + 5.898 55.126 + 6.201 44.162 + 6.504 34.209 + 6.806 25.064 + 7.108 16.880 + 7.411 9.200 + 7.715 0.000 diff --git a/datafiles/thrustcurves/AeroTech_K1999.eng b/datafiles/thrustcurves/AeroTech_K1999.eng new file mode 100644 index 000000000..5136824d0 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_K1999.eng @@ -0,0 +1,21 @@ +; AeroTech K1999N +; Curvefit to instruction sheet on Aerotech website (1/29/07) +; by Chris Kobel +; burn time: 1.4 seconds +; total impulse: 2522 newton-seconds +; average thrust: 405 pounds +K1999N 98 289 6-10-14-18 1.195 2.989 AT + 0.02 1557.5 + 0.08 1780.0 + 0.10 1913.5 + 0.12 1869.0 + 0.18 2002.5 + 1.08 2002.5 + 1.10 1958.0 + 1.20 1780.0 + 1.25 1557.5 + 1.27 1335.0 + 1.31 890.0 + 1.33 667.5 + 1.35 222.5 + 1.40 0.0 diff --git a/datafiles/thrustcurves/AeroTech_K250.eng b/datafiles/thrustcurves/AeroTech_K250.eng new file mode 100644 index 000000000..bcc73d2e2 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_K250.eng @@ -0,0 +1,30 @@ +; AeroTech K250W +; Copyright Tripoli Motor Testing 1998 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +K250W 54 673 0 1.52902 2.21133 AT + 0.199 365.330 + 0.599 403.324 + 0.999 418.669 + 1.400 409.813 + 1.801 408.949 + 2.201 412.146 + 2.602 411.952 + 3.003 409.488 + 3.403 393.214 + 3.804 373.599 + 4.205 348.913 + 4.605 328.463 + 5.006 307.163 + 5.407 281.467 + 5.807 249.011 + 6.208 217.159 + 6.609 185.908 + 7.009 149.190 + 7.410 119.808 + 7.811 92.096 + 8.211 69.726 + 8.613 52.613 + 9.014 35.876 + 9.414 16.727 + 9.815 4.086 + 10.216 0.000 diff --git a/datafiles/thrustcurves/AeroTech_K270.eng b/datafiles/thrustcurves/AeroTech_K270.eng new file mode 100644 index 000000000..2cc906906 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_K270.eng @@ -0,0 +1,35 @@ +; Aerotech K270W-P Moon Burner from TRA Certification Data +K270W 54 579 P 1.188 2.1 AT + 0.046 177.061 + 0.062 292.932 + 0.092 425.727 + 0.154 414.01 + 0.277 389.273 + 0.446 377.556 + 0.585 381.462 + 0.738 372.349 + 1.0 377.556 + 1.154 376.254 + 1.231 378.858 + 1.308 395.783 + 1.4 380.16 + 1.569 399.689 + 1.615 381.462 + 1.846 381.462 + 2.369 368.443 + 2.415 381.462 + 2.554 360.631 + 3.015 350.216 + 3.354 328.083 + 3.723 300.743 + 4.0 273.403 + 4.6 225.232 + 5.262 175.759 + 5.677 144.513 + 6.0 124.984 + 6.538 89.832 + 7.015 66.398 + 8.0 22.133 + 8.323 10.415 + 8.508 5.208 + 8.692 0.0 diff --git a/datafiles/thrustcurves/AeroTech_K458.eng b/datafiles/thrustcurves/AeroTech_K458.eng new file mode 100644 index 000000000..ffcf9af5c --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_K458.eng @@ -0,0 +1,30 @@ +; AeroTech K458W +; Copyright Tripoli Motor Testing 1998 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +K458W 98 275 0 1.42778 3.16378 AT + 0.133 294.911 + 0.403 404.808 + 0.674 462.021 + 0.944 515.863 + 1.214 555.072 + 1.484 583.153 + 1.755 600.299 + 2.025 610.254 + 2.295 618.543 + 2.566 623.155 + 2.835 618.885 + 3.105 589.082 + 3.376 546.307 + 3.647 505.042 + 3.917 451.412 + 4.186 391.651 + 4.457 338.409 + 4.727 288.429 + 4.997 245.814 + 5.268 208.209 + 5.539 178.153 + 5.808 149.825 + 6.078 62.931 + 6.349 8.427 + 6.620 2.562 + 6.891 0.000 diff --git a/datafiles/thrustcurves/AeroTech_K485.eng b/datafiles/thrustcurves/AeroTech_K485.eng new file mode 100644 index 000000000..3c808ec6f --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_K485.eng @@ -0,0 +1,30 @@ +; AeroTech K485HW +; Copyright Tripoli Motor Testing 1999 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +K485HW 54 699 0 0.910784 2.22029 AT + 0.075 454.453 + 0.227 568.735 + 0.380 831.332 + 0.533 825.584 + 0.686 795.935 + 0.840 759.473 + 0.992 727.238 + 1.145 680.051 + 1.298 653.091 + 1.451 627.316 + 1.604 601.548 + 1.756 576.270 + 1.909 542.033 + 2.063 479.078 + 2.216 394.184 + 2.369 346.719 + 2.521 307.435 + 2.674 276.291 + 2.827 216.608 + 2.980 146.021 + 3.133 106.838 + 3.285 81.226 + 3.439 52.105 + 3.592 37.385 + 3.745 29.462 + 3.898 0.000 diff --git a/datafiles/thrustcurves/AeroTech_K550.eng b/datafiles/thrustcurves/AeroTech_K550.eng new file mode 100644 index 000000000..cd8254ba7 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_K550.eng @@ -0,0 +1,30 @@ +; AeroTech K550W +; Copyright Tripoli Motor Testing 1998 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +K550W 54 410 0 0.919744 1.48736 AT + 0.065 604.264 + 0.196 642.625 + 0.327 682.197 + 0.458 732.995 + 0.591 758.236 + 0.723 780.289 + 0.854 794.452 + 0.985 797.939 + 1.117 797.601 + 1.249 773.842 + 1.381 711.608 + 1.512 646.522 + 1.644 590.724 + 1.775 537.505 + 1.907 491.012 + 2.040 445.836 + 2.171 401.461 + 2.302 364.291 + 2.433 319.614 + 2.566 255.577 + 2.698 172.573 + 2.829 103.501 + 2.960 51.795 + 3.092 26.814 + 3.224 15.203 + 3.356 0.000 diff --git a/datafiles/thrustcurves/AeroTech_K560.eng b/datafiles/thrustcurves/AeroTech_K560.eng new file mode 100644 index 000000000..bc7912eeb --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_K560.eng @@ -0,0 +1,30 @@ +; AeroTech K560W +; Copyright Tripoli Motor Testing 1998 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +K560W 75 396 0 1.40806 2.71354 AT + 0.096 552.123 + 0.290 645.403 + 0.484 681.109 + 0.679 716.167 + 0.874 742.678 + 1.069 764.778 + 1.264 775.710 + 1.458 785.859 + 1.653 789.305 + 1.848 789.077 + 2.043 744.622 + 2.237 676.886 + 2.432 614.711 + 2.627 557.908 + 2.822 503.641 + 3.017 455.504 + 3.211 412.045 + 3.406 372.963 + 3.601 335.987 + 3.796 307.346 + 3.991 279.856 + 4.185 223.491 + 4.380 70.441 + 4.575 10.028 + 4.770 2.445 + 4.965 0.000 diff --git a/datafiles/thrustcurves/AeroTech_K650.eng b/datafiles/thrustcurves/AeroTech_K650.eng new file mode 100644 index 000000000..ac0c45c24 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_K650.eng @@ -0,0 +1,30 @@ +; AeroTech K650T +; Copyright Tripoli Motor Testing 1998 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +K650T 98 289 0 1.27008 2.9353 AT + 0.079 514.338 + 0.240 594.264 + 0.401 618.849 + 0.563 641.658 + 0.723 665.057 + 0.884 686.488 + 1.046 704.685 + 1.206 720.215 + 1.368 730.072 + 1.529 736.891 + 1.690 743.109 + 1.851 747.503 + 2.013 747.557 + 2.174 744.081 + 2.335 732.294 + 2.496 710.412 + 2.657 682.670 + 2.819 653.246 + 2.979 627.020 + 3.141 595.456 + 3.302 563.844 + 3.463 551.080 + 3.624 236.059 + 3.785 1.383 + 3.947 1.234 + 4.108 0.000 diff --git a/datafiles/thrustcurves/AeroTech_K680.eng b/datafiles/thrustcurves/AeroTech_K680.eng new file mode 100644 index 000000000..c92ed34c3 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_K680.eng @@ -0,0 +1,21 @@ +; +;Aerotech K680R RASP engine file +;Data Entered by Tim Van Milligan +;Source: TRA Certification paperwork, and +;Aerotech's instruction sheet: RMS 98/2560-10240 REDLINE. +K680R 98 289 100 1.316 3.035 AT +0.085 629.798 +0.494 717.881 +0.996 797.157 +1.29 819.178 +1.506 819.178 +2.001 775.136 +2.519 673.84 +2.99 563.735 +3.137 541.714 +3.176 532.906 +3.238 563.735 +3.276 563.735 +3.408 52.85 +3.431 22.02 +3.49 0 diff --git a/datafiles/thrustcurves/AeroTech_K695.eng b/datafiles/thrustcurves/AeroTech_K695.eng new file mode 100644 index 000000000..5932925a9 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_K695.eng @@ -0,0 +1,29 @@ +; AeroTech K695R +; provided by ThrustCurve.org (www.thrustcurve.org) +K695R 54 410 0 0.9184 1.48736 AT + 0.044 618.611 + 0.134 727.840 + 0.224 751.996 + 0.314 812.480 + 0.404 900.125 + 0.495 884.763 + 0.585 873.457 + 0.675 864.561 + 0.765 849.672 + 0.856 838.886 + 0.946 822.550 + 1.036 806.240 + 1.126 781.342 + 1.216 753.973 + 1.307 728.472 + 1.398 697.629 + 1.487 672.979 + 1.578 646.660 + 1.667 620.897 + 1.758 595.574 + 1.849 571.720 + 1.939 546.822 + 2.029 272.824 + 2.119 57.950 + 2.209 4.509 + 2.300 0.000 diff --git a/datafiles/thrustcurves/AeroTech_K700.eng b/datafiles/thrustcurves/AeroTech_K700.eng new file mode 100644 index 000000000..4155a3b4c --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_K700.eng @@ -0,0 +1,30 @@ +; AeroTech K700W +; Copyright Tripoli Motor Testing 1998 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +K700W 54 568 0 1.29158 2.03526 AT + 0.069 1005.472 + 0.209 1018.916 + 0.350 1026.610 + 0.491 1028.637 + 0.632 1029.337 + 0.773 1004.203 + 0.914 970.694 + 1.055 946.516 + 1.196 918.437 + 1.336 873.783 + 1.478 821.276 + 1.619 773.270 + 1.759 735.553 + 1.900 692.732 + 2.041 658.984 + 2.182 626.737 + 2.323 591.431 + 2.464 508.666 + 2.605 420.175 + 2.746 328.309 + 2.886 202.409 + 3.028 121.672 + 3.169 80.453 + 3.309 50.873 + 3.451 31.548 + 3.593 0.000 diff --git a/datafiles/thrustcurves/AeroTech_K780.eng b/datafiles/thrustcurves/AeroTech_K780.eng new file mode 100644 index 000000000..cfda61d1a --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_K780.eng @@ -0,0 +1,29 @@ +; AeroTech K780R +; provided by ThrustCurve.org (www.thrustcurve.org) +K780R 75 289 0 1.26784 2.9344 AT + 0.053 383.290 + 0.173 718.241 + 0.292 849.343 + 0.413 885.503 + 0.533 903.243 + 0.652 924.403 + 0.772 938.825 + 0.892 938.623 + 1.013 947.130 + 1.133 953.578 + 1.253 944.001 + 1.373 935.448 + 1.495 929.447 + 1.617 920.379 + 1.737 897.293 + 1.857 888.917 + 1.977 861.127 + 2.098 840.971 + 2.217 812.360 + 2.337 779.614 + 2.457 747.866 + 2.578 726.819 + 2.697 729.258 + 2.817 279.891 + 2.940 10.969 + 3.063 0.000 diff --git a/datafiles/thrustcurves/AeroTech_K828.eng b/datafiles/thrustcurves/AeroTech_K828.eng new file mode 100644 index 000000000..2c073f6b3 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_K828.eng @@ -0,0 +1,30 @@ +; +K828FJ 54.0 579.00 6-10-14-18 1.45000 2.25500 AT + 0.01 1112.06 + 0.02 1238.60 + 0.04 1303.79 + 0.06 1135.06 + 0.08 1077.54 + 0.13 1031.53 + 0.20 1016.19 + 0.50 993.18 + 0.65 1004.68 + 1.00 985.51 + 1.08 974.01 + 1.19 974.01 + 1.42 954.83 + 1.51 935.66 + 1.69 912.65 + 1.75 885.81 + 1.83 893.48 + 1.89 843.63 + 1.95 774.60 + 2.00 667.23 + 2.15 444.82 + 2.20 364.29 + 2.23 260.76 + 2.27 184.06 + 2.33 111.21 + 2.39 49.85 + 2.50 0.00 +; diff --git a/datafiles/thrustcurves/AeroTech_L1120.eng b/datafiles/thrustcurves/AeroTech_L1120.eng new file mode 100644 index 000000000..639635ad0 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_L1120.eng @@ -0,0 +1,30 @@ +; AeroTech L1120W +; Copyright Tripoli Motor Testing 1998 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +L1120W 75 665 0 2.75699 4.65786 AT + 0.097 1377.215 + 0.293 1442.670 + 0.489 1496.986 + 0.685 1537.057 + 0.882 1554.962 + 1.078 1554.131 + 1.275 1547.973 + 1.472 1533.465 + 1.668 1510.342 + 1.865 1472.279 + 2.061 1362.534 + 2.257 1245.425 + 2.454 1148.864 + 2.651 1062.680 + 2.847 984.952 + 3.044 916.169 + 3.241 831.929 + 3.436 766.450 + 3.633 698.978 + 3.830 562.966 + 4.026 384.579 + 4.223 227.654 + 4.420 105.078 + 4.616 56.339 + 4.813 21.712 + 5.009 0.000 diff --git a/datafiles/thrustcurves/AeroTech_L1150.eng b/datafiles/thrustcurves/AeroTech_L1150.eng new file mode 100644 index 000000000..32059ad5e --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_L1150.eng @@ -0,0 +1,29 @@ +; AeroTech L1150 +; provided by ThrustCurve.org (www.thrustcurve.org) +L1150 75 531 0 2.06528 3.6736 AT + 0.053 935.855 + 0.175 1292.642 + 0.300 1260.926 + 0.425 1241.482 + 0.550 1257.058 + 0.675 1272.287 + 0.800 1287.605 + 0.925 1301.012 + 1.048 1309.708 + 1.170 1308.417 + 1.295 1304.830 + 1.420 1285.265 + 1.545 1267.657 + 1.670 1255.624 + 1.795 1227.212 + 1.920 1202.443 + 2.043 1182.617 + 2.165 1150.712 + 2.290 1117.909 + 2.415 1081.739 + 2.540 1037.547 + 2.665 1007.091 + 2.790 1008.911 + 2.915 643.124 + 3.040 64.371 + 3.165 0.000 diff --git a/datafiles/thrustcurves/AeroTech_L1300.eng b/datafiles/thrustcurves/AeroTech_L1300.eng new file mode 100644 index 000000000..ec9fc0228 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_L1300.eng @@ -0,0 +1,14 @@ +; +; +L1300R 98 443 100 2.508 4.884 AT +0.0231839 1299.23 +0.502318 1332.26 +0.996909 1497.42 +1.49923 1552.47 +1.99382 1508.43 +2.49614 1354.29 +2.99845 1101.05 +3.12983 1090.03 +3.21484 1145.09 +3.3694 176.167 +3.5 0 diff --git a/datafiles/thrustcurves/AeroTech_L1420.eng b/datafiles/thrustcurves/AeroTech_L1420.eng new file mode 100644 index 000000000..022b1eff5 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_L1420.eng @@ -0,0 +1,14 @@ +; +; +L1420R 75 443 100 2.56 4.562 AT +0.0386399 1332.26 +0.123648 1563.48 +0.502318 1519.44 +0.996909 1574.49 +1.49923 1662.58 +2.00155 1574.49 +2.48068 1409.34 +2.92117 1299.23 +2.99073 1167.11 +3.11437 187.178 +3.24 0 diff --git a/datafiles/thrustcurves/AeroTech_L1500.eng b/datafiles/thrustcurves/AeroTech_L1500.eng new file mode 100644 index 000000000..ffb10578f --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_L1500.eng @@ -0,0 +1,30 @@ +; AeroTech L1500T +; Copyright Tripoli Motor Testing 1998 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +L1500T 98 443 0 2.464 4.6592 AT + 0.073 1320.328 + 0.222 1454.823 + 0.372 1508.992 + 0.522 1556.781 + 0.672 1602.407 + 0.822 1642.004 + 0.971 1670.099 + 1.120 1694.804 + 1.270 1701.295 + 1.420 1704.286 + 1.570 1701.008 + 1.720 1694.550 + 1.869 1683.861 + 2.018 1659.694 + 2.168 1620.161 + 2.318 1570.033 + 2.468 1517.933 + 2.618 1463.319 + 2.767 1400.991 + 2.916 1331.420 + 3.066 1279.479 + 3.216 1108.987 + 3.366 217.788 + 3.516 10.579 + 3.666 3.245 + 3.816 0.000 diff --git a/datafiles/thrustcurves/AeroTech_L850.eng b/datafiles/thrustcurves/AeroTech_L850.eng new file mode 100644 index 000000000..fee1d2ad4 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_L850.eng @@ -0,0 +1,30 @@ +; AeroTech L850W +; Copyright Tripoli Motor Testing 1998 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +L850W 75 531 0 2.06528 3.67315 AT + 0.091 1015.926 + 0.274 1064.942 + 0.458 1101.366 + 0.643 1143.358 + 0.827 1170.928 + 1.011 1184.795 + 1.196 1178.044 + 1.380 1177.598 + 1.564 1174.910 + 1.748 1170.021 + 1.932 1113.716 + 2.117 1042.586 + 2.301 972.795 + 2.485 908.071 + 2.670 844.471 + 2.854 773.595 + 3.039 714.046 + 3.222 649.095 + 3.406 597.341 + 3.591 557.444 + 3.775 422.233 + 3.959 200.739 + 4.144 79.411 + 4.328 43.959 + 4.513 14.862 + 4.697 0.000 diff --git a/datafiles/thrustcurves/AeroTech_L952.eng b/datafiles/thrustcurves/AeroTech_L952.eng new file mode 100644 index 000000000..9afeb32a7 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_L952.eng @@ -0,0 +1,30 @@ +; AeroTech L952W +; Copyright Tripoli Motor Testing 1998 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +L952W 98 427 0 2.73011 5.01222 AT + 0.141 679.073 + 0.425 801.562 + 0.709 848.474 + 0.994 913.345 + 1.278 981.614 + 1.562 1043.690 + 1.847 1088.114 + 2.131 1112.556 + 2.416 1121.541 + 2.700 1118.573 + 2.984 1100.665 + 3.269 1039.140 + 3.553 965.784 + 3.837 876.793 + 4.122 780.693 + 4.406 693.903 + 4.691 608.030 + 4.975 528.335 + 5.259 463.528 + 5.544 405.769 + 5.828 358.367 + 6.112 279.009 + 6.397 99.897 + 6.681 20.108 + 6.967 3.317 + 7.252 0.000 diff --git a/datafiles/thrustcurves/AeroTech_M1297.eng b/datafiles/thrustcurves/AeroTech_M1297.eng new file mode 100644 index 000000000..87452973e --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_M1297.eng @@ -0,0 +1,35 @@ +; +; Aerotech M1297W +; Greg Gardner - 12/20/04 +M1297W 75 665 0 2.722 4.637 AT +0.10 1433.4 +0.15 1789.3 +0.20 1922.8 +0.25 1869.4 +0.30 1856.0 +0.35 1833.8 +0.40 1767.0 +0.50 1722.6 +0.60 1709.2 +0.90 1700.3 +1.00 1688.1 +1.50 1678.7 +1.75 1634.6 +1.85 1622.3 +1.95 1572.8 +2.00 1554.0 +2.50 1346.5 +3.00 1136.0 +3.20 1053.3 +3.25 1044.1 +3.35 1032.0 +3.38 1020.0 +3.40 937.0 +3.50 738.0 +3.60 545.0 +3.75 393.0 +4.00 226.0 +4.25 94.0 +4.35 45.0 +4.40 0.0 +; diff --git a/datafiles/thrustcurves/AeroTech_M1315.eng b/datafiles/thrustcurves/AeroTech_M1315.eng new file mode 100644 index 000000000..cb47989c7 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_M1315.eng @@ -0,0 +1,30 @@ +; AeroTech M1315W +; Copyright Tripoli Motor Testing 1999 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +M1315W 75 801 0 3.4496 5.6448 AT + 0.116 1728.683 + 0.349 1673.336 + 0.582 1686.810 + 0.816 1696.068 + 1.049 1663.167 + 1.282 1631.243 + 1.516 1620.471 + 1.749 1619.702 + 1.982 1621.042 + 2.216 1615.320 + 2.449 1567.089 + 2.682 1493.722 + 2.916 1420.079 + 3.149 1358.660 + 3.382 1292.507 + 3.616 1224.806 + 3.849 1171.995 + 4.082 928.809 + 4.316 577.949 + 4.549 395.445 + 4.782 314.006 + 5.016 228.273 + 5.249 159.803 + 5.482 118.348 + 5.716 109.782 + 5.949 0.000 diff --git a/datafiles/thrustcurves/AeroTech_M1419.eng b/datafiles/thrustcurves/AeroTech_M1419.eng new file mode 100644 index 000000000..89319c7c4 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_M1419.eng @@ -0,0 +1,30 @@ +; AeroTech M1419W +; Copyright Tripoli Motor Testing 1998 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +M1419W 98 579 0 4.032 6.91622 AT + 0.154 1154.896 + 0.465 1241.151 + 0.776 1300.224 + 1.087 1358.364 + 1.399 1411.033 + 1.710 1461.033 + 2.022 1485.747 + 2.333 1503.653 + 2.644 1513.113 + 2.955 1511.947 + 3.267 1492.438 + 3.578 1418.368 + 3.890 1326.608 + 4.201 1219.222 + 4.513 1087.648 + 4.824 937.068 + 5.135 810.066 + 5.446 709.130 + 5.757 624.701 + 6.069 557.223 + 6.380 437.806 + 6.692 252.076 + 7.003 107.741 + 7.315 19.973 + 7.626 0.515 + 7.937 0.000 diff --git a/datafiles/thrustcurves/AeroTech_M1550.eng b/datafiles/thrustcurves/AeroTech_M1550.eng new file mode 100644 index 000000000..75211ccfd --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_M1550.eng @@ -0,0 +1,29 @@ +; AeroTech M1550R +; provided by ThrustCurve.org (www.thrustcurve.org) +M1550R 75 800 0 3.4496 5.6448 AT + 0.069 1720.759 + 0.212 2125.329 + 0.358 1995.947 + 0.501 1908.442 + 0.645 1868.713 + 0.790 1835.504 + 0.935 1808.662 + 1.079 1796.300 + 1.222 1785.423 + 1.368 1773.153 + 1.511 1746.590 + 1.655 1715.709 + 1.800 1689.633 + 1.945 1660.720 + 2.089 1633.277 + 2.232 1606.038 + 2.378 1570.222 + 2.521 1534.714 + 2.665 1503.345 + 2.810 1461.317 + 2.955 1427.572 + 3.099 1393.229 + 3.242 939.955 + 3.388 268.504 + 3.532 4.985 + 3.677 0.000 diff --git a/datafiles/thrustcurves/AeroTech_M1600.eng b/datafiles/thrustcurves/AeroTech_M1600.eng new file mode 100644 index 000000000..f14584762 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_M1600.eng @@ -0,0 +1,29 @@ +; AeroTech M1600R +; provided by ThrustCurve.org (www.thrustcurve.org) +M1600R 98 579 0 4.032 6.91712 AT + 0.088 1370.361 + 0.268 1626.628 + 0.448 1672.654 + 0.628 1720.596 + 0.808 1763.287 + 0.987 1801.282 + 1.167 1829.825 + 1.348 1845.146 + 1.529 1856.370 + 1.710 1850.089 + 1.890 1847.370 + 2.070 1829.454 + 2.250 1810.982 + 2.430 1784.910 + 2.610 1754.267 + 2.790 1726.898 + 2.971 1689.288 + 3.152 1641.579 + 3.332 1581.589 + 3.513 1511.036 + 3.692 1431.400 + 3.872 1361.032 + 4.053 1234.566 + 4.232 621.206 + 4.414 42.471 + 4.595 0.000 diff --git a/datafiles/thrustcurves/AeroTech_M1850.eng b/datafiles/thrustcurves/AeroTech_M1850.eng new file mode 100644 index 000000000..6ee44babb --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_M1850.eng @@ -0,0 +1,28 @@ +; +;75-7680 case +; Greg Gardner - 10/25/07 +M1850W 75 935 0 3.979 6.871 AT +0.1 2411 +0.2 2135 +0.3 2015 +0.4 2000 +0.5 2055 +1.0 2098 +1.5 1860 +2.0 1788 +2.5 1659 +3.0 1468 +3.25 1423 +3.35 1334 +3.5 1201 +3.75 934 +3.8 930 +4.0 881 +4.25 600 +4.5 468 +4.75 400 +5.0 290 +5.5 85 +6.0 23 +6.5 0 +; diff --git a/datafiles/thrustcurves/AeroTech_M1939.eng b/datafiles/thrustcurves/AeroTech_M1939.eng new file mode 100644 index 000000000..c01290803 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_M1939.eng @@ -0,0 +1,30 @@ +; AeroTech M1939W +; Copyright Tripoli Motor Testing 1998 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +M1939W 98 732 0 5.656 8.98822 AT + 0.134 1905.185 + 0.406 2021.155 + 0.679 2095.900 + 0.952 2158.087 + 1.225 2198.211 + 1.498 2219.694 + 1.770 2228.643 + 2.042 2229.881 + 2.315 2225.641 + 2.587 2211.713 + 2.860 2164.724 + 3.133 2047.014 + 3.405 1916.238 + 3.677 1805.664 + 3.950 1658.489 + 4.223 1497.704 + 4.496 1339.452 + 4.769 1213.061 + 5.041 1102.130 + 5.313 966.508 + 5.585 670.253 + 5.858 443.975 + 6.131 155.355 + 6.404 41.358 + 6.677 5.775 + 6.950 0.000 diff --git a/datafiles/thrustcurves/AeroTech_M2000.eng b/datafiles/thrustcurves/AeroTech_M2000.eng new file mode 100644 index 000000000..e2bf3ec0e --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_M2000.eng @@ -0,0 +1,29 @@ +; AeroTech M2000R +; provided by ThrustCurve.org (www.thrustcurve.org) +M2000R 98 732 0 5.65824 8.98688 AT + 0.091 1530.959 + 0.279 2186.270 + 0.466 2166.698 + 0.655 2187.237 + 0.844 2219.069 + 1.031 2248.071 + 1.220 2273.743 + 1.409 2298.306 + 1.596 2309.753 + 1.785 2315.708 + 1.974 2316.158 + 2.161 2306.313 + 2.350 2282.230 + 2.539 2252.104 + 2.726 2209.638 + 2.915 2168.800 + 3.104 2117.175 + 3.291 2067.533 + 3.480 2004.508 + 3.669 1934.442 + 3.856 1831.480 + 4.045 1745.634 + 4.234 1504.269 + 4.421 649.796 + 4.610 58.178 + 4.799 0.000 diff --git a/datafiles/thrustcurves/AeroTech_M2400.eng b/datafiles/thrustcurves/AeroTech_M2400.eng new file mode 100644 index 000000000..7487ded06 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_M2400.eng @@ -0,0 +1,30 @@ +; AeroTech M2400T +; Copyright Tripoli Motor Testing 1998 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +M2400T 98 597 0 3.65254 6.4512 AT + 0.070 2441.945 + 0.211 2495.460 + 0.353 2556.133 + 0.495 2601.596 + 0.636 2637.660 + 0.778 2660.804 + 0.920 2676.486 + 1.061 2687.081 + 1.203 2695.807 + 1.345 2694.493 + 1.486 2684.268 + 1.628 2667.289 + 1.771 2629.961 + 1.914 2578.923 + 2.055 2522.074 + 2.197 2461.704 + 2.339 2393.518 + 2.480 2303.939 + 2.622 2201.610 + 2.764 2097.461 + 2.905 2010.409 + 3.047 1275.776 + 3.189 418.836 + 3.330 17.586 + 3.473 3.669 + 3.616 0.000 diff --git a/datafiles/thrustcurves/AeroTech_M2500.eng b/datafiles/thrustcurves/AeroTech_M2500.eng new file mode 100644 index 000000000..8847c58a0 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_M2500.eng @@ -0,0 +1,30 @@ +; AeroTech M2500T +; Copyright Tripoli Motor Testing 1998 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +M2500T 98 751 0 4.6592 8.064 AT + 0.082 2651.855 + 0.249 2780.285 + 0.416 2820.733 + 0.583 2843.010 + 0.751 2847.765 + 0.918 2851.215 + 1.084 2854.737 + 1.252 2861.690 + 1.420 2858.088 + 1.586 2851.086 + 1.754 2844.622 + 1.922 2830.855 + 2.089 2804.711 + 2.255 2765.796 + 2.423 2710.509 + 2.591 2648.262 + 2.757 2586.910 + 2.925 2520.794 + 3.093 2462.217 + 3.259 2419.937 + 3.426 1894.936 + 3.594 808.043 + 3.761 282.403 + 3.928 97.876 + 4.096 24.492 + 4.264 0.000 diff --git a/datafiles/thrustcurves/AeroTech_M650.eng b/datafiles/thrustcurves/AeroTech_M650.eng new file mode 100644 index 000000000..38c027ec2 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_M650.eng @@ -0,0 +1,25 @@ +; +;75-6400 case +; Greg Gardner - 10/25/07 +M650W 75 801 0 3.351 5.125 AT +0.08 1240 +0.12 1328 +0.25 1230 +0.5 1142 +1.0 1071 +1.5 1048 +2.0 1018 +2.5 982 +3.0 950 +3.5 853 +4.0 781 +5.0 595 +6.0 443 +7.0 297 +8.0 155 +9.0 88 +10.0 32 +10.5 12 +11.0 4 +11.5 0 +; diff --git a/datafiles/thrustcurves/AeroTech_M750.eng b/datafiles/thrustcurves/AeroTech_M750.eng new file mode 100644 index 000000000..903d0a1ae --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_M750.eng @@ -0,0 +1,24 @@ +; +;98-10240 case +; Greg Gardner - 10/25/07 +M750W 98 732 0 5.3 8.776 AT +0.1 1032 +0.2 992 +0.3 974 +0.48 966 +1.0 1055 +1.5 1152 +2.0 1192 +2.5 1218 +4.0 1103 +6.0 818 +8.0 561 +10.0 318 +11.0 216 +12.0 125 +13.0 76 +14.0 47 +15.0 23 +15.5 9 +16.0 0 +; diff --git a/datafiles/thrustcurves/AeroTech_M845.eng b/datafiles/thrustcurves/AeroTech_M845.eng new file mode 100644 index 000000000..a7e8c6992 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_M845.eng @@ -0,0 +1,16 @@ +; +; +M845HW 98 795.02 100 3.569 6.833 AT +0.015456 1332.26 +0.0463679 1706.62 +0.0772798 1178.12 +0.185471 1310.24 +0.973725 1222.16 +1.51468 1200.14 +1.97836 1123.07 +3.97218 1057 +4.20402 880.836 +6.01236 627.596 +6.495 418.397 +7.017 99.0941 +7.5 0 diff --git a/datafiles/thrustcurves/AeroTech_N2000.eng b/datafiles/thrustcurves/AeroTech_N2000.eng new file mode 100644 index 000000000..52788cf53 --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_N2000.eng @@ -0,0 +1,30 @@ +; AeroTech N2000W +; Copyright Tripoli Motor Testing 1997 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +N2000W 98 1046 0 7.66707 12.2828 AT + 0.146 2775.075 + 0.446 2831.810 + 0.746 2834.354 + 1.046 2829.564 + 1.346 2777.650 + 1.646 2688.252 + 1.950 2597.973 + 2.254 2501.043 + 2.554 2415.747 + 2.854 2343.624 + 3.154 2262.579 + 3.454 2178.182 + 3.758 2104.164 + 4.062 2024.475 + 4.362 1935.616 + 4.663 1839.781 + 4.962 1756.910 + 5.262 1351.806 + 5.567 954.556 + 5.871 681.831 + 6.171 475.910 + 6.471 361.124 + 6.771 194.633 + 7.071 44.938 + 7.375 6.030 + 7.679 0.000 diff --git a/datafiles/thrustcurves/AeroTech_N4800.eng b/datafiles/thrustcurves/AeroTech_N4800.eng new file mode 100644 index 000000000..b9db5111e --- /dev/null +++ b/datafiles/thrustcurves/AeroTech_N4800.eng @@ -0,0 +1,29 @@ +; AeroTech N4800T +; provided by ThrustCurve.org (www.thrustcurve.org) +N4800T 98 1194 0 9.7664 14.784 AT + 0.098 4752.717 + 0.301 6007.533 + 0.506 5594.225 + 0.710 5270.361 + 0.914 5150.120 + 1.119 5108.054 + 1.324 5086.206 + 1.528 5031.651 + 1.731 4941.811 + 1.936 4800.400 + 2.140 4664.876 + 2.344 4527.840 + 2.549 4401.003 + 2.754 4263.565 + 2.958 4120.406 + 3.161 3971.136 + 3.366 3876.421 + 3.570 3916.232 + 3.774 3913.510 + 3.979 3312.758 + 4.184 1649.267 + 4.388 523.361 + 4.591 327.209 + 4.796 251.041 + 5.001 128.177 + 5.206 0.000 diff --git a/datafiles/thrustcurves/Apogee_1/2A2.eng b/datafiles/thrustcurves/Apogee_1/2A2.eng new file mode 100644 index 000000000..7471c9d0e --- /dev/null +++ b/datafiles/thrustcurves/Apogee_1/2A2.eng @@ -0,0 +1,39 @@ +; +;Apogee 1/2A2 RASP.ENG file made from NAR published data +;File produced September 4, 2000 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +1/2A2 11 57 2-4-6 0.0015 0.0044 Apogee +0.007 0.19 +0.045 1.494 +0.078 3.152 +0.088 3.805 +0.093 3.805 +0.1 3.97 +0.105 3.696 +0.11 3.071 +0.117 2.554 +0.123 2.582 +0.132 2.31 +0.163 2.146 +0.2 1.984 +0.242 1.902 +0.253 2.01 +0.275 1.929 +0.342 1.929 +0.403 1.929 +0.41 1.848 +0.42 1.902 +0.467 1.902 +0.528 1.929 +0.565 1.929 +0.58 1.902 +0.593 1.848 +0.603 1.657 +0.61 1.141 +0.615 0.597 +0.622 0.244 +0.63 0 diff --git a/datafiles/thrustcurves/Apogee_1/4A2.eng b/datafiles/thrustcurves/Apogee_1/4A2.eng new file mode 100644 index 000000000..3c66820b9 --- /dev/null +++ b/datafiles/thrustcurves/Apogee_1/4A2.eng @@ -0,0 +1,30 @@ +;Apogee 1/4A2 RASP.ENG file made from NAR published data +;File produced September 4, 2000 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +1/4A2 11 38 2-4 0.0008 0.0036 Apogee +0.007 0.162 +0.023 0.65 +0.041 1.463 +0.058 2.519 +0.074 3.738 +0.079 3.9 +0.088 4.915 +0.097 5.119 +0.106 5.4 +0.11 5.119 +0.118 3.981 +0.125 3.656 +0.132 3.453 +0.136 3.209 +0.151 3.169 +0.156 2.966 +0.168 2.884 +0.18 2.397 +0.194 1.625 +0.207 1.056 +0.218 0.406 +0.23 0 diff --git a/datafiles/thrustcurves/Apogee_A2.eng b/datafiles/thrustcurves/Apogee_A2.eng new file mode 100644 index 000000000..00e958569 --- /dev/null +++ b/datafiles/thrustcurves/Apogee_A2.eng @@ -0,0 +1,35 @@ +; +;Apogee A2 RASP.ENG file made from NAR published data +;File produced September 4, 2000 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +A2 11 58 0-3-5-7 0.003 0.0067 Apogee +0.014 0.241 +0.036 0.895 +0.064 2.618 +0.1 4.82 +0.111 4.133 +0.125 2.687 +0.139 2.307 +0.185 2.031 +0.296 1.928 +0.481 1.825 +0.517 1.722 +0.538 1.791 +0.649 1.688 +0.748 1.757 +0.869 1.825 +1.04 1.894 +1.101 1.894 +1.119 1.825 +1.144 1.928 +1.229 1.859 +1.265 1.894 +1.283 1.757 +1.29 1.412 +1.293 0.688 +1.3 0.275 +1.31 0 diff --git a/datafiles/thrustcurves/Apogee_B2.eng b/datafiles/thrustcurves/Apogee_B2.eng new file mode 100644 index 000000000..50c2fd7f6 --- /dev/null +++ b/datafiles/thrustcurves/Apogee_B2.eng @@ -0,0 +1,35 @@ +; +;Apogee B2 RASP.ENG file made from NAR published data +;File produced September 4, 2000 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +B2 11 88 0-3-5-7-9 0.006 0.0106 Apogee +0.057 1.637 +0.093 4.091 +0.121 5.48 +0.143 4.787 +0.157 3.478 +0.207 2.578 +0.328 2.087 +0.371 2.087 +0.406 1.882 +0.641 1.841 +0.869 1.841 +1.283 1.882 +1.361 1.882 +1.397 1.718 +1.439 1.841 +1.532 1.718 +1.71 1.841 +1.888 1.882 +2.095 1.8 +2.23 1.8 +2.295 1.677 +2.423 1.759 +2.444 1.637 +2.466 0.982 +2.494 0.327 +2.53 0 diff --git a/datafiles/thrustcurves/Apogee_B7.eng b/datafiles/thrustcurves/Apogee_B7.eng new file mode 100644 index 000000000..30c41d60b --- /dev/null +++ b/datafiles/thrustcurves/Apogee_B7.eng @@ -0,0 +1,37 @@ +; +;Apogee B7 RASP.ENG file made from NAR published data +;File produced September 4, 2000 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +B7 13 50 4-6-8-10 0.0028 0.0091 Apogee +0.007 5.708 +0.013 7.211 +0.032 6.111 +0.045 8.116 +0.056 7.717 +0.069 9.02 +0.078 12.122 +0.087 14.76 +0.106 13.832 +0.117 13.733 +0.125 12.636 +0.155 12.438 +0.168 11.836 +0.2 11.243 +0.209 11.737 +0.219 10.739 +0.266 9.846 +0.29 9.849 +0.299 8.949 +0.367 7.456 +0.393 7.159 +0.429 5.761 +0.487 4.567 +0.571 2.975 +0.607 2.178 +0.669 1.084 +0.708 0.489 +0.74 0 diff --git a/datafiles/thrustcurves/Apogee_C10.eng b/datafiles/thrustcurves/Apogee_C10.eng new file mode 100644 index 000000000..416c05123 --- /dev/null +++ b/datafiles/thrustcurves/Apogee_C10.eng @@ -0,0 +1,33 @@ +; +;Apogee C10 RASP.ENG file made from NAR published data +;File produced September 4, 2000 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +C10 18 50 4-7-10 0.0049 0.0176 Apogee +0.01 2.712 +0.019 5.842 +0.029 17.116 +0.037 25.72 +0.051 22.535 +0.07 20.446 +0.106 18.983 +0.164 17.085 +0.188 17.085 +0.2 15.824 +0.216 16.036 +0.255 15.602 +0.293 14.35 +0.343 13.503 +0.394 12.655 +0.41 11.605 +0.434 11.605 +0.521 9.287 +0.631 6.34 +0.741 4.021 +0.851 2.119 +0.911 1.48 +0.945 1.264 +0.96 0 diff --git a/datafiles/thrustcurves/Apogee_C4.eng b/datafiles/thrustcurves/Apogee_C4.eng new file mode 100644 index 000000000..af5490906 --- /dev/null +++ b/datafiles/thrustcurves/Apogee_C4.eng @@ -0,0 +1,37 @@ +; +;Apogee C4 RASP.ENG file made from NAR published data +;File produced September 4, 2000 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +C4 18 50 3-5-7 0.0045 0.017 Apogee +0.018 3.23 +0.041 6.874 +0.147 8.779 +0.294 10.683 +0.365 11.31 +0.388 10.521 +0.412 8.779 +0.441 7.04 +0.465 4.555 +0.529 3.479 +0.629 2.981 +0.653 3.23 +0.718 2.816 +0.853 2.733 +1.065 2.65 +1.253 2.567 +1.453 2.401 +1.694 2.484 +1.794 2.484 +1.812 2.733 +1.841 2.401 +1.947 2.401 +2.112 2.401 +2.235 2.401 +2.282 2.236 +2.312 1.656 +2.329 0.662 +2.35 0 diff --git a/datafiles/thrustcurves/Apogee_C6.eng b/datafiles/thrustcurves/Apogee_C6.eng new file mode 100644 index 000000000..62cfe2415 --- /dev/null +++ b/datafiles/thrustcurves/Apogee_C6.eng @@ -0,0 +1,41 @@ +; +;Apogee C6 RASP.ENG file made from NAR published data +;File produced September 4, 2000 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +C6 13 83 4-7-10 0.007 0.0151 Apogee +0.008 13.958 +0.016 21.1 +0.022 15.511 +0.03 12.831 +0.052 14.8 +0.081 15.927 +0.092 14.658 +0.114 16.069 +0.125 14.658 +0.136 15.369 +0.168 14.8 +0.214 13.816 +0.225 12.973 +0.247 13.958 +0.252 12.831 +0.285 12.547 +0.307 12.405 +0.317 12.831 +0.328 11.562 +0.347 11.988 +0.393 11.42 +0.442 10.719 +0.464 11.136 +0.488 9.164 +0.545 8.459 +0.624 7.754 +0.716 6.485 +0.838 5.075 +0.977 3.102 +1.096 1.833 +1.207 0.986 +1.32 0 diff --git a/datafiles/thrustcurves/Apogee_D10.eng b/datafiles/thrustcurves/Apogee_D10.eng new file mode 100644 index 000000000..80d4ff933 --- /dev/null +++ b/datafiles/thrustcurves/Apogee_D10.eng @@ -0,0 +1,41 @@ +; +;Apogee D10 RASP.ENG file made from NAR published data +;File produced September 4, 2000 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +D10 18 70 3-5-7 0.0098 0.0259 Apogee +0.011 14.506 +0.018 25.13 +0.032 20.938 +0.079 19.065 +0.122 21.139 +0.136 19.686 +0.169 21.139 +0.201 20.728 +0.223 21.76 +0.233 20.938 +0.255 21.97 +0.276 20.938 +0.352 20.728 +0.402 20.107 +0.42 20.728 +0.459 20.107 +0.488 20.517 +0.556 18.243 +0.671 15.959 +0.707 14.717 +0.729 15.127 +0.779 12.853 +0.793 13.474 +0.836 11.401 +0.904 10.158 +0.926 10.569 +0.99 8.083 +1.026 8.498 +1.123 6.011 +1.231 2.487 +1.342 0.829 +1.4 0 diff --git a/datafiles/thrustcurves/Apogee_D3.eng b/datafiles/thrustcurves/Apogee_D3.eng new file mode 100644 index 000000000..5c23b4076 --- /dev/null +++ b/datafiles/thrustcurves/Apogee_D3.eng @@ -0,0 +1,27 @@ +; +;Apogee D3 RASP.ENG file made from NAR published data +;File produced September 4, 2000 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +D3 18 77 3-5-7 0.0098 0.0249 Apogee +0.05 6.79 +0.168 8.788 +0.318 10.46 +0.385 10.07 +0.402 7.909 +0.469 5.432 +0.486 3.914 +0.687 3.115 +1.122 2.876 +2.06 2.636 +3.349 2.397 +4.639 2.156 +5.727 1.997 +6.163 1.837 +6.263 3.994 +6.347 2.317 +6.364 0.719 +6.39 0 diff --git a/datafiles/thrustcurves/Apogee_E6.eng b/datafiles/thrustcurves/Apogee_E6.eng new file mode 100644 index 000000000..75d54c701 --- /dev/null +++ b/datafiles/thrustcurves/Apogee_E6.eng @@ -0,0 +1,28 @@ +; +;Aerotech E6 RASP.ENG file made from NAR published data +;File produced July 4, 2000 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +E6 24 70 2-4-6-8-100 0.0215 0.0463 Apogee +0.056 18.59 +0.112 20.12 +0.168 17.575 +0.307 14.38 +0.531 10.45 +0.894 7.696 +1.146 6.244 +1.691 5.808 +2.836 5.663 +3.898 5.517 +4.275 5.227 +4.415 4.937 +5.058 5.082 +5.519 5.227 +5.603 6.679 +5.729 3.921 +5.882 2.323 +5.966 1.016 +6.06 0 diff --git a/datafiles/thrustcurves/Apogee_F10.eng b/datafiles/thrustcurves/Apogee_F10.eng new file mode 100644 index 000000000..66c2154b3 --- /dev/null +++ b/datafiles/thrustcurves/Apogee_F10.eng @@ -0,0 +1,36 @@ +; +;Aerotech F10 RASP.ENG file made from NAR published data +;File produced July 4, 2000 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +F10 29 93 4-6-8 0.0407 0.0841 Apogee +0.015 28.22 +0.077 26.082 +0.201 24.934 +0.31 22.806 +0.464 20.183 +0.573 17.886 +0.789 16.075 +1.068 13.946 +1.393 12.63 +1.718 11.155 +2.166 9.844 +2.677 9.515 +3.311 9.187 +3.683 8.859 +3.791 9.679 +4.101 9.679 +4.658 9.515 +5.168 9.023 +5.725 9.023 +6.112 8.531 +6.329 8.859 +6.499 7.546 +6.685 5.742 +6.778 4.921 +6.917 2.625 +7.025 1.312 +7.13 0 diff --git a/datafiles/thrustcurves/Cesaroni_G60.eng b/datafiles/thrustcurves/Cesaroni_G60.eng new file mode 100644 index 000000000..d4218fade --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_G60.eng @@ -0,0 +1,30 @@ +; Cesaroni G60 +; Copyright Tripoli Motor Testing 2002 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +G60 38 125 0 0.077056 0.2016 CSR + 0.043 65.216 + 0.130 74.906 + 0.218 84.596 + 0.305 83.963 + 0.393 81.982 + 0.480 81.956 + 0.568 81.138 + 0.655 80.530 + 0.743 79.923 + 0.830 78.867 + 0.918 76.675 + 1.005 75.118 + 1.094 73.732 + 1.182 71.315 + 1.270 68.781 + 1.357 66.853 + 1.445 65.111 + 1.532 63.526 + 1.620 61.229 + 1.707 59.249 + 1.795 57.110 + 1.882 51.671 + 1.970 18.562 + 2.057 2.667 + 2.146 1.470 + 2.234 0.000 diff --git a/datafiles/thrustcurves/Cesaroni_G69.eng b/datafiles/thrustcurves/Cesaroni_G69.eng new file mode 100644 index 000000000..c5d5addc6 --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_G69.eng @@ -0,0 +1,25 @@ +; Pro38 G69 +G69 38 127 5-7-9-12 0.066500 0.2045 Pro38 + 0.079 79.935 + 0.103 72.367 + 0.136 82.989 + 0.217 85.910 + 0.247 82.193 + 0.311 84.317 + 0.352 80.201 + 0.387 82.856 + 0.840 72.499 + 0.944 72.234 + 0.978 69.047 + 1.017 71.835 + 1.082 67.852 + 1.227 66.657 + 1.237 65.329 + 1.493 62.010 + 1.530 59.354 + 1.591 60.151 + 1.714 56.167 + 1.769 54.574 + 1.848 46.607 + 1.887 27.884 + 1.958 00.000 diff --git a/datafiles/thrustcurves/Cesaroni_G69_1.eng b/datafiles/thrustcurves/Cesaroni_G69_1.eng new file mode 100644 index 000000000..06b0f47fd --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_G69_1.eng @@ -0,0 +1,14 @@ +; +133G69 38.0 127.00 3-5-7-9-12 0.08400 0.20510 CTI + 0.06 80.77 + 0.20 84.50 + 0.41 82.74 + 0.59 80.11 + 0.81 76.82 + 1.00 72.87 + 1.20 68.92 + 1.40 64.31 + 1.61 59.48 + 1.82 50.92 + 1.90 21.40 + 1.93 0.00 diff --git a/datafiles/thrustcurves/Cesaroni_G79.eng b/datafiles/thrustcurves/Cesaroni_G79.eng new file mode 100644 index 000000000..dbbb08d55 --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_G79.eng @@ -0,0 +1,30 @@ +; +; Pro38 G79SS +G79SS 38 127 6-8-10-13 0.069000 0.2070 Pro38 +0.042 67.279 +0.050 72.145 +0.065 76.176 +0.072 76.176 +0.082 74.647 +0.094 68.252 +0.109 66.167 +0.122 65.611 +0.433 81.041 +0.633 88.130 +0.643 87.574 +0.684 89.659 +0.723 89.798 +0.834 92.162 +0.939 93.135 +1.000 93.969 +1.151 91.884 +1.160 90.772 +1.185 91.189 +1.303 86.879 +1.499 77.149 +1.518 75.064 +1.540 66.584 +1.587 23.631 +1.607 10.982 +1.629 4.865 +1.631 0.000 diff --git a/datafiles/thrustcurves/Cesaroni_G79_1.eng b/datafiles/thrustcurves/Cesaroni_G79_1.eng new file mode 100644 index 000000000..21566ab79 --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_G79_1.eng @@ -0,0 +1,27 @@ +; +; +G79SS 38.0 127.00 13-10-8-6-4 0.08500 0.22600 CTI + 0.00 9.07 + 0.03 54.45 + 0.07 76.33 + 0.09 70.95 + 0.11 65.92 + 0.17 68.59 + 0.20 70.64 + 0.30 74.89 + 0.40 80.39 + 0.50 83.76 + 0.60 86.45 + 0.70 88.65 + 0.81 91.40 + 0.90 93.06 + 1.00 93.99 + 1.10 95.81 + 1.20 90.70 + 1.30 86.92 + 1.40 81.98 + 1.50 76.54 + 1.55 58.92 + 1.60 16.41 + 1.63 5.16 + 1.63 0.00 diff --git a/datafiles/thrustcurves/Cesaroni_H120.eng b/datafiles/thrustcurves/Cesaroni_H120.eng new file mode 100644 index 000000000..6556837ce --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_H120.eng @@ -0,0 +1,35 @@ +;Pro-38 Red Lightning 2 Grain reload +H120-14A 38 186 14-11-9-7-5 0.1366 0.295 CTI +0.016 53.023 +0.029 107.113 +0.036 124.55 +0.049 129.532 +0.062 117.789 +0.072 98.217 +0.131 123.838 +0.199 136.649 +0.258 144.122 +0.313 147.681 +0.369 146.257 +0.441 145.19 +0.558 143.411 +0.683 141.631 +0.777 140.92 +0.859 139.14 +0.98 136.293 +1.097 133.091 +1.251 128.82 +1.434 122.771 +1.558 118.856 +1.639 117.077 +1.731 117.077 +1.884 117.077 +1.927 105.334 +1.959 88.964 +1.995 68.325 +2.031 41.991 +2.083 18.505 +2.142 6.761 +2.181 2.135 +2.24 0 +; diff --git a/datafiles/thrustcurves/Cesaroni_H143.eng b/datafiles/thrustcurves/Cesaroni_H143.eng new file mode 100644 index 000000000..4edc13140 --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_H143.eng @@ -0,0 +1,16 @@ +; +; +H143SS 38.0 186.00 4-6-8-10-13 0.16540 0.34700 CTI + 0.06 114.68 + 0.19 134.25 + 0.40 149.61 + 0.60 158.10 + 0.80 163.77 + 1.00 167.00 + 1.21 160.93 + 1.40 148.80 + 1.60 128.59 + 1.63 117.26 + 1.65 106.35 + 1.70 35.63 + 1.73 0.00 diff --git a/datafiles/thrustcurves/Cesaroni_H153.eng b/datafiles/thrustcurves/Cesaroni_H153.eng new file mode 100644 index 000000000..a95f09bd7 --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_H153.eng @@ -0,0 +1,16 @@ +; +; +244H153 38.0 186.00 4-6-8-10-13 0.14390 0.30390 CTI + 0.13 149.36 + 0.17 173.70 + 0.23 171.77 + 0.39 179.91 + 0.60 188.30 + 0.81 180.40 + 1.01 168.25 + 1.18 160.91 + 1.29 149.64 + 1.41 136.95 + 1.60 105.37 + 1.69 23.58 + 1.75 0.00 diff --git a/datafiles/thrustcurves/Cesaroni_H565.eng b/datafiles/thrustcurves/Cesaroni_H565.eng new file mode 100644 index 000000000..71609ac0e --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_H565.eng @@ -0,0 +1,21 @@ +; +; +H565 38 245 5-7-9-11-14 0.1742 0.3622 Cesaroni +0.01 106.91 +0.02 479.76 +0.0347144 528.926 +0.0515118 553.719 +0.0761478 578.512 +0.100784 586.777 +0.150056 611.57 +0.2 607.1 +0.3 614.73 +0.4 616.58 +0.5 622.37 +0.51 645.28 +0.52 628.99 +0.53 535.19 +0.54 327.35 +0.55 147.98 +0.56 60.36 +0.57 0 diff --git a/datafiles/thrustcurves/Cesaroni_I170.eng b/datafiles/thrustcurves/Cesaroni_I170.eng new file mode 100644 index 000000000..0a59f7672 --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_I170.eng @@ -0,0 +1,30 @@ +; Cesaroni I170 +; Copyright Tripoli Motor Testing 2002 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +I170 38 242 0 0.209216 0.404992 CSR + 0.044 133.251 + 0.134 230.114 + 0.226 224.136 + 0.318 223.695 + 0.409 223.888 + 0.501 225.265 + 0.593 226.670 + 0.684 229.397 + 0.775 231.998 + 0.866 233.034 + 0.957 233.144 + 1.049 232.703 + 1.141 231.574 + 1.232 227.745 + 1.324 225.844 + 1.416 221.160 + 1.506 217.396 + 1.597 211.711 + 1.689 205.678 + 1.780 198.020 + 1.872 125.016 + 1.964 61.847 + 2.055 23.279 + 2.147 2.066 + 2.239 0.799 + 2.330 0.000 diff --git a/datafiles/thrustcurves/Cesaroni_I205.eng b/datafiles/thrustcurves/Cesaroni_I205.eng new file mode 100644 index 000000000..baeddf068 --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_I205.eng @@ -0,0 +1,16 @@ +; +; +384I205 38.0 245.00 5-7-9-11-14 0.20610 0.40220 CTI + 0.10 181.50 + 0.13 213.30 + 0.20 210.10 + 0.40 226.61 + 0.60 235.80 + 0.80 234.00 + 1.00 232.80 + 1.20 227.70 + 1.40 216.80 + 1.60 197.21 + 1.74 183.13 + 1.80 87.30 + 1.88 0.00 diff --git a/datafiles/thrustcurves/Cesaroni_I212.eng b/datafiles/thrustcurves/Cesaroni_I212.eng new file mode 100644 index 000000000..4d4b40ff8 --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_I212.eng @@ -0,0 +1,17 @@ +; +; +I212SS 38.0 245.00 5-7-9-11-14 0.24810 0.47500 CTI + 0.04 189.66 + 0.20 207.11 + 0.40 222.41 + 0.60 236.62 + 0.80 249.60 + 0.95 255.15 + 1.01 250.22 + 1.21 233.54 + 1.40 208.99 + 1.55 183.99 + 1.60 168.08 + 1.63 134.62 + 1.69 25.86 + 1.71 0.00 diff --git a/datafiles/thrustcurves/Cesaroni_I240.eng b/datafiles/thrustcurves/Cesaroni_I240.eng new file mode 100644 index 000000000..ad5799ad3 --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_I240.eng @@ -0,0 +1,30 @@ +; Cesaroni I240 +; Copyright Tripoli Motor Testing 2002 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +I240 38 302 0 0.274624 0.503552 CSR + 0.043 265.317 + 0.131 320.903 + 0.221 314.148 + 0.310 312.413 + 0.399 313.564 + 0.488 314.335 + 0.577 321.117 + 0.667 325.923 + 0.755 327.040 + 0.844 326.831 + 0.933 324.348 + 1.023 321.063 + 1.111 317.446 + 1.200 308.301 + 1.290 300.612 + 1.379 293.536 + 1.468 283.358 + 1.556 273.832 + 1.646 259.708 + 1.735 190.662 + 1.824 124.130 + 1.912 60.875 + 2.002 26.967 + 2.092 7.636 + 2.181 2.296 + 2.271 0.000 diff --git a/datafiles/thrustcurves/Cesaroni_I285.eng b/datafiles/thrustcurves/Cesaroni_I285.eng new file mode 100644 index 000000000..497f3501b --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_I285.eng @@ -0,0 +1,16 @@ +; +; +512I285 38.0 303.00 6-8-10-12-15 0.27240 0.50590 CTI + 0.10 350.60 + 0.15 318.73 + 0.20 312.30 + 0.40 322.37 + 0.60 330.57 + 0.80 329.40 + 1.00 319.64 + 1.20 294.14 + 1.40 271.90 + 1.60 239.90 + 1.66 178.10 + 1.80 44.80 + 1.91 0.00 diff --git a/datafiles/thrustcurves/Cesaroni_I287.eng b/datafiles/thrustcurves/Cesaroni_I287.eng new file mode 100644 index 000000000..12556e56a --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_I287.eng @@ -0,0 +1,18 @@ +; +; +I287SS 38.0 303.00 6-8-10-12-15 0.33080 0.60500 CTI + 0.05 275.86 + 0.20 292.53 + 0.41 309.20 + 0.61 327.53 + 0.80 341.70 + 0.90 344.20 + 1.01 331.70 + 1.20 311.70 + 1.40 280.03 + 1.53 245.02 + 1.58 176.62 + 1.60 141.76 + 1.64 68.99 + 1.70 17.48 + 1.70 0.00 diff --git a/datafiles/thrustcurves/Cesaroni_I350.eng b/datafiles/thrustcurves/Cesaroni_I350.eng new file mode 100644 index 000000000..249406c73 --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_I350.eng @@ -0,0 +1,17 @@ +; +; +I350SS 38.0 367.00 7-9-11-13-16 0.41350 0.78200 CTI + 0.05 399.74 + 0.13 390.06 + 0.19 386.19 + 0.40 388.13 + 0.60 388.13 + 0.80 388.13 + 1.00 389.91 + 1.20 387.38 + 1.33 368.77 + 1.44 350.38 + 1.52 320.37 + 1.60 164.79 + 1.68 36.77 + 1.71 0.00 diff --git a/datafiles/thrustcurves/Cesaroni_I360.eng b/datafiles/thrustcurves/Cesaroni_I360.eng new file mode 100644 index 000000000..dc8e5ceb4 --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_I360.eng @@ -0,0 +1,20 @@ +; +; +I360 38 367 15 0.3346 0.5963 Cesaroni +0.08 555.5 +0.1 489.7 +0.13 448 +0.2 449 +0.4 483.7 +0.55 498 +0.6 494.9 +0.7 481.91 +0.8 457.9 +1 406.6 +1.2 344.4 +1.3 309.3 +1.4 182.2 +1.55 158.9 +1.6 101.8 +1.7 55.8 +1.77 0 diff --git a/datafiles/thrustcurves/Cesaroni_I540.eng b/datafiles/thrustcurves/Cesaroni_I540.eng new file mode 100644 index 000000000..9c39ad088 --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_I540.eng @@ -0,0 +1,21 @@ +; +; +I540WT 38 367 7-9-11-13-16 0.3288 0.5982 CTI +0.03 597.86 +0.04 611.31 +0.06 605.64 +0.12 612.36 +0.24 624.54 +0.36 626 +0.48 623.63 +0.6 616.42 +0.72 598.14 +0.84 583.16 +0.95 568.92 +0.96 558.53 +0.98 533.45 +1.02 436.53 +1.06 303.15 +1.09 184.92 +1.13 74.27 +1.18 0 diff --git a/datafiles/thrustcurves/Cesaroni_J210.eng b/datafiles/thrustcurves/Cesaroni_J210.eng new file mode 100644 index 000000000..b9bde28f2 --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_J210.eng @@ -0,0 +1,17 @@ +; +; +J210 54.0 236.00 6-16 0.08270 0.84200 CTI + 0.04 335.00 + 0.16 270.92 + 0.41 269.30 + 0.80 268.49 + 1.18 256.32 + 1.62 236.85 + 2.03 214.14 + 2.38 193.86 + 2.79 174.39 + 3.20 163.85 + 3.60 157.36 + 3.75 135.46 + 3.86 85.17 + 3.99 0.00 diff --git a/datafiles/thrustcurves/Cesaroni_J280.eng b/datafiles/thrustcurves/Cesaroni_J280.eng new file mode 100644 index 000000000..dd8bf4f41 --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_J280.eng @@ -0,0 +1,16 @@ +; +; +J280SS 54.0 236.00 6-16 0.51200 0.95400 CTI + 0.10 259.43 + 0.30 278.91 + 0.60 293.07 + 0.90 306.85 + 1.20 319.19 + 1.50 321.10 + 1.80 310.85 + 2.11 279.89 + 2.35 286.70 + 2.40 269.17 + 2.44 178.24 + 2.49 42.80 + 2.54 0.00 diff --git a/datafiles/thrustcurves/Cesaroni_J285.eng b/datafiles/thrustcurves/Cesaroni_J285.eng new file mode 100644 index 000000000..a9c783e70 --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_J285.eng @@ -0,0 +1,16 @@ +; +; +J285 38.0 367.00 6-8-10-12-15 0.31250 0.59500 CTI + 0.06 351.01 + 0.15 346.01 + 0.25 357.64 + 0.50 363.90 + 0.75 369.26 + 1.03 343.33 + 1.27 337.07 + 1.51 317.40 + 1.75 282.53 + 1.93 127.86 + 2.02 84.94 + 2.25 11.02 + 2.26 0.00 diff --git a/datafiles/thrustcurves/Cesaroni_J295.eng b/datafiles/thrustcurves/Cesaroni_J295.eng new file mode 100644 index 000000000..922671b28 --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_J295.eng @@ -0,0 +1,15 @@ +; +; +J295 54.0 329.00 6-16 0.59400 1.11900 CTI + 0.04 450.52 + 0.28 428.70 + 0.54 423.25 + 1.00 391.61 + 1.48 352.34 + 1.99 304.35 + 2.51 266.17 + 3.00 243.26 + 3.50 216.92 + 3.67 126.54 + 3.82 64.36 + 4.00 0.00 diff --git a/datafiles/thrustcurves/Cesaroni_J300.eng b/datafiles/thrustcurves/Cesaroni_J300.eng new file mode 100644 index 000000000..601ee22ec --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_J300.eng @@ -0,0 +1,30 @@ +; Cesaroni J300 +; Copyright Tripoli Motor Testing 2002 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +J300 38 360 0 0.340032 0.606592 CSR + 0.043 357.026 + 0.131 436.586 + 0.221 407.925 + 0.310 399.528 + 0.400 400.588 + 0.490 406.733 + 0.578 414.302 + 0.667 417.117 + 0.756 418.415 + 0.846 421.302 + 0.935 422.229 + 1.025 415.951 + 1.114 406.356 + 1.202 395.237 + 1.292 381.728 + 1.381 369.861 + 1.471 355.451 + 1.560 331.691 + 1.649 246.243 + 1.738 161.766 + 1.827 109.478 + 1.917 71.413 + 2.006 37.058 + 2.096 13.880 + 2.185 5.059 + 2.275 0.000 diff --git a/datafiles/thrustcurves/Cesaroni_J330.eng b/datafiles/thrustcurves/Cesaroni_J330.eng new file mode 100644 index 000000000..c1e8e5eab --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_J330.eng @@ -0,0 +1,17 @@ +; +; +J330 38.0 421.00 7-9-11-13-16 0.37500 0.70200 CTI + 0.05 459.32 + 0.16 448.20 + 0.27 440.41 + 0.51 437.08 + 0.75 427.07 + 1.02 412.61 + 1.26 387.03 + 1.50 360.34 + 1.69 321.41 + 1.79 300.28 + 1.91 126.79 + 1.99 107.88 + 2.23 22.56 + 2.26 0.00 diff --git a/datafiles/thrustcurves/Cesaroni_J360.eng b/datafiles/thrustcurves/Cesaroni_J360.eng new file mode 100644 index 000000000..acc545dc9 --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_J360.eng @@ -0,0 +1,30 @@ +; Cesaroni J360 +; Copyright Tripoli Motor Testing 2002 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +J360 38 419 0 0.409024 0.709184 CSR + 0.041 618.905 + 0.124 616.584 + 0.207 563.785 + 0.291 557.730 + 0.374 558.409 + 0.457 562.088 + 0.541 561.267 + 0.624 563.219 + 0.708 565.328 + 0.793 566.558 + 0.876 549.383 + 0.959 529.633 + 1.043 511.099 + 1.126 483.285 + 1.209 445.397 + 1.293 421.658 + 1.377 378.330 + 1.461 261.647 + 1.545 197.445 + 1.628 146.570 + 1.711 101.807 + 1.795 78.039 + 1.878 47.847 + 1.961 31.861 + 2.046 9.220 + 2.130 0.000 diff --git a/datafiles/thrustcurves/Cesaroni_J380.eng b/datafiles/thrustcurves/Cesaroni_J380.eng new file mode 100644 index 000000000..ebd8eff61 --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_J380.eng @@ -0,0 +1,16 @@ +; +; +J380SS 54.0 320.00 6-16 0.76900 1.29330 CTI + 0.05 368.48 + 0.30 348.31 + 0.60 378.83 + 0.90 400.93 + 1.20 419.52 + 1.50 433.09 + 1.80 434.60 + 2.10 408.81 + 2.40 369.92 + 2.56 410.58 + 2.59 297.80 + 2.71 45.25 + 2.73 0.00 diff --git a/datafiles/thrustcurves/Cesaroni_J400.eng b/datafiles/thrustcurves/Cesaroni_J400.eng new file mode 100644 index 000000000..ab756c156 --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_J400.eng @@ -0,0 +1,18 @@ +; +; +J400SS 38.0 421.00 7-9-11-13-16 0.48960 0.70200 CTI + 0.05 451.79 + 0.20 461.14 + 0.31 465.81 + 0.44 463.47 + 0.60 477.48 + 0.80 482.15 + 1.00 461.31 + 1.20 433.12 + 1.35 402.76 + 1.40 382.92 + 1.47 321.04 + 1.55 258.00 + 1.60 178.62 + 1.73 14.58 + 1.75 0.00 diff --git a/datafiles/thrustcurves/Cesaroni_J410.eng b/datafiles/thrustcurves/Cesaroni_J410.eng new file mode 100644 index 000000000..e176b034e --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_J410.eng @@ -0,0 +1,23 @@ +; Pro38 Red Lightning 6G. +J410-16A 38 421 16 0.4098 0.735 CTI + 0.023 375.45 + 0.029 446.221 + 0.042 510.996 + 0.11 499.0 + 0.22 495.402 + 0.442 491.803 + 0.675 475.01 + 0.901 464.214 + 1.092 448.62 + 1.221 437.825 + 1.34 429.428 + 1.492 419.832 + 1.553 389.844 + 1.592 349.06 + 1.65 273.491 + 1.689 196.721 + 1.75 122.351 + 1.809 64.774 + 1.889 28.788 + 1.941 13.195 + 1.989 0.0 diff --git a/datafiles/thrustcurves/Cesaroni_K445.eng b/datafiles/thrustcurves/Cesaroni_K445.eng new file mode 100644 index 000000000..366ef5b45 --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_K445.eng @@ -0,0 +1,15 @@ +; +; +K445 54.0 404.00 7-17 0.79200 1.39800 CTI + 0.05 664.83 + 0.19 640.68 + 0.48 622.98 + 1.00 576.29 + 1.51 515.12 + 2.00 442.68 + 2.50 392.26 + 3.02 350.93 + 3.13 339.66 + 3.31 210.88 + 3.47 78.88 + 3.67 0.00 diff --git a/datafiles/thrustcurves/Cesaroni_K510.eng b/datafiles/thrustcurves/Cesaroni_K510.eng new file mode 100644 index 000000000..c673b791d --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_K510.eng @@ -0,0 +1,25 @@ +; +; Cesaroni Pro75 2486K510 +; 'Classic Propellant' +; +; RockSim file by Kathy Miller +; wRasp Adaptation by Len Lekx +; +K510 75 350 0 1.19 2.59 CTI +0.10 645.25 +0.30 689.75 +0.50 658.60 +1.00 636.35 +1.60 600.75 +2.00 565.15 +2.40 534.00 +2.50 525.10 +3.00 471.70 +3.50 422.75 +3.70 400.50 +4.00 391.60 +4.40 382.70 +4.50 378.25 +4.60 333.75 +4.70 66.75 +4.84 0.00 diff --git a/datafiles/thrustcurves/Cesaroni_K510_1.eng b/datafiles/thrustcurves/Cesaroni_K510_1.eng new file mode 100644 index 000000000..ca5e64db0 --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_K510_1.eng @@ -0,0 +1,25 @@ +; +; +K510 75.0 350.00 0 1.19700 2.59000 CTI + 0.04 394.38 + 0.07 617.68 + 0.10 645.17 + 0.21 658.16 + 0.35 669.23 + 0.53 667.72 + 0.82 661.58 + 1.18 626.92 + 1.72 588.46 + 2.15 557.69 + 2.39 542.31 + 2.90 492.86 + 3.07 470.31 + 3.56 426.81 + 3.98 398.96 + 4.32 393.98 + 4.48 380.63 + 4.60 364.22 + 4.65 290.91 + 4.80 91.23 + 4.84 45.82 + 4.84 0.00 diff --git a/datafiles/thrustcurves/Cesaroni_K530.eng b/datafiles/thrustcurves/Cesaroni_K530.eng new file mode 100644 index 000000000..df2132f99 --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_K530.eng @@ -0,0 +1,18 @@ +; +; +K530SS 54.0 404.00 6-16 1.02500 1.63980 CTI + 0.05 533.59 + 0.09 503.98 + 0.29 514.09 + 0.60 534.31 + 0.90 557.41 + 1.20 577.63 + 1.50 587.74 + 1.80 596.40 + 2.10 535.44 + 2.31 502.54 + 2.47 551.63 + 2.56 393.94 + 2.60 274.37 + 2.64 137.19 + 2.67 0.00 diff --git a/datafiles/thrustcurves/Cesaroni_K570.eng b/datafiles/thrustcurves/Cesaroni_K570.eng new file mode 100644 index 000000000..0b4130799 --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_K570.eng @@ -0,0 +1,13 @@ +; +; +K570 54.0 488.00 7-17 0.99000 1.68500 CTI + 0.04 892.67 + 0.50 797.99 + 1.00 738.68 + 1.50 659.37 + 2.00 585.96 + 2.50 512.88 + 2.97 417.16 + 3.20 224.79 + 3.47 67.00 + 3.59 0.00 diff --git a/datafiles/thrustcurves/Cesaroni_K575.eng b/datafiles/thrustcurves/Cesaroni_K575.eng new file mode 100644 index 000000000..9beb7b4f3 --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_K575.eng @@ -0,0 +1,16 @@ +; +; +K575SS 75 395 1000 1.803 3.143 Cesaroni +0 16 +0.11 664.5 +0.43 620.2 +0.87 629 +1.3 637.92 +1.73 637.92 +2.17 629 +2.6 615.77 +3.03 553.75 +3.47 518.31 +3.9 438.57 +4.18 79.74 +4.33 0 diff --git a/datafiles/thrustcurves/Cesaroni_K650.eng b/datafiles/thrustcurves/Cesaroni_K650.eng new file mode 100644 index 000000000..8d9f29d17 --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_K650.eng @@ -0,0 +1,17 @@ +; +; +K650SS 54.0 488.00 6-16 1.28100 1.98990 CTI + 0.04 664.52 + 0.12 645.90 + 0.31 642.24 + 0.60 664.78 + 0.91 684.59 + 1.22 712.82 + 1.50 723.41 + 1.80 728.70 + 2.10 664.52 + 2.40 614.68 + 2.51 680.53 + 2.55 534.62 + 2.61 268.19 + 2.66 0.00 diff --git a/datafiles/thrustcurves/Cesaroni_K660.eng b/datafiles/thrustcurves/Cesaroni_K660.eng new file mode 100644 index 000000000..41caa36a7 --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_K660.eng @@ -0,0 +1,19 @@ +; +; +K660 54.0 572.00 7-17 1.17700 1.94900 CTI + 0.07 1078.90 + 0.23 1006.47 + 0.40 966.76 + 0.80 897.52 + 1.20 842.72 + 1.60 794.15 + 2.01 744.52 + 2.40 692.27 + 2.54 671.37 + 2.68 439.08 + 2.80 400.68 + 3.01 386.90 + 3.20 234.31 + 3.45 106.65 + 3.60 44.03 + 3.69 0.00 diff --git a/datafiles/thrustcurves/Cesaroni_L1090.eng b/datafiles/thrustcurves/Cesaroni_L1090.eng new file mode 100644 index 000000000..391d4064d --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_L1090.eng @@ -0,0 +1,18 @@ +; +; +L1090SS 75 665 1000 3.491 5.461 Cesaroni +0 487.3 +0.11 1639.1 +0.22 1484.05 +0.44 1417.6 +0.87 1373.3 +1.31 1329 +1.74 1306.85 +2.18 1262.55 +2.61 1218.25 +3.05 1151.8 +3.21 775.25 +3.48 598.05 +3.92 553.75 +4.13 221.5 +4.35 0 diff --git a/datafiles/thrustcurves/Cesaroni_L1115.eng b/datafiles/thrustcurves/Cesaroni_L1115.eng new file mode 100644 index 000000000..1914f3848 --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_L1115.eng @@ -0,0 +1,23 @@ +; +; Cesaroni Pro75 5015L1115 +; 'Classic Propellant' +; +; RockSim file by Kathy Miller +; wRasp Adaptation by Len Lekx +; +L1115 75 621 0 2.39 4.40 CTI +0.10 1468.85 +0.30 1490.75 +0.80 1401.75 +1.00 1437.35 +1.50 1335.00 +2.00 1268.25 +2.20 1246.00 +2.50 1112.50 +3.00 1090.25 +3.30 979.00 +3.80 979.00 +4.00 623.00 +4.20 311.50 +4.40 35.00 +4.48 0.00 diff --git a/datafiles/thrustcurves/Cesaroni_L1115_1.eng b/datafiles/thrustcurves/Cesaroni_L1115_1.eng new file mode 100644 index 000000000..b02a52444 --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_L1115_1.eng @@ -0,0 +1,28 @@ +; +; +L1115 75.0 621.00 0 2.39400 4.40400 CTI + 0.01 45.46 + 0.01 522.52 + 0.01 984.04 + 0.04 1256.10 + 0.05 1389.85 + 0.08 1713.25 + 0.24 1515.65 + 0.30 1474.74 + 0.40 1443.28 + 0.42 1446.25 + 0.50 1430.02 + 0.76 1392.85 + 1.00 1361.70 + 1.28 1339.45 + 1.84 1259.35 + 2.25 1201.50 + 3.00 1076.11 + 3.50 990.86 + 3.92 832.85 + 4.00 607.78 + 4.10 434.99 + 4.22 288.73 + 4.38 156.49 + 4.48 86.39 + 5.00 0.00 diff --git a/datafiles/thrustcurves/Cesaroni_L610.eng b/datafiles/thrustcurves/Cesaroni_L610.eng new file mode 100644 index 000000000..5cd2843a6 --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_L610.eng @@ -0,0 +1,26 @@ +; +; +L610 98 394 0 2.415 4.975 CTI +0.06 262.5 +0.12 667.2 +0.25 929.7 +0.39 871.21 +0.65 849.83 +1.05 823.1 +1.5 785.69 +2 747.3 +2.5 707.3 +3 667.2 +3.48 641.38 +4 593.28 +4.47 561.21 +5 523.79 +5.44 502.41 +5.68 491.72 +6 475.69 +6.5 459.66 +7.01 443.62 +7.5 413.7 +8 284.7 +8.12 53.3 +8.13 0 diff --git a/datafiles/thrustcurves/Cesaroni_L730.eng b/datafiles/thrustcurves/Cesaroni_L730.eng new file mode 100644 index 000000000..33f124576 --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_L730.eng @@ -0,0 +1,28 @@ +; +; +L730 54.0 649.00 0 1.35100 2.24700 CTI + 0.00 81.36 + 0.01 1079.71 + 0.02 1216.59 + 0.04 1154.68 + 0.20 1127.51 + 0.45 1055.11 + 0.60 1028.17 + 0.75 995.24 + 1.00 959.33 + 1.50 898.71 + 2.00 830.70 + 2.50 730.76 + 2.60 592.55 + 2.70 510.96 + 2.90 487.88 + 3.00 405.72 + 3.10 299.80 + 3.20 296.09 + 3.30 251.85 + 3.40 171.70 + 3.50 165.26 + 3.60 139.38 + 3.65 117.77 + 3.77 45.38 + 3.77 0.00 diff --git a/datafiles/thrustcurves/Cesaroni_L800.eng b/datafiles/thrustcurves/Cesaroni_L800.eng new file mode 100644 index 000000000..9795533f4 --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_L800.eng @@ -0,0 +1,24 @@ +; +; Cesaroni Pro75 3757L800 +; 'Classic Propellant' +; +; RockSim file by Kathy Miller +; wRasp Adaptation by Len Lekx +; +L800 75 486 0 1.79 3.51 CTI +0.10 1023.50 +0.20 1005.70 +0.30 1023.50 +0.50 1014.60 +1.00 1010.15 +1.50 1001.25 +2.00 956.75 +2.40 890.00 +2.50 845.50 +3.00 756.50 +3.50 689.75 +3.70 667.50 +3.90 654.15 +4.00 623.00 +4.60 111.25 +4.67 0.00 diff --git a/datafiles/thrustcurves/Cesaroni_L800_1.eng b/datafiles/thrustcurves/Cesaroni_L800_1.eng new file mode 100644 index 000000000..0ed573793 --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_L800_1.eng @@ -0,0 +1,26 @@ +; +; +L800 75.0 486.00 0 1.79500 3.51100 CTI + 0.00 27.28 + 0.01 402.41 + 0.01 1285.54 + 0.12 1056.51 + 0.26 1041.73 + 0.71 1026.95 + 1.28 998.38 + 2.05 901.36 + 2.41 849.64 + 2.83 763.51 + 3.25 707.06 + 3.65 655.14 + 3.80 651.74 + 4.00 624.07 + 4.10 601.34 + 4.19 536.17 + 4.31 415.67 + 4.41 270.17 + 4.52 140.20 + 4.60 76.92 + 4.65 54.94 + 4.67 40.16 + 5.00 0.00 diff --git a/datafiles/thrustcurves/Cesaroni_L890.eng b/datafiles/thrustcurves/Cesaroni_L890.eng new file mode 100644 index 000000000..5289d09bc --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_L890.eng @@ -0,0 +1,15 @@ +; +; +L890SS 75 530 1000 2.671 4.346 Cesaroni +0 20 +0.05 1151.8 +0.41 1054.34 +0.83 1045.48 +1.24 1036.62 +1.65 1027.76 +2.07 1018.9 +2.89 886 +3.31 775.25 +3.72 664.5 +3.98 177.2 +4.13 0 diff --git a/datafiles/thrustcurves/Cesaroni_M1060.eng b/datafiles/thrustcurves/Cesaroni_M1060.eng new file mode 100644 index 000000000..b6d67625c --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_M1060.eng @@ -0,0 +1,25 @@ +; +; +M1060 98 548 0 3.622 6.673 CTI +0.07 131 +0.1 594 +0.2 1453 +0.238 1494 +0.378 1450 +0.378 1425 +0.5 1423 +1 1462 +1.5 1456 +2 1430 +2.5 1376 +3 1280 +3.5 1190 +4 1051 +4.5 976 +5 883 +5.5 835 +6 793 +6.5 321 +7 13 +7.229 7 +7.23 0 diff --git a/datafiles/thrustcurves/Cesaroni_M1400.eng b/datafiles/thrustcurves/Cesaroni_M1400.eng new file mode 100644 index 000000000..b6a75d060 --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_M1400.eng @@ -0,0 +1,22 @@ +; +; Cesaroni Pro75 6251M1400 +; 'Classic Propellant' +; +; RockSim file by Kathy Miller +; wRasp Adaptation by Len Lekx +; +M1400 75 7570 0 2.99 5.30 CTI +0.10 1993.60 +0.50 1891.25 +1.10 1780.00 +1.50 1691.00 +2.00 1602.00 +2.30 1557.50 +2.50 1513.00 +3.00 1335.00 +3.50 1223.75 +3.70 1112.00 +3.90 667.50 +4.00 534.00 +4.40 222.50 +4.47 0.00 diff --git a/datafiles/thrustcurves/Cesaroni_M1400_1.eng b/datafiles/thrustcurves/Cesaroni_M1400_1.eng new file mode 100644 index 000000000..de7f4c891 --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_M1400_1.eng @@ -0,0 +1,28 @@ +; +; +M1400 75.0 757.00 0 2.99200 5.30200 CTI + 0.02 991.61 + 0.07 1939.66 + 0.11 2291.75 + 0.14 1976.39 + 0.19 1962.48 + 0.29 1936.13 + 0.52 1881.02 + 0.75 1833.40 + 1.00 1778.08 + 1.25 1738.57 + 1.70 1654.82 + 2.40 1502.39 + 2.85 1389.48 + 3.25 1283.00 + 3.40 1232.23 + 3.53 1199.64 + 3.65 1083.69 + 3.70 909.39 + 3.90 641.50 + 4.00 502.82 + 4.03 463.03 + 4.22 336.09 + 4.43 138.68 + 4.47 93.21 + 5.00 0.00 diff --git a/datafiles/thrustcurves/Cesaroni_M1450.eng b/datafiles/thrustcurves/Cesaroni_M1450.eng new file mode 100644 index 000000000..e43f4d5e1 --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_M1450.eng @@ -0,0 +1,24 @@ +; +; +M1450 98 702 0 4.83 8.578 CTI +0.01 60 +0.06 524 +0.1 2164 +0.151 2416 +0.25 2162 +0.5 2037 +0.75 2022 +1 2009 +1.5 2006 +2 1968 +2.5 1895 +3 1770 +3.5 1673 +4 1517 +4.5 1337 +5 1166 +5.5 954 +5.8 687 +6.2 360 +6.86 79 +6.87 0 diff --git a/datafiles/thrustcurves/Cesaroni_M2505.eng b/datafiles/thrustcurves/Cesaroni_M2505.eng new file mode 100644 index 000000000..5d64794e2 --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_M2505.eng @@ -0,0 +1,15 @@ +; +; +M2505 98.0 548.00 0 3.42300 6.25800 CTI + 0.12 2600.00 + 0.21 2482.00 + 0.60 2715.00 + 0.90 2876.00 + 1.20 2938.00 + 1.50 2889.00 + 1.80 2785.00 + 2.10 2573.00 + 2.40 2349.00 + 2.70 2182.00 + 3.00 85.00 + 3.00 0.00 diff --git a/datafiles/thrustcurves/Cesaroni_M520.eng b/datafiles/thrustcurves/Cesaroni_M520.eng new file mode 100644 index 000000000..6b8e6634c --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_M520.eng @@ -0,0 +1,25 @@ +; +; +M520 98.0 548.00 0 3.71300 6.69300 CTI + 0.01 1077.00 + 0.25 1062.83 + 0.38 1065.66 + 0.50 971.00 + 0.71 938.12 + 0.93 915.45 + 1.23 878.61 + 2.07 906.95 + 2.61 901.28 + 3.03 892.78 + 3.50 872.94 + 3.93 836.09 + 4.96 756.73 + 6.08 657.54 + 7.05 549.84 + 7.79 461.98 + 8.39 391.12 + 9.06 323.10 + 10.01 243.74 + 11.01 172.89 + 12.00 116.20 + 13.95 0.00 diff --git a/datafiles/thrustcurves/Cesaroni_M795.eng b/datafiles/thrustcurves/Cesaroni_M795.eng new file mode 100644 index 000000000..3a11facb9 --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_M795.eng @@ -0,0 +1,25 @@ +; +; +M795 98 702 0 4.892 8.492 CTI +0.15 612.314 +0.21 1532.76 +0.245 1722 +0.43 1717.66 +0.5 1542.85 +0.62 1430.02 +0.8 1389.71 +1 1374.27 +1.5 1338.9 +2 1305.38 +3 1271.81 +4 1204 +5 1078 +6 928 +7 743 +8 563 +9 424.898 +10 299.697 +11 196.164 +12 116.759 +12.7 65.434 +12.76 0 diff --git a/datafiles/thrustcurves/Cesaroni_N1100.eng b/datafiles/thrustcurves/Cesaroni_N1100.eng new file mode 100644 index 000000000..119bc59d4 --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_N1100.eng @@ -0,0 +1,15 @@ +; +; +N1100 98 1010 0 4.517 11.644 CTI +0.16 2624 +0.33 2708 +0.91 2055 +1.22 1896 +2.44 1793 +3.66 1625 +4.88 1402 +6.12 1158 +7.41 854 +9.77 494 +12.18 111.2 +12.19 0 diff --git a/datafiles/thrustcurves/Cesaroni_N2500.eng b/datafiles/thrustcurves/Cesaroni_N2500.eng new file mode 100644 index 000000000..59c1425b2 --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_N2500.eng @@ -0,0 +1,25 @@ +; +; +N2500 98.0 1010.00 0 6.77800 11.66800 CTI + 0.02 773.70 + 0.05 3356.60 + 0.06 3657.80 + 0.10 3546.80 + 0.25 3403.80 + 0.40 3309.20 + 0.80 3262.50 + 1.00 3206.10 + 1.50 3088.50 + 2.00 2940.40 + 2.50 2792.60 + 3.00 2598.40 + 3.50 2402.50 + 4.00 2227.00 + 4.25 2152.50 + 4.40 2102.50 + 4.50 2007.00 + 4.60 1683.80 + 4.75 1269.50 + 5.00 767.30 + 5.41 341.30 + 5.42 0.00 diff --git a/datafiles/thrustcurves/Cesaroni_O5100.eng b/datafiles/thrustcurves/Cesaroni_O5100.eng new file mode 100644 index 000000000..6ed9444ac --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_O5100.eng @@ -0,0 +1,44 @@ +; +;Cesaroni Technologies Inc Motor Data File +;Composed by Carl Tulanko for 150mm "O" CAR Certed Motor +;24-Jun-2003 using CTI Cert graph to chart points +O5100 150 803 1000 13.245 23.577 Cesaroni +0.01 815.07 +0.02 1407.85 +0.03 2334.11 +0.04 3260.42 +0.05 4001.47 +0.07 4927.78 +0.07 5483.57 +0.09 5817.04 +0.13 6057.88 +0.2 6206.09 +0.3 6298.72 +0.43 6280.19 +0.6 6261.67 +0.78 6298.72 +0.97 6354.3 +1.05 6428.4 +1.12 6391.35 +1.34 6465.46 +1.49 6502.51 +1.75 6539.56 +1.88 6558.09 +2.16 6521.03 +2.36 6465.46 +2.58 6372.82 +2.96 6113.46 +3.56 5557.67 +4.13 4909.25 +4.72 4260.83 +4.83 4149.68 +4.93 3038.1 +5 2612 +5.1 2111.79 +5.23 1741.29 +5.32 1537.53 +5.52 1222.61 +5.8 907.69 +5.85 666.88 +5.89 333.44 +5.9 0 diff --git a/datafiles/thrustcurves/Cesaroni_O5800.eng b/datafiles/thrustcurves/Cesaroni_O5800.eng new file mode 100644 index 000000000..d1f8e4024 --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_O5800.eng @@ -0,0 +1,33 @@ +; Pro 150 O5800 White Thunder +O5800 150 754 P 13.950000000000001 26.368000000000002 CTI + 0.069 6337.621 + 0.103 5700.965 + 0.218 5874.598 + 0.378 6135.048 + 0.561 6337.621 + 0.745 6221.865 + 0.985 6221.865 + 1.18 6192.926 + 1.455 6308.682 + 1.753 6366.559 + 1.994 6337.621 + 2.269 6395.498 + 2.509 6308.682 + 2.83 6192.926 + 3.14 6048.232 + 3.426 5874.598 + 3.69 5729.904 + 3.965 5585.209 + 4.263 5382.637 + 4.572 5295.82 + 4.939 5180.064 + 5.053 5035.37 + 5.11 4717.042 + 5.133 4225.08 + 5.145 3675.241 + 5.156 3038.585 + 5.179 2344.051 + 5.214 1475.884 + 5.259 607.717 + 5.294 57.878 + 5.295 0.0 diff --git a/datafiles/thrustcurves/Cesaroni_O8000.eng b/datafiles/thrustcurves/Cesaroni_O8000.eng new file mode 100644 index 000000000..6031e41d2 --- /dev/null +++ b/datafiles/thrustcurves/Cesaroni_O8000.eng @@ -0,0 +1,34 @@ +; Pro 150 O8000 White Thunder +O8000 150 957 P 18.61 32.672000000000004 CTI + 0.045 3964.63 + 0.046 6742.765 + 0.047 8623.794 + 0.125 7929.26 + 0.239 8160.772 + 0.364 8392.283 + 0.489 8508.039 + 0.614 8536.977 + 0.773 8392.283 + 0.989 8421.222 + 1.273 8479.1 + 1.602 8623.794 + 2.011 8565.916 + 2.33 8565.916 + 2.682 8479.1 + 3.102 8276.527 + 3.568 8045.016 + 3.886 7900.322 + 4.239 7668.81 + 4.591 7524.116 + 4.739 7524.116 + 4.909 7263.666 + 4.955 7003.215 + 4.977 6540.193 + 4.989 5845.659 + 5.0 5006.431 + 5.023 4051.447 + 5.034 3067.524 + 5.045 1996.785 + 5.08 1012.862 + 5.114 318.328 + 5.17 0.0 diff --git a/datafiles/thrustcurves/Contrail_G100.eng b/datafiles/thrustcurves/Contrail_G100.eng new file mode 100644 index 000000000..27625aeed --- /dev/null +++ b/datafiles/thrustcurves/Contrail_G100.eng @@ -0,0 +1,7 @@ +; +G100 38 406 0 0.093 0.511 Contrail_Rockets +0 182.756 +0.199105 177.584 +0.606264 132.757 +0.986577 53.4476 +1.43 0 diff --git a/datafiles/thrustcurves/Contrail_G123.eng b/datafiles/thrustcurves/Contrail_G123.eng new file mode 100644 index 000000000..33a0249eb --- /dev/null +++ b/datafiles/thrustcurves/Contrail_G123.eng @@ -0,0 +1,9 @@ +; +; +G123 38 406 0 0.083 0.511 Contrail_Rockets +0.00223714 217.239 +0.00671141 399.995 +0.0201342 220.687 +0.914989 72.4129 +0.955257 37.9306 +1.15 0 diff --git a/datafiles/thrustcurves/Contrail_G130.eng b/datafiles/thrustcurves/Contrail_G130.eng new file mode 100644 index 000000000..5b4f4187a --- /dev/null +++ b/datafiles/thrustcurves/Contrail_G130.eng @@ -0,0 +1,8 @@ +; +; +G130 38 406 0 0.093 0.516 Contrail_Rockets +0 662.061 +0.0145414 448.27 +0.0234899 241.376 +0.642058 41.3788 +0.86 0 diff --git a/datafiles/thrustcurves/Contrail_G234.eng b/datafiles/thrustcurves/Contrail_G234.eng new file mode 100644 index 000000000..e363596f4 --- /dev/null +++ b/datafiles/thrustcurves/Contrail_G234.eng @@ -0,0 +1,12 @@ +; +;G-234-HP Reload +;38mm/16 Inch Hardware +;Fast Nozzle +G234 38 406.4 0 0.498 0.544 Contrail_Rockets +0.00169492 245.419 +0.0973154 540.63 +0.183445 526.943 +0.202461 191.616 +0.237136 143.712 +0.260626 136.868 +0.533 0 diff --git a/datafiles/thrustcurves/Contrail_G300.eng b/datafiles/thrustcurves/Contrail_G300.eng new file mode 100644 index 000000000..b11822e30 --- /dev/null +++ b/datafiles/thrustcurves/Contrail_G300.eng @@ -0,0 +1,11 @@ +; +;G-300 PVC Motor for 38mm/16 Inch Case. +;Motor uses Fast Nozzle +;90cc's of Nitrous Oxide Used +G300 38 406.4 0 0.023 0.544 Contrail_Rockets +0.00111857 602.221 +0.0497763 814.367 +0.100671 670.655 +0.114094 266.893 +0.158837 239.52 +0.25 0 diff --git a/datafiles/thrustcurves/Contrail_H121.eng b/datafiles/thrustcurves/Contrail_H121.eng new file mode 100644 index 000000000..2d1b380bf --- /dev/null +++ b/datafiles/thrustcurves/Contrail_H121.eng @@ -0,0 +1,12 @@ +; +; +H121 38 516 0 0.11 0.612 Contrail_Rockets +0.00223714 251.721 +0.0402685 265.514 +0.0738255 203.446 +0.400447 179.308 +0.60179 134.481 +1.08949 127.585 +1.40268 93.1023 +1.61969 37.9306 +1.85 0 diff --git a/datafiles/thrustcurves/Contrail_H141.eng b/datafiles/thrustcurves/Contrail_H141.eng new file mode 100644 index 000000000..cabed3b84 --- /dev/null +++ b/datafiles/thrustcurves/Contrail_H141.eng @@ -0,0 +1,9 @@ +; +; +H141 38 516 0 0.125 0.612 Contrail_Rockets +0.00223714 265.514 +0.111857 262.066 +1.20134 106.895 +1.25951 55.1717 +1.3557 27.5859 +1.7 0 diff --git a/datafiles/thrustcurves/Contrail_H211.eng b/datafiles/thrustcurves/Contrail_H211.eng new file mode 100644 index 000000000..966d41f5e --- /dev/null +++ b/datafiles/thrustcurves/Contrail_H211.eng @@ -0,0 +1,11 @@ +; +; +H211 38 516 0 0.125 0.612 Contrail_Rockets +0.00111857 531.028 +0.0190157 634.475 +0.0223714 593.096 +0.033557 544.821 +0.296421 317.238 +0.313199 186.205 +0.743848 96.5506 +0.97 0 diff --git a/datafiles/thrustcurves/Contrail_H222.eng b/datafiles/thrustcurves/Contrail_H222.eng new file mode 100644 index 000000000..d70875899 --- /dev/null +++ b/datafiles/thrustcurves/Contrail_H222.eng @@ -0,0 +1,13 @@ +; +;H-222-HP Reload +;38mm/16 inch Case Used +;Medium Nozzle Used For Reload +;140cc of Nitrous Oxide Used +H222 38 406.4 0 0.022 0.52 Contrail_Rockets +0 684.342 +0.0302013 656.968 +0.0525727 574.847 +0.0581655 349.014 +0.346756 260.05 +0.364653 191.616 +0.7 0 diff --git a/datafiles/thrustcurves/Contrail_H246.eng b/datafiles/thrustcurves/Contrail_H246.eng new file mode 100644 index 000000000..0bd0b6398 --- /dev/null +++ b/datafiles/thrustcurves/Contrail_H246.eng @@ -0,0 +1,11 @@ +; +;H-246 HP Reload +;38mm/20 Inch Case Used +;Medium Nozzle Used +;185cc Nitrous Oxide Used +H246 38 508 0 0.022 0.598 Contrail_Rockets +0.00111857 609.064 +0.0123043 499.57 +0.502237 253.206 +0.514541 157.399 +0.9 0 diff --git a/datafiles/thrustcurves/Contrail_H277.eng b/datafiles/thrustcurves/Contrail_H277.eng new file mode 100644 index 000000000..603264841 --- /dev/null +++ b/datafiles/thrustcurves/Contrail_H277.eng @@ -0,0 +1,11 @@ +; +; +H277 38 719 0 0.11 0.71 Contrail_Rockets +0 765.508 +0.0738255 703.44 +0.118568 337.927 +0.917226 179.308 +0.957494 75.8612 +0.995526 41.3788 +1.02908 48.2753 +1.15 0 diff --git a/datafiles/thrustcurves/Contrail_H300.eng b/datafiles/thrustcurves/Contrail_H300.eng new file mode 100644 index 000000000..05f3910e9 --- /dev/null +++ b/datafiles/thrustcurves/Contrail_H300.eng @@ -0,0 +1,10 @@ +; +; +H300 38 516 0 0.11 0.612 Contrail_Rockets +0 558.614 +0.115213 717.233 +0.12528 268.962 +0.214765 248.273 +0.286353 241.376 +0.334452 227.583 +0.62 0 diff --git a/datafiles/thrustcurves/Contrail_H303.eng b/datafiles/thrustcurves/Contrail_H303.eng new file mode 100644 index 000000000..e1cc50583 --- /dev/null +++ b/datafiles/thrustcurves/Contrail_H303.eng @@ -0,0 +1,13 @@ +; +;H-303-PVC Hybrid Motor +;Uses Fast Nozzle +;38mm/20 Inch Hardware +;Uses 185cc Nitrous Oxide +H303 38 508 0 0.023 0.589 Contrail_Rockets +0 663.812 +0.0447427 780.15 +0.108501 704.872 +0.111857 342.171 +0.176734 328.484 +0.196868 307.954 +0.6 0 diff --git a/datafiles/thrustcurves/Contrail_H340.eng b/datafiles/thrustcurves/Contrail_H340.eng new file mode 100644 index 000000000..ab350daf6 --- /dev/null +++ b/datafiles/thrustcurves/Contrail_H340.eng @@ -0,0 +1,10 @@ +; +; +H340 38 711.2 0 0.024 0.816 Contrail_Rockets +0 920.322 +0.0847458 715.806 +0.101695 345.121 +0.683051 332.338 +0.740678 255.645 +0.766102 153.387 +0.95 0 diff --git a/datafiles/thrustcurves/Contrail_I155.eng b/datafiles/thrustcurves/Contrail_I155.eng new file mode 100644 index 000000000..934a4a3fa --- /dev/null +++ b/datafiles/thrustcurves/Contrail_I155.eng @@ -0,0 +1,8 @@ +; +; +I155 38 711.2 0 0.045 0.725 Contrail_Rockets +0.0111857 222.411 +2.71253 150.555 +2.82998 82.121 +2.96421 58.1691 +3.5 0 diff --git a/datafiles/thrustcurves/Contrail_I210.eng b/datafiles/thrustcurves/Contrail_I210.eng new file mode 100644 index 000000000..17ea5733f --- /dev/null +++ b/datafiles/thrustcurves/Contrail_I210.eng @@ -0,0 +1,10 @@ +; +; +I210 38 922 0 0.125 0.87 Contrail_Rockets +0 468.96 +0.464206 386.202 +0.497763 206.894 +2.25391 110.343 +2.34899 41.3788 +2.40492 13.7929 +2.72 0 diff --git a/datafiles/thrustcurves/Contrail_I221.eng b/datafiles/thrustcurves/Contrail_I221.eng new file mode 100644 index 000000000..63fa12da9 --- /dev/null +++ b/datafiles/thrustcurves/Contrail_I221.eng @@ -0,0 +1,9 @@ +; +; +I221 38 719 0 0.125 0.71 Contrail_Rockets +0 482.753 +0.503356 358.616 +0.519016 179.308 +1.49217 103.447 +1.53691 27.5859 +1.74 0 diff --git a/datafiles/thrustcurves/Contrail_I290.eng b/datafiles/thrustcurves/Contrail_I290.eng new file mode 100644 index 000000000..36c1a7a45 --- /dev/null +++ b/datafiles/thrustcurves/Contrail_I290.eng @@ -0,0 +1,15 @@ +; +; +I290 38 914.4 0 0.068 0.884 Contrail_Rockets +0 521.516 +0.0847458 337.451 +0.138983 357.903 +0.19661 398.806 +0.308475 490.838 +0.40339 449.935 +0.589831 357.903 +0.762712 419.258 +0.932203 265.871 +1.08814 163.613 +1.24068 81.8064 +1.5 0 diff --git a/datafiles/thrustcurves/Contrail_I307.eng b/datafiles/thrustcurves/Contrail_I307.eng new file mode 100644 index 000000000..eeabc88d8 --- /dev/null +++ b/datafiles/thrustcurves/Contrail_I307.eng @@ -0,0 +1,12 @@ +; +; +I307 38 922 0 0.11 0.81 Contrail_Rockets +0.00223714 551.717 +0.199105 717.233 +0.210291 386.202 +0.756152 620.682 +0.834452 455.167 +0.941834 310.341 +1.09172 199.998 +1.22371 117.24 +1.85 0 diff --git a/datafiles/thrustcurves/Contrail_I333.eng b/datafiles/thrustcurves/Contrail_I333.eng new file mode 100644 index 000000000..3e7333ba0 --- /dev/null +++ b/datafiles/thrustcurves/Contrail_I333.eng @@ -0,0 +1,12 @@ +; +;I-333-PVC Reload +;38mm/36 Inch Hardware +;Uses Fast Nozzle +;460cc Nitrous Oxide +I333 38 914.4 0 0.068 0.929 Contrail_Rockets +0.00894855 855.427 +0.0290828 881.09 +0.0536913 504.702 +0.604027 342.171 +0.796421 461.931 +1.7 0 diff --git a/datafiles/thrustcurves/Contrail_I400.eng b/datafiles/thrustcurves/Contrail_I400.eng new file mode 100644 index 000000000..6b3495005 --- /dev/null +++ b/datafiles/thrustcurves/Contrail_I400.eng @@ -0,0 +1,14 @@ +; +;I-400-HP +;38mm/36 Inch Hardware +;Uses Fast/X-Fast Nozzle +;460cc Nitrous Oxide +I400 38 914.4 0 0.086 0.925 Contrail_Rockets +0.00447427 667.233 +0.0782998 898.199 +0.116331 598.799 +0.297539 521.811 +0.420582 410.605 +0.559284 487.594 +0.738255 367.834 +1 0 diff --git a/datafiles/thrustcurves/Contrail_I500.eng b/datafiles/thrustcurves/Contrail_I500.eng new file mode 100644 index 000000000..31cfb23eb --- /dev/null +++ b/datafiles/thrustcurves/Contrail_I500.eng @@ -0,0 +1,9 @@ +; +; +I500 38 719 0 0.748 0.8 Contrail_Rockets +0.00111857 1155.16 +0.0201342 706.888 +0.0313199 999.988 +0.574944 103.447 +0.623043 120.688 +0.7 0 diff --git a/datafiles/thrustcurves/Contrail_I727.eng b/datafiles/thrustcurves/Contrail_I727.eng new file mode 100644 index 000000000..31a5ddc43 --- /dev/null +++ b/datafiles/thrustcurves/Contrail_I727.eng @@ -0,0 +1,11 @@ +; +; +I727 38 914.4 0 0.022 0.929 Contrail_Rockets +0.00847458 1278.22 +0.0355932 1661.69 +0.0983051 1508.31 +0.144068 1482.74 +0.171186 1175.97 +0.218644 1022.58 +0.422034 792.499 +0.75 0 diff --git a/datafiles/thrustcurves/Contrail_I747.eng b/datafiles/thrustcurves/Contrail_I747.eng new file mode 100644 index 000000000..76de3965f --- /dev/null +++ b/datafiles/thrustcurves/Contrail_I747.eng @@ -0,0 +1,5 @@ +; +; +I747 38 711.2 0 0.068 0.839 Contrail_Rockets +0 1917.34 +0.45 0 diff --git a/datafiles/thrustcurves/Contrail_J150.eng b/datafiles/thrustcurves/Contrail_J150.eng new file mode 100644 index 000000000..9bd4adef9 --- /dev/null +++ b/datafiles/thrustcurves/Contrail_J150.eng @@ -0,0 +1,11 @@ +; +;J-150-HP +;38mm/36 Inch +;550cc +;Slow Nozzle +J150 38 914.4 0 0.091 0.839 Contrail_Rockets +0 266.893 +2.00224 184.772 +2.75727 150.555 +3.00895 92.3861 +4.1 0 diff --git a/datafiles/thrustcurves/Contrail_J222.eng b/datafiles/thrustcurves/Contrail_J222.eng new file mode 100644 index 000000000..2a3ceb778 --- /dev/null +++ b/datafiles/thrustcurves/Contrail_J222.eng @@ -0,0 +1,13 @@ +; +;J-222-HP Reload +;Medium Nozzle +;38mm/48 Inch Hardware +;830cc +J222 38 1219.2 0 0.091 1.043 Contrail_Rockets +0.00559284 547.473 +0.167785 355.858 +2.86353 191.616 +2.95861 143.712 +3.08725 130.025 +3.46756 95.8079 +4.3 0 diff --git a/datafiles/thrustcurves/Contrail_J234.eng b/datafiles/thrustcurves/Contrail_J234.eng new file mode 100644 index 000000000..574cbd349 --- /dev/null +++ b/datafiles/thrustcurves/Contrail_J234.eng @@ -0,0 +1,11 @@ +; +;J-234-BG Reload +;Slow Nozzle +;54mm/36 Inch Hardware +J234 54 914.4 0 0.177 1.764 Contrail_Rockets +0.00559284 229.255 +0.503356 349.014 +3.47875 208.724 +3.62416 116.338 +3.75839 78.6993 +4.3 0 diff --git a/datafiles/thrustcurves/Contrail_J242.eng b/datafiles/thrustcurves/Contrail_J242.eng new file mode 100644 index 000000000..a0c271f5c --- /dev/null +++ b/datafiles/thrustcurves/Contrail_J242.eng @@ -0,0 +1,8 @@ +; +; +J242 38 1227 0 0.11 1.065 Contrail_Rockets +0.0111857 448.27 +1.73937 268.962 +1.76174 165.515 +2.97539 48.2753 +3.1 0 diff --git a/datafiles/thrustcurves/Contrail_J245.eng b/datafiles/thrustcurves/Contrail_J245.eng new file mode 100644 index 000000000..9fbd976d9 --- /dev/null +++ b/datafiles/thrustcurves/Contrail_J245.eng @@ -0,0 +1,12 @@ +; +;J-245-BG Reload +;Slow Nozzle +;54mm/28 Inch Hardware +J245 54 711.2 0 0.1 1.55 Contrail_Rockets +0 444.822 +0.139821 355.858 +1.05145 307.954 +2.06376 184.772 +2.15884 102.651 +2.28188 68.4342 +2.62 0 diff --git a/datafiles/thrustcurves/Contrail_J246.eng b/datafiles/thrustcurves/Contrail_J246.eng new file mode 100644 index 000000000..3dfff0989 --- /dev/null +++ b/datafiles/thrustcurves/Contrail_J246.eng @@ -0,0 +1,14 @@ +; +;J-246-HP Reload +;38mm/36 Inch Hardware +;550cc +;Medium Nozzle +J246 38 914.4 0 0.068 0.861 Contrail_Rockets +0.0167785 492.726 +0.0279642 328.484 +0.134228 526.943 +0.341163 403.762 +0.520134 349.014 +2.00224 191.616 +2.12528 116.338 +2.8 0 diff --git a/datafiles/thrustcurves/Contrail_J272.eng b/datafiles/thrustcurves/Contrail_J272.eng new file mode 100644 index 000000000..314da9ac7 --- /dev/null +++ b/datafiles/thrustcurves/Contrail_J272.eng @@ -0,0 +1,13 @@ +; +; +J272 54 914.4 0 0.114 1.746 Contrail_Rockets +0.00847458 398.806 +0.169492 572.645 +0.533898 460.161 +0.872881 388.58 +1.05932 357.903 +2.91525 204.516 +3.19492 71.5806 +3.51695 40.9032 +3.63559 51.129 +3.86 0 diff --git a/datafiles/thrustcurves/Contrail_J292.eng b/datafiles/thrustcurves/Contrail_J292.eng new file mode 100644 index 000000000..3b4b919f8 --- /dev/null +++ b/datafiles/thrustcurves/Contrail_J292.eng @@ -0,0 +1,10 @@ +; +; +J292 54 711.2 0 0.136 1.542 Contrail_Rockets +0.00847458 552.193 +0.262712 480.612 +0.423729 419.258 +0.762712 337.451 +1.97458 245.419 +2.07627 143.161 +2.53 0 diff --git a/datafiles/thrustcurves/Contrail_J333.eng b/datafiles/thrustcurves/Contrail_J333.eng new file mode 100644 index 000000000..4a0dba86d --- /dev/null +++ b/datafiles/thrustcurves/Contrail_J333.eng @@ -0,0 +1,10 @@ +; +; +J333 38 1227 0 0.11 1.064 Contrail_Rockets +0 717.233 +0.204139 799.99 +0.752237 448.27 +0.763423 268.962 +2.16443 62.0682 +2.23714 27.5859 +2.4 0 diff --git a/datafiles/thrustcurves/Contrail_J345.eng b/datafiles/thrustcurves/Contrail_J345.eng new file mode 100644 index 000000000..72f66c6fe --- /dev/null +++ b/datafiles/thrustcurves/Contrail_J345.eng @@ -0,0 +1,11 @@ +; +;J-345-PVC +;38mm/48 Inch Hardware +;735cc +;Fast Nozzle +J345 38 1219.2 0 0.098 1.118 Contrail_Rockets +0.00559284 881.09 +0.0782998 667.233 +1.21924 376.388 +1.26398 359.279 +2.7 0 diff --git a/datafiles/thrustcurves/Contrail_J355.eng b/datafiles/thrustcurves/Contrail_J355.eng new file mode 100644 index 000000000..99af48c08 --- /dev/null +++ b/datafiles/thrustcurves/Contrail_J355.eng @@ -0,0 +1,18 @@ +; +; +J355 54 711.2 0 0.09 1.564 Contrail_Rockets +0 562.419 +0.176271 501.064 +0.2 286.322 +0.433898 286.322 +0.688136 337.451 +0.701695 501.064 +0.80678 490.838 +1.00339 521.516 +1.21695 419.258 +1.31186 429.484 +1.37627 460.161 +1.49831 429.484 +1.54576 224.968 +1.64068 122.71 +1.91 0 diff --git a/datafiles/thrustcurves/Contrail_J358.eng b/datafiles/thrustcurves/Contrail_J358.eng new file mode 100644 index 000000000..9f61faa4c --- /dev/null +++ b/datafiles/thrustcurves/Contrail_J358.eng @@ -0,0 +1,12 @@ +; +; +J358 54 914.4 0 0.111 1.743 Contrail_Rockets +0.00847458 726.032 +0.0932203 726.032 +0.110169 501.064 +0.483051 480.612 +0.550847 398.806 +2.23729 286.322 +2.32203 153.387 +2.44915 112.484 +2.69 0 diff --git a/datafiles/thrustcurves/Contrail_J416.eng b/datafiles/thrustcurves/Contrail_J416.eng new file mode 100644 index 000000000..07e4551fe --- /dev/null +++ b/datafiles/thrustcurves/Contrail_J416.eng @@ -0,0 +1,15 @@ +; +; +J416 54 914.4 0 0.158 1.7 Contrail_Rockets +0 787.386 +0.0762712 777.161 +0.211864 572.645 +0.432203 531.741 +0.864407 511.29 +1.26271 480.612 +1.82203 470.387 +2.00847 347.677 +2.13559 276.097 +2.24576 184.064 +2.40678 81.8064 +2.75 0 diff --git a/datafiles/thrustcurves/Contrail_J555.eng b/datafiles/thrustcurves/Contrail_J555.eng new file mode 100644 index 000000000..a1b444eb2 --- /dev/null +++ b/datafiles/thrustcurves/Contrail_J555.eng @@ -0,0 +1,10 @@ +; +; +J555 38 1227 0 0.166 1.132 Contrail_Rockets +0 931.023 +0.0581655 1344.81 +0.277405 810.335 +1.17226 241.376 +1.2774 68.9647 +1.31767 51.7235 +1.6 0 diff --git a/datafiles/thrustcurves/Contrail_J642.eng b/datafiles/thrustcurves/Contrail_J642.eng new file mode 100644 index 000000000..229af7ee5 --- /dev/null +++ b/datafiles/thrustcurves/Contrail_J642.eng @@ -0,0 +1,14 @@ +; +; +J642 54 914.4 0 0.159 1.791 Contrail_Rockets +0.00677966 1482.74 +0.0779661 997.015 +0.471186 1303.79 +0.542373 818.064 +0.633898 741.37 +0.742373 587.983 +1.25085 485.725 +1.29831 332.338 +1.39661 178.951 +1.47458 51.129 +1.72 0 diff --git a/datafiles/thrustcurves/Contrail_J800.eng b/datafiles/thrustcurves/Contrail_J800.eng new file mode 100644 index 000000000..c33d77b7e --- /dev/null +++ b/datafiles/thrustcurves/Contrail_J800.eng @@ -0,0 +1,14 @@ +; +;J-800-HP +;38mm/48 Inch +;685cc +;XXF Nozzle (Short Nozzle) +J800 38 1219.2 0 0.105 1.148 Contrail_Rockets +0.00223714 1830.61 +0.52349 889.644 +0.639821 650.125 +0.740492 444.822 +0.823266 273.737 +0.90604 153.977 +0.997763 136.868 +1.2 0 diff --git a/datafiles/thrustcurves/Contrail_K234.eng b/datafiles/thrustcurves/Contrail_K234.eng new file mode 100644 index 000000000..62ce6e175 --- /dev/null +++ b/datafiles/thrustcurves/Contrail_K234.eng @@ -0,0 +1,13 @@ +; +;K-234-BG Reload +;Slow Nozzle +;54mm/48 Inch Hardware +K234 54 1219.2 0 0.385 2.063 Contrail_Rockets +0 92.3861 +0.234899 396.918 +0.973154 338.749 +5.97315 171.085 +6.05145 106.073 +6.19687 78.6993 +6.37584 54.7473 +7.05 0 diff --git a/datafiles/thrustcurves/Contrail_K265.eng b/datafiles/thrustcurves/Contrail_K265.eng new file mode 100644 index 000000000..14fe5a08b --- /dev/null +++ b/datafiles/thrustcurves/Contrail_K265.eng @@ -0,0 +1,10 @@ +; +; +K265 54 1219.2 0 0.271 2.085 Contrail_Rockets +0 470.387 +2.44068 347.677 +3.91525 224.968 +4.77966 173.839 +5.13559 112.484 +5.33898 51.129 +6.26 0 diff --git a/datafiles/thrustcurves/Contrail_K300.eng b/datafiles/thrustcurves/Contrail_K300.eng new file mode 100644 index 000000000..385b78cba --- /dev/null +++ b/datafiles/thrustcurves/Contrail_K300.eng @@ -0,0 +1,16 @@ +; +;K-300-BS +;75mm/40 Inch Hardware +;2050cc +;Slow Nozzle +K300 75 1016 0 0.181 4.059 Contrail_Rockets +0 431.135 +0.324385 526.943 +0.98434 479.039 +1.1745 369.545 +5 280.58 +5.19016 171.085 +5.35794 102.651 +5.6264 54.7473 +5.79418 27.3737 +6.5 0 diff --git a/datafiles/thrustcurves/Contrail_K321.eng b/datafiles/thrustcurves/Contrail_K321.eng new file mode 100644 index 000000000..42c6698cd --- /dev/null +++ b/datafiles/thrustcurves/Contrail_K321.eng @@ -0,0 +1,16 @@ +; +;K-321-BG Reload +;54mm/48 Inch Hardware +;Medium Nozzle +K321 54 1219.2 0 0.183 2.043 Contrail_Rockets +0.00559284 218.989 +0.218121 410.605 +0.973154 718.559 +0.989933 732.246 +1.05705 444.822 +1.4877 403.762 +3.97092 232.676 +4.11633 88.9644 +4.23378 54.7473 +4.34564 54.7473 +4.9 0 diff --git a/datafiles/thrustcurves/Contrail_K404.eng b/datafiles/thrustcurves/Contrail_K404.eng new file mode 100644 index 000000000..ad9e93328 --- /dev/null +++ b/datafiles/thrustcurves/Contrail_K404.eng @@ -0,0 +1,13 @@ +; +;K-404-Sparky +;75mm/40 Inch Hardware +;2050cc +;Slow Nozzle +K404 75 1016 0 0.318 4.15 Contrail_Rockets +0.0111857 670.655 +4.63087 335.328 +4.80984 205.303 +4.9217 130.025 +5.0783 82.121 +5.26846 41.0605 +6.4 0 diff --git a/datafiles/thrustcurves/Contrail_K456.eng b/datafiles/thrustcurves/Contrail_K456.eng new file mode 100644 index 000000000..72e556e86 --- /dev/null +++ b/datafiles/thrustcurves/Contrail_K456.eng @@ -0,0 +1,11 @@ +; +; +K456 75 813 0 0.58 3.704 Contrail_Rockets +0.00559284 681.026 +0.212528 896.541 +0.503356 775.853 +1.36465 577.579 +1.52685 525.856 +2.51119 370.685 +2.66779 129.309 +3.7 0 diff --git a/datafiles/thrustcurves/Contrail_K630.eng b/datafiles/thrustcurves/Contrail_K630.eng new file mode 100644 index 000000000..1da3faf61 --- /dev/null +++ b/datafiles/thrustcurves/Contrail_K630.eng @@ -0,0 +1,14 @@ +; +;K-630-Sparky Reload +;75mm/41 Inch Hardare +;1400cc +;Medium Nozzle +K630 75 1041.4 0 0.075 3.55 Contrail_Rockets +0.00559284 307.954 +0.0978747 573.136 +0.500559 889.644 +1.75336 667.233 +1.85403 410.605 +1.93792 239.52 +2.04978 128.314 +2.2 0 diff --git a/datafiles/thrustcurves/Contrail_K678.eng b/datafiles/thrustcurves/Contrail_K678.eng new file mode 100644 index 000000000..16febf9bd --- /dev/null +++ b/datafiles/thrustcurves/Contrail_K678.eng @@ -0,0 +1,11 @@ +; +;K-678-Sparky +;75mm/40 Inch Hardware +;2050cc +;Medium Nozzle +K678 75 1016 0 0.827 4.05 Contrail_Rockets +0.00559284 1163.38 +2.21477 444.822 +2.32103 256.628 +2.38814 102.651 +2.8 0 diff --git a/datafiles/thrustcurves/Contrail_K707.eng b/datafiles/thrustcurves/Contrail_K707.eng new file mode 100644 index 000000000..3d6ec1eb2 --- /dev/null +++ b/datafiles/thrustcurves/Contrail_K707.eng @@ -0,0 +1,15 @@ +; +; +K707 75 813 0 0.145 3.674 Contrail_Rockets +0.0466102 281.209 +0.122881 1278.22 +0.165254 894.757 +0.495763 1431.61 +0.618644 1150.4 +0.694915 945.886 +0.834746 920.322 +1.01271 664.677 +1.50847 536.854 +1.62288 281.209 +1.72881 127.822 +2 0 diff --git a/datafiles/thrustcurves/Contrail_K777.eng b/datafiles/thrustcurves/Contrail_K777.eng new file mode 100644 index 000000000..befb6141c --- /dev/null +++ b/datafiles/thrustcurves/Contrail_K777.eng @@ -0,0 +1,13 @@ +; +; +K777 75 1016 0 0.645 4.05 Contrail_Rockets +0 931.023 +0.0950783 965.506 +0.111857 1793.08 +0.167785 1741.36 +0.206935 1344.81 +0.727069 1137.92 +1.00112 810.335 +1.97427 413.788 +2.04698 172.412 +2.6 0 diff --git a/datafiles/thrustcurves/Contrail_L1222.eng b/datafiles/thrustcurves/Contrail_L1222.eng new file mode 100644 index 000000000..f11e7a169 --- /dev/null +++ b/datafiles/thrustcurves/Contrail_L1222.eng @@ -0,0 +1,12 @@ +; +;Contrail Rockets LLC Hybrid Rocket Motor (L1222) +;75mm-3200cc Motor System +;Sparky Hybrid Fuel +;Data Input By Tom R. Sanders of Contrail Rockets +L1222 75 1339.85 0 3.9 4.989 Contrail_Rockets +0 455 +0.25 455 +0.5 2725 +0.75 1816 +2.75 680 +3.1 0 diff --git a/datafiles/thrustcurves/Contrail_L2525.eng b/datafiles/thrustcurves/Contrail_L2525.eng new file mode 100644 index 000000000..032e4879f --- /dev/null +++ b/datafiles/thrustcurves/Contrail_L2525.eng @@ -0,0 +1,6 @@ +; +; +L2525 75 1492.25 0 3.5 5.579 Contrail_Rockets +0 4200 +0.754759 3294.57 +1.9 0 diff --git a/datafiles/thrustcurves/Contrail_L369.eng b/datafiles/thrustcurves/Contrail_L369.eng new file mode 100644 index 000000000..7c1d69d8d --- /dev/null +++ b/datafiles/thrustcurves/Contrail_L369.eng @@ -0,0 +1,12 @@ +; +;L-369-Sparky +;Slow nozzle +;75mm/54 Inch Hardware +;3200cc +L369 75 1371.6 0 0.514 4.8 Contrail_Rockets +0.0223714 540.63 +1.45414 533.787 +8.92617 260.05 +9.08277 130.025 +9.28412 68.4342 +10.6 0 diff --git a/datafiles/thrustcurves/Contrail_L800.eng b/datafiles/thrustcurves/Contrail_L800.eng new file mode 100644 index 000000000..dadaba267 --- /dev/null +++ b/datafiles/thrustcurves/Contrail_L800.eng @@ -0,0 +1,16 @@ +; +;L-800-Sparky +;75mm/54 Inch Hardware +;3200cc +;Medium Nozzle +L800 75 1371.6 0 0.988 4.726 Contrail_Rockets +0.00559284 1351.58 +0.167785 1129.16 +0.329978 1266.03 +0.553691 1248.92 +0.665548 1129.16 +3.48434 821.21 +3.5962 496.148 +3.69687 273.737 +3.83669 153.977 +4.6 0 diff --git a/datafiles/thrustcurves/Contrail_M1575.eng b/datafiles/thrustcurves/Contrail_M1575.eng new file mode 100644 index 000000000..3b4e0b85e --- /dev/null +++ b/datafiles/thrustcurves/Contrail_M1575.eng @@ -0,0 +1,12 @@ +; +;M-1575-Black Gold Reload +;5300cc +;98mm/60 Inch Hardware +M1575 98 1524 0 0.726 10.863 Contrail_Rockets +0.139821 2429.41 +0.503356 2976.89 +2.95302 1402.9 +3.06488 923.861 +3.21029 376.388 +3.31096 205.303 +4.2 0 diff --git a/datafiles/thrustcurves/Contrail_M2700.eng b/datafiles/thrustcurves/Contrail_M2700.eng new file mode 100644 index 000000000..34bc383d9 --- /dev/null +++ b/datafiles/thrustcurves/Contrail_M2700.eng @@ -0,0 +1,18 @@ +; +; +M2700 98 1524 0 0.412 10.432 Contrail_Rockets +0.00847458 2965.48 +0.0508475 3272.26 +0.105932 5930.96 +0.347458 5828.7 +0.504237 6442.25 +0.512712 5726.45 +0.601695 5112.9 +0.745763 3681.29 +0.902542 3067.74 +1.06356 2454.19 +1.18644 1942.9 +1.34322 1738.39 +1.75 715.806 +1.95763 102.258 +2.3 0 diff --git a/datafiles/thrustcurves/Contrail_M2800.eng b/datafiles/thrustcurves/Contrail_M2800.eng new file mode 100644 index 000000000..a1b26b205 --- /dev/null +++ b/datafiles/thrustcurves/Contrail_M2800.eng @@ -0,0 +1,14 @@ +; +;M-2800-Black Gold Reload +;5300cc +;98mm/60 inch Hardware +M2800 98 1524 0 0.476 10.704 Contrail_Rockets +0.00838926 2395.2 +0.251678 2805.8 +0.545302 3695.45 +0.75783 5611.6 +0.911633 4311.35 +1.1745 3558.58 +1.4094 2258.33 +1.70861 1505.55 +2.3 0 diff --git a/datafiles/thrustcurves/Contrail_M711.eng b/datafiles/thrustcurves/Contrail_M711.eng new file mode 100644 index 000000000..9570b1f5f --- /dev/null +++ b/datafiles/thrustcurves/Contrail_M711.eng @@ -0,0 +1,11 @@ +; +;Contrail Rockets LLC Hybrid Rocket Motor. (M-711) +;75-3200cc Hardware Set +;Black Smoke Fuel +M711BS 75 1340 0 4.2 4.9 Contrail_Rockets +0 1140 +1.46697 1069.77 +4 680 +6.47256 589.147 +6.67413 279.07 +7.22 0 diff --git a/datafiles/thrustcurves/Contrail_O6300.eng b/datafiles/thrustcurves/Contrail_O6300.eng new file mode 100644 index 000000000..3d0e4fd28 --- /dev/null +++ b/datafiles/thrustcurves/Contrail_O6300.eng @@ -0,0 +1,13 @@ +; +; +O6300 152 1828.8 0 3.175 28.576 Contrail_Rockets +0.0338983 12271 +0.728814 9714.51 +1.65254 9203.22 +2.37288 8947.57 +2.51695 6646.77 +2.78814 4601.61 +2.99153 4857.25 +3.27966 3579.03 +3.61017 1278.22 +4.29 0 diff --git a/datafiles/thrustcurves/Ellis_G20.eng b/datafiles/thrustcurves/Ellis_G20.eng new file mode 100644 index 000000000..c8fc64894 --- /dev/null +++ b/datafiles/thrustcurves/Ellis_G20.eng @@ -0,0 +1,10 @@ +; +G20 29 149 3 0.0729 0.1179 Ellis_Mountain +0.0463679 46.6843 +0.278207 30.3888 +0.479134 26.8655 +1.00464 24.6634 +3.47759 22.0209 +4.32767 13.653 +5.11592 3.08293 +5.47 0 diff --git a/datafiles/thrustcurves/Ellis_G35.eng b/datafiles/thrustcurves/Ellis_G35.eng new file mode 100644 index 000000000..2c6da54f5 --- /dev/null +++ b/datafiles/thrustcurves/Ellis_G35.eng @@ -0,0 +1,10 @@ +; +;Ellis Mountain G35 Single Use Motor +G35EM 29 165 6-10 0.082 0.135 Ellis_Mountain +0.01 51.12 +0.04 57.55 +0.08 43.78 +2.73 28.16 +3.28 28.16 +3.78 6.73 +4 0 diff --git a/datafiles/thrustcurves/Ellis_G37.eng b/datafiles/thrustcurves/Ellis_G37.eng new file mode 100644 index 000000000..1df8f92c1 --- /dev/null +++ b/datafiles/thrustcurves/Ellis_G37.eng @@ -0,0 +1,14 @@ +; +; +G37 24 181 6-10-100 0.068 0.1133 Ellis_Mountain +0.0231839 69.586 +0.162287 55.9331 +0.332303 48.0056 +0.502318 44.9226 +0.996909 40.9589 +1.49923 38.7568 +2.00155 34.3526 +2.49614 28.1868 +2.75116 18.4976 +2.99845 5.28502 +3.1 0 diff --git a/datafiles/thrustcurves/Ellis_H275.eng b/datafiles/thrustcurves/Ellis_H275.eng new file mode 100644 index 000000000..1d83b445f --- /dev/null +++ b/datafiles/thrustcurves/Ellis_H275.eng @@ -0,0 +1,11 @@ +; +; +H275 29 275 10 0.142 0.255 Ellis_Mountain +0.0123648 792.752 +0.015456 356.739 +0.197836 312.697 +0.797527 268.655 +0.911901 255.442 +0.992272 123.317 +1.04173 39.6376 +1.1 0 diff --git a/datafiles/thrustcurves/Ellis_H48.eng b/datafiles/thrustcurves/Ellis_H48.eng new file mode 100644 index 000000000..a6d69fbc9 --- /dev/null +++ b/datafiles/thrustcurves/Ellis_H48.eng @@ -0,0 +1,20 @@ +; +;Ellis Mountain Rocket Works +;H48 Single Use motor +H48 38 200 8-100 0.154 0.292 Ellis_Mountain +0.05 101.5 +0.1 101.5 +0.21 92.18 +0.46 86.48 +0.74 83.38 +1 80 +1.49 74.57 +1.99 68.36 +2.48 63.18 +2.99 56.45 +3.2 34.18 +3.5 18 +3.69 13.46 +4 11 +4.36 7.77 +4.4 0 diff --git a/datafiles/thrustcurves/Ellis_H50.eng b/datafiles/thrustcurves/Ellis_H50.eng new file mode 100644 index 000000000..717bcd09e --- /dev/null +++ b/datafiles/thrustcurves/Ellis_H50.eng @@ -0,0 +1,18 @@ +; +;Ellis Mountain Rocket Works +;H50 Single Use motor +H50 29 279 6-10 0.163 0.3 Ellis_Mountain +0.01 63.67 +0.17 108.9 +0.27 94.9 +0.47 81.43 +0.79 71.02 +1.27 64.9 +1.97 60.61 +2.56 56.94 +3.01 52.04 +3.52 45.31 +3.97 34.9 +4.49 18.37 +4.97 4.9 +5.28 0 diff --git a/datafiles/thrustcurves/Ellis_I130.eng b/datafiles/thrustcurves/Ellis_I130.eng new file mode 100644 index 000000000..005f74f35 --- /dev/null +++ b/datafiles/thrustcurves/Ellis_I130.eng @@ -0,0 +1,12 @@ +; +; +I130 38 330 100 0.308 0.625 Ellis_Mountain +0.015456 266.453 +0.0540958 160.753 +0.502318 169.561 +2.23338 180.571 +2.48841 149.742 +2.99073 136.53 +3.49304 77.0732 +4.01082 26.4251 +4.43 0 diff --git a/datafiles/thrustcurves/Ellis_I134.eng b/datafiles/thrustcurves/Ellis_I134.eng new file mode 100644 index 000000000..95a6c9adf --- /dev/null +++ b/datafiles/thrustcurves/Ellis_I134.eng @@ -0,0 +1,14 @@ +; +;Ellis Mountain Rocket Works +;I134 38mm Single Use motor +I134 38 355 15 0.2807 0.5812 Ellis_Mountain +0.1 268.8 +0.2 138 +1 116 +2 102 +3 85 +4 67 +4.65 16.46 +4.82 6.86 +5.07 6.86 +5.15 0 diff --git a/datafiles/thrustcurves/Ellis_I150.eng b/datafiles/thrustcurves/Ellis_I150.eng new file mode 100644 index 000000000..936cd57e5 --- /dev/null +++ b/datafiles/thrustcurves/Ellis_I150.eng @@ -0,0 +1,30 @@ +; Ellis Mountain I150 +; Copyright Tripoli Motor Testing 1998 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +I150 38 229 0 0.172032 0.425152 EM + 0.050 101.298 + 0.152 159.193 + 0.255 169.686 + 0.358 179.603 + 0.460 188.152 + 0.564 193.364 + 0.667 204.520 + 0.769 212.046 + 0.872 212.937 + 0.975 208.076 + 1.077 196.555 + 1.180 191.025 + 1.283 186.106 + 1.386 181.835 + 1.490 177.947 + 1.592 175.877 + 1.695 173.744 + 1.798 170.664 + 1.900 161.823 + 2.003 149.111 + 2.106 124.923 + 2.208 68.392 + 2.311 20.122 + 2.415 7.794 + 2.518 4.464 + 2.621 0.000 diff --git a/datafiles/thrustcurves/Ellis_I160.eng b/datafiles/thrustcurves/Ellis_I160.eng new file mode 100644 index 000000000..659ce054a --- /dev/null +++ b/datafiles/thrustcurves/Ellis_I160.eng @@ -0,0 +1,30 @@ +; Ellis Mountain I160 +; Copyright Tripoli Motor Testing 1998 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +I160 38 280 0 0.235648 0.528192 EM + 0.068 169.405 + 0.206 199.425 + 0.346 205.072 + 0.485 206.075 + 0.624 205.840 + 0.763 204.052 + 0.902 200.850 + 1.042 200.885 + 1.180 203.053 + 1.319 204.157 + 1.458 206.392 + 1.598 210.051 + 1.736 212.769 + 1.875 211.177 + 2.015 207.500 + 2.154 189.766 + 2.293 136.149 + 2.431 52.306 + 2.571 42.841 + 2.710 41.803 + 2.849 33.042 + 2.987 24.614 + 3.127 17.154 + 3.267 7.477 + 3.406 1.777 + 3.546 0.000 diff --git a/datafiles/thrustcurves/Ellis_I230.eng b/datafiles/thrustcurves/Ellis_I230.eng new file mode 100644 index 000000000..2d12b4953 --- /dev/null +++ b/datafiles/thrustcurves/Ellis_I230.eng @@ -0,0 +1,30 @@ +; Ellis Mountain I230 +; Copyright Tripoli Motor Testing 1998 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +I230 38 331 0 0.282688 0.620928 EM + 0.058 292.627 + 0.178 317.660 + 0.298 309.874 + 0.418 305.243 + 0.537 299.679 + 0.657 298.170 + 0.777 294.591 + 0.897 293.800 + 1.018 289.736 + 1.138 288.222 + 1.257 284.614 + 1.377 281.149 + 1.497 274.879 + 1.617 269.775 + 1.736 258.925 + 1.856 242.249 + 1.976 207.607 + 2.097 136.698 + 2.217 86.506 + 2.336 74.324 + 2.456 51.246 + 2.576 45.546 + 2.696 27.050 + 2.816 6.382 + 2.936 1.423 + 3.057 0.000 diff --git a/datafiles/thrustcurves/Ellis_I69.eng b/datafiles/thrustcurves/Ellis_I69.eng new file mode 100644 index 000000000..c0d600535 --- /dev/null +++ b/datafiles/thrustcurves/Ellis_I69.eng @@ -0,0 +1,19 @@ +; +;Ellis Mountain Rocket Works +;I69 38mm Single Use motor +I69 29 406 10 0.236 0.4 Ellis_Mountain +0.05 78.67 +0.1 149.7 +0.25 133.5 +0.49 111.51 +0.75 100 +1.07 93.18 +1.48 87.83 +2 82.49 +2.5 78 +2.99 73.32 +3.5 64.5 +3.99 48.88 +4.5 29.79 +4.99 9.17 +5.28 0 diff --git a/datafiles/thrustcurves/Ellis_J110.eng b/datafiles/thrustcurves/Ellis_J110.eng new file mode 100644 index 000000000..394d41d91 --- /dev/null +++ b/datafiles/thrustcurves/Ellis_J110.eng @@ -0,0 +1,11 @@ +; +; +J110 54 276.2 100 0.45359 0.8754 Ellis_Mountain +0.108192 193.784 +0.386399 147.54 +1.00464 139.833 +4.034 116.711 +5.00773 94.6899 +6.01236 67.1637 +6.53787 37.4355 +6.8 0 diff --git a/datafiles/thrustcurves/Ellis_J148.eng b/datafiles/thrustcurves/Ellis_J148.eng new file mode 100644 index 000000000..2b37673c6 --- /dev/null +++ b/datafiles/thrustcurves/Ellis_J148.eng @@ -0,0 +1,12 @@ +; +; +J148 54 355.6 14 0.67 1.179 Ellis_Mountain +0.139104 218.007 +0.231839 183.875 +0.448223 171.763 +1.00464 170.662 +2.10201 170.662 +5.02318 147.54 +5.31685 133.226 +5.67233 49.547 +6.1 0 diff --git a/datafiles/thrustcurves/Ellis_J228.eng b/datafiles/thrustcurves/Ellis_J228.eng new file mode 100644 index 000000000..54560e8d6 --- /dev/null +++ b/datafiles/thrustcurves/Ellis_J228.eng @@ -0,0 +1,13 @@ +; +; +J228 38 562 6 0.27 0.8391 Ellis_Mountain +0.0309119 665.031 +0.0927357 444.822 +0.262751 356.739 +0.664606 343.526 +0.989181 317.101 +1.96291 259.847 +2.99845 193.784 +4.01855 118.913 +4.99227 35.2334 +5.2 0 diff --git a/datafiles/thrustcurves/Ellis_J270.eng b/datafiles/thrustcurves/Ellis_J270.eng new file mode 100644 index 000000000..02d8cc509 --- /dev/null +++ b/datafiles/thrustcurves/Ellis_J270.eng @@ -0,0 +1,30 @@ +; Ellis Mountain J270 +; Copyright Tripoli Motor Testing 1998 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +J270 38 384 0 0.341824 0.711872 EM + 0.057 357.607 + 0.175 386.516 + 0.294 368.069 + 0.412 360.627 + 0.530 356.068 + 0.648 353.900 + 0.767 351.910 + 0.885 349.900 + 1.003 348.675 + 1.121 347.552 + 1.240 343.075 + 1.358 338.000 + 1.476 330.566 + 1.594 315.474 + 1.712 293.325 + 1.831 266.102 + 1.949 184.040 + 2.067 131.638 + 2.185 109.171 + 2.304 89.570 + 2.422 74.945 + 2.540 55.700 + 2.658 31.860 + 2.777 17.751 + 2.896 10.109 + 3.015 0.000 diff --git a/datafiles/thrustcurves/Ellis_J330.eng b/datafiles/thrustcurves/Ellis_J330.eng new file mode 100644 index 000000000..dae2cea0e --- /dev/null +++ b/datafiles/thrustcurves/Ellis_J330.eng @@ -0,0 +1,30 @@ +; Ellis Mountain J330 +; Copyright Tripoli Motor Testing 1998 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +J330 38 433 0 0.407232 0.820736 EM + 0.055 482.013 + 0.169 515.156 + 0.283 509.959 + 0.398 511.485 + 0.512 509.155 + 0.626 503.627 + 0.740 495.461 + 0.854 486.118 + 0.969 477.786 + 1.083 472.073 + 1.197 455.861 + 1.310 433.714 + 1.425 407.542 + 1.540 367.945 + 1.654 271.221 + 1.768 203.711 + 1.881 152.800 + 1.996 106.108 + 2.110 91.404 + 2.225 72.286 + 2.339 63.983 + 2.452 61.809 + 2.567 42.010 + 2.681 16.437 + 2.796 4.496 + 2.910 0.000 diff --git a/datafiles/thrustcurves/Ellis_K475.eng b/datafiles/thrustcurves/Ellis_K475.eng new file mode 100644 index 000000000..3fb8edb67 --- /dev/null +++ b/datafiles/thrustcurves/Ellis_K475.eng @@ -0,0 +1,13 @@ +; +; +K475 54 663.6 14 1.035 2.168 Ellis_Mountain +0.0463679 797.157 +0.15456 616.585 +0.278207 585.756 +0.479134 568.139 +2.92117 576.948 +3.29212 568.139 +4.00309 303.888 +4.51314 224.613 +5.02318 74.8711 +5.5 0 diff --git a/datafiles/thrustcurves/Ellis_L330.eng b/datafiles/thrustcurves/Ellis_L330.eng new file mode 100644 index 000000000..4a6715424 --- /dev/null +++ b/datafiles/thrustcurves/Ellis_L330.eng @@ -0,0 +1,30 @@ +; Ellis Mountain L330 +; Copyright Tripoli Motor Testing 1998 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +L330 76 381 0 1.46944 2.67008 EM + 0.194 298.963 + 0.584 378.807 + 0.975 376.204 + 1.366 382.475 + 1.757 391.163 + 2.148 399.442 + 2.539 406.048 + 2.930 407.731 + 3.321 405.666 + 3.711 400.636 + 4.103 393.384 + 4.494 384.520 + 4.884 377.009 + 5.275 368.385 + 5.666 359.041 + 6.057 350.117 + 6.448 341.587 + 6.839 337.109 + 7.230 300.039 + 7.621 194.602 + 8.011 123.445 + 8.403 66.942 + 8.794 32.233 + 9.184 8.248 + 9.576 1.563 + 9.968 0.000 diff --git a/datafiles/thrustcurves/Ellis_L600.eng b/datafiles/thrustcurves/Ellis_L600.eng new file mode 100644 index 000000000..3aa297fcb --- /dev/null +++ b/datafiles/thrustcurves/Ellis_L600.eng @@ -0,0 +1,30 @@ +; Ellis Mountain L600 +; Copyright Tripoli Motor Testing 1998 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +L600 76 584 0 2.4407 4.11981 EM + 0.186 829.668 + 0.561 773.861 + 0.936 767.837 + 1.313 755.034 + 1.689 736.454 + 2.064 722.717 + 2.440 706.215 + 2.816 688.253 + 3.191 673.457 + 3.567 660.981 + 3.943 648.124 + 4.318 634.689 + 4.694 622.058 + 5.070 607.970 + 5.445 594.926 + 5.821 583.003 + 6.197 573.084 + 6.572 553.530 + 6.948 399.379 + 7.324 270.410 + 7.699 211.401 + 8.075 144.237 + 8.451 74.227 + 8.826 19.378 + 9.202 4.274 + 9.578 0.000 diff --git a/datafiles/thrustcurves/Ellis_M1000.eng b/datafiles/thrustcurves/Ellis_M1000.eng new file mode 100644 index 000000000..a19dda8b1 --- /dev/null +++ b/datafiles/thrustcurves/Ellis_M1000.eng @@ -0,0 +1,30 @@ +; Ellis Mountain M1000 +; Copyright Tripoli Motor Testing 1998 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +M1000 76 787 0 3.47514 5.5776 EM + 0.159 1897.088 + 0.481 1606.200 + 0.803 1441.676 + 1.125 1360.014 + 1.447 1299.506 + 1.769 1259.449 + 2.091 1231.131 + 2.413 1202.529 + 2.735 1179.968 + 3.057 1154.573 + 3.379 1108.815 + 3.701 1075.453 + 4.023 1045.316 + 4.345 1010.304 + 4.667 951.184 + 4.989 860.548 + 5.310 727.369 + 5.633 595.659 + 5.955 518.911 + 6.277 439.902 + 6.599 347.743 + 6.921 239.388 + 7.243 144.608 + 7.565 75.112 + 7.887 33.539 + 8.210 0.000 diff --git a/datafiles/thrustcurves/Estes_1/2A3.eng b/datafiles/thrustcurves/Estes_1/2A3.eng new file mode 100644 index 000000000..e947f919b --- /dev/null +++ b/datafiles/thrustcurves/Estes_1/2A3.eng @@ -0,0 +1,36 @@ +; +;Estes 1/2A3T RASP.ENG file made from NAR published data +;File produced October 3, 2000 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +1/2A3T 13 45 2-4 0.002 0.0066 Estes +0.024 0.501 +0.042 1.454 +0.064 3.009 +0.076 4.062 +0.088 4.914 +0.093 5.065 +0.103 6.068 +0.112 6.87 +0.117 7.021 +0.126 7.62 +0.137 7.472 +0.146 6.87 +0.153 6.118 +0.159 5.065 +0.166 4.363 +0.179 3.66 +0.197 2.908 +0.222 2.256 +0.25 2.156 +0.277 2.106 +0.294 2.056 +0.304 2.156 +0.316 1.955 +0.326 1.554 +0.339 1.053 +0.35 0.651 +0.36 0 diff --git a/datafiles/thrustcurves/Estes_1/2A6.eng b/datafiles/thrustcurves/Estes_1/2A6.eng new file mode 100644 index 000000000..716ba46a9 --- /dev/null +++ b/datafiles/thrustcurves/Estes_1/2A6.eng @@ -0,0 +1,29 @@ +; +;Estes 1/2A6 RASP.ENG file made from NAR published data +;File produced October 3, 2000 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +1/2A6 18 70 2 0.0026 0.0138 Estes +0.031 0.404 +0.064 1.258 +0.096 2.263 +0.124 3.467 +0.149 4.72 +0.172 6.023 +0.196 7.027 +0.21 7.528 +0.225 7.86 +0.235 7.482 +0.244 6.683 +0.254 5.685 +0.263 4.487 +0.269 4.087 +0.279 3.039 +0.29 1.79 +0.297 1.042 +0.306 0.593 +0.314 0.344 +0.33 0 diff --git a/datafiles/thrustcurves/Estes_1/4A3.eng b/datafiles/thrustcurves/Estes_1/4A3.eng new file mode 100644 index 000000000..e74ab247a --- /dev/null +++ b/datafiles/thrustcurves/Estes_1/4A3.eng @@ -0,0 +1,34 @@ +;Estes 1/4A3T RASP.ENG file made from NAR published data +;File produced October 3, 2000 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +1/4A3T 13 45 3 0.00083 0.0061 Estes +0.016 0.243 +0.044 1.164 +0.08 2.698 +0.088 2.851 +0.096 3.312 +0.105 3.804 +0.116 4.325 +0.129 4.754 +0.131 4.754 +0.135 4.95 +0.139 4.815 +0.143 4.814 +0.149 4.66 +0.157 4.289 +0.173 3.548 +0.187 2.808 +0.194 2.592 +0.197 2.13 +0.202 1.913 +0.206 1.512 +0.213 1.389 +0.218 1.112 +0.227 0.802 +0.236 0.493 +0.241 0.277 +0.25 0 diff --git a/datafiles/thrustcurves/Estes_A10.eng b/datafiles/thrustcurves/Estes_A10.eng new file mode 100644 index 000000000..064f78c85 --- /dev/null +++ b/datafiles/thrustcurves/Estes_A10.eng @@ -0,0 +1,29 @@ +; +;Estes A10T RASP.ENG file made from NAR published data +;File produced October 3, 2000 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +A10T 13 45 3-100 0.0038 0.00525 Estes +0.026 0.478 +0.055 1.919 +0.093 4.513 +0.124 8.165 +0.146 10.956 +0.166 12.64 +0.179 11.046 +0.194 7.966 +0.203 6.042 +0.209 3.154 +0.225 1.421 +0.26 1.225 +0.333 1.41 +0.456 1.206 +0.575 1.195 +0.663 1.282 +0.76 1.273 +0.811 1.268 +0.828 0.689 +0.85 0 diff --git a/datafiles/thrustcurves/Estes_A3.eng b/datafiles/thrustcurves/Estes_A3.eng new file mode 100644 index 000000000..46d406d26 --- /dev/null +++ b/datafiles/thrustcurves/Estes_A3.eng @@ -0,0 +1,33 @@ +; +;Estes A3T RASP.ENG file made from NAR published data +;File produced October 3, 2000 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +A3T 13 45 4 0.0033 0.0085 Estes +0.024 0.195 +0.048 0.899 +0.086 2.658 +0.11 4.183 +0.14 5.83 +0.159 5.395 +0.18 4.301 +0.199 3.635 +0.215 2.736 +0.234 2.267 +0.258 2.15 +0.315 2.072 +0.441 1.993 +0.554 2.033 +0.605 2.072 +0.673 1.954 +0.764 1.954 +0.874 2.072 +0.931 2.15 +0.953 2.072 +0.966 1.719 +0.977 1.173 +0.993 0.547 +1.01 0 diff --git a/datafiles/thrustcurves/Estes_A8.eng b/datafiles/thrustcurves/Estes_A8.eng new file mode 100644 index 000000000..22ab10998 --- /dev/null +++ b/datafiles/thrustcurves/Estes_A8.eng @@ -0,0 +1,32 @@ +; +;Estes A8 RASP.ENG file made from NAR published data +;File produced October 3, 2000 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +A8 18 70 3-5 0.0033 0.01635 Estes +0.041 0.512 +0.084 2.115 +0.127 4.358 +0.166 6.794 +0.192 8.588 +0.206 9.294 +0.226 9.73 +0.236 8.845 +0.247 7.179 +0.261 5.063 +0.277 3.717 +0.306 3.205 +0.351 2.884 +0.405 2.499 +0.467 2.371 +0.532 2.307 +0.589 2.371 +0.632 2.371 +0.652 2.243 +0.668 1.794 +0.684 1.153 +0.703 0.448 +0.73 0 diff --git a/datafiles/thrustcurves/Estes_B4.eng b/datafiles/thrustcurves/Estes_B4.eng new file mode 100644 index 000000000..64b099809 --- /dev/null +++ b/datafiles/thrustcurves/Estes_B4.eng @@ -0,0 +1,34 @@ +; +;Estes B4 RASP.ENG file made from NAR published data +;File produced October 3, 2000 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +B4 18 70 2-4 0.006 0.0189 Estes +0.02 0.418 +0.04 1.673 +0.065 4.076 +0.085 6.69 +0.105 9.304 +0.119 11.496 +0.136 12.75 +0.153 11.916 +0.173 10.666 +0.187 9.304 +0.198 7.214 +0.207 5.645 +0.226 4.809 +0.258 4.182 +0.326 3.763 +0.422 3.554 +0.549 3.345 +0.665 3.345 +0.776 3.345 +0.863 3.345 +0.94 3.449 +0.991 3.449 +1.002 2.404 +1.01 1.254 +1.03 0 diff --git a/datafiles/thrustcurves/Estes_B6.eng b/datafiles/thrustcurves/Estes_B6.eng new file mode 100644 index 000000000..b7204010e --- /dev/null +++ b/datafiles/thrustcurves/Estes_B6.eng @@ -0,0 +1,26 @@ +; Estes B6-0 from NAR data by Mark Koelsch +B6-0 18 70 0 0.0056 0.0156 E + 0.036 1.364 + 0.064 2.727 + 0.082 4.215 + 0.111 6.694 + 0.135 9.05 + 0.146 9.545 + 0.172 11.901 + 0.181 12.149 + 0.191 11.901 + 0.211 9.174 + 0.239 7.314 + 0.264 6.074 + 0.275 5.95 + 0.333 5.207 + 0.394 4.835 + 0.445 4.835 + 0.556 4.339 + 0.667 4.587 + 0.723 4.339 + 0.78 4.339 + 0.793 4.091 + 0.812 2.603 + 0.833 1.24 + 0.857 0.0 diff --git a/datafiles/thrustcurves/Estes_C11.eng b/datafiles/thrustcurves/Estes_C11.eng new file mode 100644 index 000000000..7e9a0283e --- /dev/null +++ b/datafiles/thrustcurves/Estes_C11.eng @@ -0,0 +1,36 @@ +; +;ESTES C11 RASP.ENG file made from NAR published data +;File produced JANUARY 1, 2002 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +C11 24 70 0-3-5-7 0.012 0.0353 Estes +0.034 1.692 +0.066 3.782 +0.107 7.566 +0.145 10.946 +0.183 14.832 +0.214 17.618 +0.226 18.213 +0.256 20.107 +0.281 21.208 +0.298 21.73 +0.306 20.206 +0.323 17.321 +0.337 14.931 +0.358 13.236 +0.385 11.947 +0.413 11.65 +0.468 10.946 +0.539 10.45 +0.619 10.648 +0.683 10.648 +0.715 10.648 +0.726 10.053 +0.74 8.163 +0.758 5.773 +0.778 3.185 +0.795 1.394 +0.81 0 diff --git a/datafiles/thrustcurves/Estes_C5.eng b/datafiles/thrustcurves/Estes_C5.eng new file mode 100644 index 000000000..a5ff56370 --- /dev/null +++ b/datafiles/thrustcurves/Estes_C5.eng @@ -0,0 +1,27 @@ +; +;Estes C5 RASP.ENG file made from NAR published data +;File produced October 3, 2000 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +C5 18 70 3 0.0113 0.0248 Estes +0.042 2.195 +0.107 9.118 +0.159 16.213 +0.21 21.85 +0.233 18.407 +0.27 13.677 +0.289 9.793 +0.303 7.092 +0.326 5.065 +0.401 4.39 +0.55 3.883 +0.802 3.714 +1.026 3.883 +1.291 3.883 +1.524 4.221 +1.683 4.221 +1.702 2.195 +1.73 0 diff --git a/datafiles/thrustcurves/Estes_C6.eng b/datafiles/thrustcurves/Estes_C6.eng new file mode 100644 index 000000000..f7cf3a389 --- /dev/null +++ b/datafiles/thrustcurves/Estes_C6.eng @@ -0,0 +1,33 @@ +; +;Estes C6 RASP.ENG file made from NAR published data +;File produced October 3, 2000 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +C6 18 70 0-3-5-7 0.0108 0.0231 Estes +0.031 0.946 +0.092 4.826 +0.139 9.936 +0.192 14.09 +0.209 11.446 +0.231 7.381 +0.248 6.151 +0.292 5.489 +0.37 4.921 +0.475 4.448 +0.671 4.258 +0.702 4.542 +0.723 4.164 +0.85 4.448 +1.063 4.353 +1.211 4.353 +1.242 4.069 +1.303 4.258 +1.468 4.353 +1.656 4.448 +1.821 4.448 +1.834 2.933 +1.847 1.325 +1.86 0 diff --git a/datafiles/thrustcurves/Estes_D11.eng b/datafiles/thrustcurves/Estes_D11.eng new file mode 100644 index 000000000..ca69c1a9b --- /dev/null +++ b/datafiles/thrustcurves/Estes_D11.eng @@ -0,0 +1,32 @@ +; +;Estes D11 RASP.ENG file made from NAR published data +;File produced October 3, 2000 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +D11 24 70 100 0.0245 0.0448 Estes +0.033 2.393 +0.084 5.783 +0.144 12.17 +0.214 20.757 +0.261 24.35 +0.289 26.01 +0.311 23.334 +0.325 18.532 +0.338 14.536 +0.356 12.331 +0.398 10.72 +0.48 9.303 +0.618 8.676 +0.761 8.247 +0.955 8.209 +1.222 7.955 +1.402 8.319 +1.54 8.291 +1.701 8.459 +1.784 8.442 +1.803 6.239 +1.834 3.033 +1.86 0 diff --git a/datafiles/thrustcurves/Estes_D12.eng b/datafiles/thrustcurves/Estes_D12.eng new file mode 100644 index 000000000..0d9392912 --- /dev/null +++ b/datafiles/thrustcurves/Estes_D12.eng @@ -0,0 +1,29 @@ +; +;Estes D12 RASP.ENG file made from NAR published data +;File produced October 3, 2000 +;The total impulse, peak thrust, average thrust and burn time are +;the same as the averaged static test data on the NAR web site in +;the certification file. The curve drawn with these data points is as +;close to the certification curve as can be with such a limited +;number of points (32) allowed with wRASP up to v1.6. +D12 24 70 0-3-5-7 0.0211 0.0426 Estes +0.049 2.569 +0.116 9.369 +0.184 17.275 +0.237 24.258 +0.282 29.73 +0.297 27.01 +0.311 22.589 +0.322 17.99 +0.348 14.126 +0.386 12.099 +0.442 10.808 +0.546 9.876 +0.718 9.306 +0.879 9.105 +1.066 8.901 +1.257 8.698 +1.436 8.31 +1.59 8.294 +1.612 4.613 +1.65 0 diff --git a/datafiles/thrustcurves/Estes_E9.eng b/datafiles/thrustcurves/Estes_E9.eng new file mode 100644 index 000000000..c7e56a78e --- /dev/null +++ b/datafiles/thrustcurves/Estes_E9.eng @@ -0,0 +1,17 @@ +; Estes E9-0 by Mark Koelsch from NAR data +E9-0 24 95 0 0.0358 0.056799999999999996 E + 0.046 1.913 + 0.235 16.696 + 0.273 18.435 + 0.326 14.957 + 0.38 12.174 + 0.44 10.435 + 0.835 9.043 + 1.093 8.87 + 1.496 8.696 + 1.997 8.696 + 2.498 8.696 + 3.014 9.217 + 3.037 5.043 + 3.067 1.217 + 3.09 0.0 diff --git a/datafiles/thrustcurves/FALSE-apogee.eng b/datafiles/thrustcurves/FALSE-apogee.eng new file mode 100644 index 000000000..52aba5cdb --- /dev/null +++ b/datafiles/thrustcurves/FALSE-apogee.eng @@ -0,0 +1,18 @@ +; False data to test 1/2/4A-motors +1/2A3 11 58 0-3-5-7 0.003 0.0067 Apogee +0.014 0.241 +0.036 0.895 +0.064 2.618 +0.1 4.82 +0.111 4.133 +0.125 2.687 +0.139 2.307 +; More false data +1/4A5 11 58 0-3-5-7 0.003 0.0067 Apogee +0.014 0.241 +0.036 0.895 +0.064 2.618 +0.1 4.82 +0.111 4.133 +0.125 2.687 +0.139 2.307 diff --git a/datafiles/thrustcurves/GR_K555.eng b/datafiles/thrustcurves/GR_K555.eng new file mode 100644 index 000000000..c39971240 --- /dev/null +++ b/datafiles/thrustcurves/GR_K555.eng @@ -0,0 +1,26 @@ +;The K555GT "Green Tornado" motor is a green flame, low smoke propellant. +;This reload produces a 9% "K" motor with 1397 N-seconds of total impulse, +;maximum thrust of 645.3 Newtons, and an average thrust of 556 Newtons, +;for a 2.51 second burn time. +K555GT 54 430 1000 0.78 1.52 Gorilla_Motors +0.025 267 +0.05 338.2 +0.1 471.7 +0.12 498.4 +0.15 511.75 +0.18 522.875 +0.2 534 +0.7 631.9 +0.75 636.35 +0.9 645.25 +1.15 636.35 +1.57 623 +1.87 614.1 +2.17 600.75 +2.25 578.5 +2.27 480.6 +2.3 356 +2.35 178 +2.45 66.75 +2.51 0 +; diff --git a/datafiles/thrustcurves/Hypertek_I130.eng b/datafiles/thrustcurves/Hypertek_I130.eng new file mode 100644 index 000000000..774916171 --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_I130.eng @@ -0,0 +1,17 @@ +;hand entered from Cesaroni (Mike Dennett) curve data +;Andrew MacMillen NAR 77472 2/5/02 +;NOTE: NOT CTI OR TMT APPROVED +;Hypertek 300CC098J +I130 54 521 100 0.298 1.049 HyperTek +0.05 200 +0.1 223 +0.5 205 +1 187 +1.5 169 +2 151 +2.25 143 +2.4 89 +2.5 71 +3 40 +3.5 18 +4 0 diff --git a/datafiles/thrustcurves/Hypertek_I136.eng b/datafiles/thrustcurves/Hypertek_I136.eng new file mode 100644 index 000000000..1cfe087e1 --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_I136.eng @@ -0,0 +1,18 @@ +; +;Hypertek I136 Data entered by Tim Van Milligan +;For RockSim www.RockSim.com +;File Created March 2, 2005 +;Data from Mike Dennett at Hypertek +I136 54 546 100 0.283 1.001 Hypertek +0.155 256.236 +0.5 232.756 +1 212.85 +1.5 196.005 +2 174.976 +2.21 163.338 +2.4 100.912 +2.5 83.813 +3 42.468 +3.5 19.754 +3.7 15.262 +3.8 0 diff --git a/datafiles/thrustcurves/Hypertek_I145.eng b/datafiles/thrustcurves/Hypertek_I145.eng new file mode 100644 index 000000000..633d719bb --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_I145.eng @@ -0,0 +1,20 @@ +; +;Hypertek I145 Data entered by Tim Van Milligan +;For RockSim www.RockSim.com +;File Created March 2, 2005 +;Data from Tripoli Certification - test date 9/8/01 +;Not endorsed by TRA or Hypertek +I145 54 546 100 0.311 1.068 Hypertek +0.057 256.195 +0.204 253.38 +0.497 236.488 +1.002 208.334 +1.205 199.888 +1.376 211.15 +1.482 197.073 +2.003 177.366 +2.125 168.92 +2.5 90.091 +2.997 45.045 +3.282 16.892 +3.7 0 diff --git a/datafiles/thrustcurves/Hypertek_I205.eng b/datafiles/thrustcurves/Hypertek_I205.eng new file mode 100644 index 000000000..c9dbe183a --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_I205.eng @@ -0,0 +1,16 @@ +; +;hand entered from Cesaroni (Mike Dennett) curve data +;Andrew MacMillen NAR 77472 2/5/02 +;NOTE: NOT CTI OR TMT APPROVED +;Hypertek 300CC125J +I205 54 521 100 0.298 1.049 HyperTek +0.05 312 +0.1 347 +0.5 312 +1 258 +1.35 223 +1.6 125 +1.75 80 +2 45 +2.25 22 +2.75 0 diff --git a/datafiles/thrustcurves/Hypertek_I222.eng b/datafiles/thrustcurves/Hypertek_I222.eng new file mode 100644 index 000000000..fe386c60e --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_I222.eng @@ -0,0 +1,20 @@ +; +;Hypertek I222 Data entered by Tim Van Milligan +;For RockSim www.RockSim.com +;File Created March 2, 2005 +;Data from Mike Dennett at Hypertek +I222 54 546 100 0.28 1.013 Hypertek +0.037 394.146 +0.065 439.192 +0.12 450.547 +0.24 436.734 +0.5 411.158 +0.66 392.487 +1 338.349 +1.348 292.639 +1.432 259.337 +1.5 193.668 +1.67 117.056 +2 57.357 +2.3 22.358 +2.4 0 diff --git a/datafiles/thrustcurves/Hypertek_I225.eng b/datafiles/thrustcurves/Hypertek_I225.eng new file mode 100644 index 000000000..7303e829c --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_I225.eng @@ -0,0 +1,21 @@ +; +;Hypertek I225 Data entered by Tim Van Milligan +;For RockSim www.RockSim.com +;File Created March 2, 2005 +;Data from Tripoli Certification - test date 9/8/01 +;Not endorsed by TRA or Hypertek +I225 54 546 100 0.298 1.067 Hypertek +0.012 309.686 +0.037 343.47 +0.106 351.916 +0.244 354.732 +0.497 337.84 +0.749 320.948 +0.998 298.425 +1.254 273.087 +1.433 239.303 +1.502 194.258 +1.755 101.352 +1.999 53.491 +2.211 11.261 +2.37 0 diff --git a/datafiles/thrustcurves/Hypertek_I260.eng b/datafiles/thrustcurves/Hypertek_I260.eng new file mode 100644 index 000000000..1d8c7a1bb --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_I260.eng @@ -0,0 +1,21 @@ +; +;Hypertek I260 Data entered by Tim Van Milligan +;For RockSim www.RockSim.com +;File Created March 2, 2005 +;Data from Mike Dennett at Hypertek +I260 54 614 100 0.409 1.296 Hypertek +0.03 339.01 +0.041 425.115 +0.12 413.854 +0.216 394.146 +0.354 391.331 +0.497 368.808 +0.749 346.286 +1.002 306.871 +1.36 264.641 +1.454 228.042 +1.502 180.181 +1.686 109.798 +2.003 50.676 +2.2 27.483 +2.3 0 diff --git a/datafiles/thrustcurves/Hypertek_I310.eng b/datafiles/thrustcurves/Hypertek_I310.eng new file mode 100644 index 000000000..4796d1a06 --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_I310.eng @@ -0,0 +1,30 @@ +; HyperTek I310 +; Copyright Tripoli Motor Testing 1998 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +I310 54 645 0 0.40096 1.30502 HT + 0.042 465.886 + 0.128 450.815 + 0.216 438.421 + 0.303 443.241 + 0.391 433.808 + 0.478 415.992 + 0.566 406.746 + 0.653 380.383 + 0.741 385.170 + 0.828 372.458 + 0.916 358.282 + 1.003 348.621 + 1.091 337.887 + 1.178 333.898 + 1.266 303.469 + 1.353 301.589 + 1.441 268.788 + 1.528 222.719 + 1.616 155.314 + 1.703 112.163 + 1.791 80.510 + 1.878 58.562 + 1.966 41.774 + 2.053 30.243 + 2.141 21.270 + 2.228 0.000 diff --git a/datafiles/thrustcurves/Hypertek_J115.eng b/datafiles/thrustcurves/Hypertek_J115.eng new file mode 100644 index 000000000..8efaca42f --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_J115.eng @@ -0,0 +1,30 @@ +; HyperTek J115 (440CC076J) +; Copyright Tripoli Motor Testing 2001 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +J115 54 614 0 0.411264 1.28218 HT + 0.129 218.303 + 0.391 230.563 + 0.653 216.171 + 0.916 165.676 + 1.178 158.834 + 1.441 161.888 + 1.703 157.955 + 1.966 152.977 + 2.228 148.337 + 2.491 141.919 + 2.753 136.970 + 3.016 129.152 + 3.278 121.815 + 3.541 111.971 + 3.803 79.163 + 4.066 53.433 + 4.328 42.975 + 4.591 38.391 + 4.853 33.418 + 5.116 28.709 + 5.378 23.886 + 5.641 19.658 + 5.903 15.894 + 6.166 11.955 + 6.428 9.151 + 6.691 0.000 diff --git a/datafiles/thrustcurves/Hypertek_J120.eng b/datafiles/thrustcurves/Hypertek_J120.eng new file mode 100644 index 000000000..7bf3181ff --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_J120.eng @@ -0,0 +1,30 @@ +; HyperTek J120 (440CC076JFX) +; Copyright Tripoli Motor Testing 2001 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +J120 54 614 0 0.442176 1.29338 HT + 0.134 232.707 + 0.405 264.084 + 0.676 230.699 + 0.948 185.971 + 1.220 174.226 + 1.491 173.853 + 1.763 165.828 + 2.034 158.016 + 2.305 152.389 + 2.577 143.399 + 2.849 135.969 + 3.120 129.537 + 3.392 124.822 + 3.664 118.872 + 3.934 109.922 + 4.206 69.777 + 4.478 47.837 + 4.749 40.178 + 5.021 35.768 + 5.293 31.265 + 5.564 26.359 + 5.835 21.215 + 6.107 17.175 + 6.378 12.931 + 6.650 9.463 + 6.922 0.000 diff --git a/datafiles/thrustcurves/Hypertek_J150.eng b/datafiles/thrustcurves/Hypertek_J150.eng new file mode 100644 index 000000000..b29e8ba14 --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_J150.eng @@ -0,0 +1,30 @@ +; HyperTek J150 +; Copyright Tripoli Motor Testing 1998 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +J150 54 645 0 0.428288 1.30592 HT + 0.111 177.498 + 0.336 193.696 + 0.561 200.136 + 0.786 204.034 + 1.011 200.531 + 1.236 197.233 + 1.461 192.706 + 1.686 189.854 + 1.911 185.892 + 2.136 183.117 + 2.361 179.325 + 2.586 174.178 + 2.813 171.123 + 3.039 164.933 + 3.264 160.032 + 3.489 154.604 + 3.714 148.653 + 3.939 92.092 + 4.164 55.325 + 4.389 42.913 + 4.614 32.903 + 4.839 24.742 + 5.064 16.445 + 5.289 8.527 + 5.515 4.923 + 5.741 0.000 diff --git a/datafiles/thrustcurves/Hypertek_J170.eng b/datafiles/thrustcurves/Hypertek_J170.eng new file mode 100644 index 000000000..c6c056c52 --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_J170.eng @@ -0,0 +1,30 @@ +; HyperTek J170 (440CC098J) +; Copyright Tripoli Motor Testing 2001 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +J170 54 614 0 0.4032 1.28218 HT + 0.092 315.198 + 0.278 351.486 + 0.466 314.152 + 0.653 255.278 + 0.841 235.396 + 1.027 234.785 + 1.214 230.871 + 1.401 223.051 + 1.589 217.688 + 1.776 209.940 + 1.962 203.806 + 2.149 197.520 + 2.336 191.243 + 2.524 178.598 + 2.711 129.785 + 2.898 82.459 + 3.084 71.693 + 3.272 64.633 + 3.459 54.015 + 3.647 45.022 + 3.833 36.373 + 4.020 28.397 + 4.207 21.518 + 4.395 16.072 + 4.582 11.712 + 4.770 0.000 diff --git a/datafiles/thrustcurves/Hypertek_J190.eng b/datafiles/thrustcurves/Hypertek_J190.eng new file mode 100644 index 000000000..78b4ef16b --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_J190.eng @@ -0,0 +1,30 @@ +; HyperTek J190 (440CC098JFX) +; Copyright Tripoli Motor Testing 2001 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +J190 54 614 0 0.439488 1.29338 HT + 0.095 338.583 + 0.287 384.416 + 0.481 341.472 + 0.674 279.175 + 0.867 267.528 + 1.060 256.325 + 1.254 250.108 + 1.447 244.404 + 1.640 238.846 + 1.833 236.505 + 2.026 232.026 + 2.219 223.962 + 2.413 213.236 + 2.606 201.661 + 2.799 150.523 + 2.992 101.327 + 3.185 84.001 + 3.378 73.902 + 3.571 60.222 + 3.765 49.208 + 3.958 39.096 + 4.151 29.873 + 4.344 22.600 + 4.538 16.842 + 4.731 11.964 + 4.925 0.000 diff --git a/datafiles/thrustcurves/Hypertek_J220.eng b/datafiles/thrustcurves/Hypertek_J220.eng new file mode 100644 index 000000000..350cceb30 --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_J220.eng @@ -0,0 +1,30 @@ +; HyperTek J220 +; Copyright Tripoli Motor Testing 1998 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +J220 54 645 0 0.417984 1.30502 HT + 0.071 277.975 + 0.215 271.735 + 0.358 289.851 + 0.502 298.227 + 0.647 293.803 + 0.792 290.609 + 0.935 283.211 + 1.079 276.013 + 1.223 271.808 + 1.368 269.774 + 1.513 262.986 + 1.656 257.451 + 1.800 253.286 + 1.944 245.781 + 2.089 239.739 + 2.233 230.852 + 2.377 220.234 + 2.521 159.239 + 2.665 97.180 + 2.809 73.147 + 2.954 58.766 + 3.098 48.973 + 3.242 37.549 + 3.385 27.410 + 3.530 19.267 + 3.675 0.000 diff --git a/datafiles/thrustcurves/Hypertek_J250.eng b/datafiles/thrustcurves/Hypertek_J250.eng new file mode 100644 index 000000000..b9e947b45 --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_J250.eng @@ -0,0 +1,30 @@ +; HyperTek J250 (440CC125J) +; Copyright Tripoli Motor Testing 2001 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +J250 54 614 0 0.40768 1.29248 HT + 0.064 409.711 + 0.194 453.238 + 0.324 416.509 + 0.454 383.773 + 0.584 359.202 + 0.715 343.963 + 0.845 336.331 + 0.975 328.849 + 1.105 318.614 + 1.235 309.097 + 1.366 306.155 + 1.496 290.597 + 1.626 283.180 + 1.756 261.190 + 1.886 200.168 + 2.017 143.646 + 2.147 126.521 + 2.277 113.229 + 2.407 91.310 + 2.538 71.216 + 2.668 54.183 + 2.798 40.347 + 2.928 29.057 + 3.058 20.139 + 3.190 13.793 + 3.321 0.000 diff --git a/datafiles/thrustcurves/Hypertek_J250_1.eng b/datafiles/thrustcurves/Hypertek_J250_1.eng new file mode 100644 index 000000000..b7637a57b --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_J250_1.eng @@ -0,0 +1,30 @@ +; HyperTek J250 +; Copyright Tripoli Motor Testing 1998 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +J250 54 645 0 0.404992 1.30637 HT + 0.055 356.092 + 0.168 316.638 + 0.281 357.597 + 0.395 351.765 + 0.508 354.216 + 0.622 354.162 + 0.735 338.625 + 0.849 332.051 + 0.963 323.651 + 1.076 315.678 + 1.190 305.773 + 1.303 298.769 + 1.417 288.922 + 1.530 293.337 + 1.644 276.552 + 1.757 269.543 + 1.871 223.360 + 1.984 131.511 + 2.098 98.246 + 2.211 76.331 + 2.325 60.095 + 2.439 47.691 + 2.552 36.215 + 2.666 26.693 + 2.779 20.007 + 2.893 0.000 diff --git a/datafiles/thrustcurves/Hypertek_J270.eng b/datafiles/thrustcurves/Hypertek_J270.eng new file mode 100644 index 000000000..86dae58eb --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_J270.eng @@ -0,0 +1,30 @@ +; HyperTek J270 (440CC125JFX) +; Copyright Tripoli Motor Testing 2001 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +J270 54 614 0 0.419776 1.29606 HT + 0.064 438.643 + 0.193 498.603 + 0.322 468.869 + 0.451 438.261 + 0.581 412.686 + 0.711 390.684 + 0.841 376.193 + 0.970 362.205 + 1.100 347.649 + 1.230 333.459 + 1.359 324.401 + 1.489 311.483 + 1.619 298.076 + 1.749 278.397 + 1.878 220.239 + 2.007 150.276 + 2.137 125.603 + 2.268 121.989 + 2.397 91.398 + 2.526 71.671 + 2.656 55.779 + 2.786 41.822 + 2.916 30.460 + 3.045 22.243 + 3.175 16.420 + 3.305 0.000 diff --git a/datafiles/thrustcurves/Hypertek_J295.eng b/datafiles/thrustcurves/Hypertek_J295.eng new file mode 100644 index 000000000..f9a28fed2 --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_J295.eng @@ -0,0 +1,17 @@ +; +;Hypertek J295 Data entered by Tim Van Milligan +;For RockSim www.RockSim.com +;File Created March 2, 2005 +;Data from Tripoli Certification - test date 9/8/01 +;Not endorsed by TRA or Hypertek +J295 54 614 100 0.409 1.31 Hypertek +0.004 467.345 +0.244 461.714 +0.501 416.669 +1.002 377.254 +1.254 343.47 +1.364 315.317 +1.502 219.596 +1.751 112.613 +2.003 50.676 +2.2 0 diff --git a/datafiles/thrustcurves/Hypertek_J317.eng b/datafiles/thrustcurves/Hypertek_J317.eng new file mode 100644 index 000000000..668b42b0c --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_J317.eng @@ -0,0 +1,30 @@ +; HyperTek J317O (835CC172J) +; Copyright Tripoli Motor Testing 2001 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +J317O 81 552 0 0.704256 1.7575 HT + 0.071 438.483 + 0.215 471.024 + 0.358 459.716 + 0.502 447.348 + 0.647 431.653 + 0.792 418.545 + 0.935 407.806 + 1.079 400.212 + 1.223 395.752 + 1.368 382.516 + 1.513 372.890 + 1.656 368.033 + 1.800 349.298 + 1.944 336.071 + 2.089 324.486 + 2.233 301.205 + 2.377 233.601 + 2.521 176.972 + 2.665 132.539 + 2.809 96.229 + 2.954 69.718 + 3.098 49.457 + 3.242 33.983 + 3.385 23.063 + 3.530 16.524 + 3.675 0.000 diff --git a/datafiles/thrustcurves/Hypertek_J330.eng b/datafiles/thrustcurves/Hypertek_J330.eng new file mode 100644 index 000000000..cebede1f5 --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_J330.eng @@ -0,0 +1,30 @@ +; HyperTek J330O (835CC172JFX) +; Copyright Tripoli Motor Testing 2001 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +J330O 81 552 0 0.727104 1.77722 HT + 0.068 453.500 + 0.206 485.741 + 0.345 476.873 + 0.483 463.622 + 0.623 439.951 + 0.761 423.260 + 0.900 426.386 + 1.040 415.245 + 1.178 443.371 + 1.317 431.352 + 1.456 407.015 + 1.595 392.143 + 1.733 390.332 + 1.872 360.092 + 2.010 334.240 + 2.150 307.215 + 2.289 225.611 + 2.427 169.224 + 2.567 126.562 + 2.705 93.167 + 2.844 68.293 + 2.983 48.099 + 3.122 32.856 + 3.260 21.857 + 3.400 15.193 + 3.540 0.000 diff --git a/datafiles/thrustcurves/Hypertek_J330_1.eng b/datafiles/thrustcurves/Hypertek_J330_1.eng new file mode 100644 index 000000000..ab68026ea --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_J330_1.eng @@ -0,0 +1,29 @@ +; HyperTek J330 (835/54-172-J) +; provided by ThrustCurve.org (www.thrustcurve.org) +J330 54 787 0 0.73024 1.59936 HT + 0.068 453.500 + 0.206 485.741 + 0.345 476.873 + 0.483 463.622 + 0.623 439.951 + 0.761 423.260 + 0.900 426.386 + 1.040 415.245 + 1.178 443.371 + 1.317 431.352 + 1.456 407.015 + 1.595 392.143 + 1.733 390.332 + 1.872 360.092 + 2.010 334.240 + 2.150 307.215 + 2.289 225.611 + 2.427 169.224 + 2.567 126.562 + 2.705 93.167 + 2.844 68.293 + 2.983 48.099 + 3.122 32.856 + 3.260 21.857 + 3.400 15.193 + 3.540 0.000 diff --git a/datafiles/thrustcurves/Hypertek_K240.eng b/datafiles/thrustcurves/Hypertek_K240.eng new file mode 100644 index 000000000..90b8310ce --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_K240.eng @@ -0,0 +1,30 @@ +; HyperTek K240 +; Copyright Tripoli Motor Testing 1998 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +K240 81 552 0 0.789376 1.80723 HT + 0.131 278.007 + 0.396 329.552 + 0.660 338.024 + 0.925 334.092 + 1.191 326.199 + 1.456 319.745 + 1.721 315.195 + 1.985 311.182 + 2.250 302.916 + 2.516 305.943 + 2.781 289.975 + 3.046 281.781 + 3.310 273.330 + 3.575 268.852 + 3.841 255.702 + 4.106 251.068 + 4.371 234.820 + 4.635 159.972 + 4.900 96.543 + 5.166 73.367 + 5.431 55.477 + 5.696 40.928 + 5.960 29.542 + 6.225 21.250 + 6.491 14.787 + 6.756 0.000 diff --git a/datafiles/thrustcurves/Hypertek_L200.eng b/datafiles/thrustcurves/Hypertek_L200.eng new file mode 100644 index 000000000..e9b9594e7 --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_L200.eng @@ -0,0 +1,30 @@ +; HyperTek L200 (1685CC098L) +; Copyright Tripoli Motor Testing 2001 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +L200 111 724 0 1.59398 3.89491 HT + 0.292 310.935 + 0.877 311.934 + 1.464 284.574 + 2.050 259.236 + 2.636 245.149 + 3.223 240.798 + 3.809 246.021 + 4.396 251.509 + 4.981 255.559 + 5.568 250.045 + 6.154 242.343 + 6.741 236.221 + 7.327 230.527 + 7.914 224.062 + 8.500 218.240 + 9.086 212.215 + 9.673 189.706 + 10.258 94.608 + 10.845 67.128 + 11.431 53.350 + 12.018 41.550 + 12.604 31.112 + 13.191 22.445 + 13.777 16.763 + 14.364 10.892 + 14.950 0.000 diff --git a/datafiles/thrustcurves/Hypertek_L225.eng b/datafiles/thrustcurves/Hypertek_L225.eng new file mode 100644 index 000000000..6d6fdfc46 --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_L225.eng @@ -0,0 +1,30 @@ +; HyperTek L225 (1685CC098LFX) +; Copyright Tripoli Motor Testing 2001 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +L225 111 724 0 1.66118 3.94822 HT + 0.271 380.609 + 0.815 364.078 + 1.359 347.671 + 1.904 311.980 + 2.449 292.842 + 2.994 284.620 + 3.539 284.123 + 4.083 295.754 + 4.628 286.654 + 5.173 271.822 + 5.718 258.225 + 6.263 249.357 + 6.807 240.396 + 7.352 232.666 + 7.897 226.844 + 8.442 217.601 + 8.986 208.639 + 9.531 122.739 + 10.076 74.667 + 10.621 60.930 + 11.166 48.898 + 11.710 38.996 + 12.255 29.719 + 12.800 21.925 + 13.345 16.360 + 13.890 0.000 diff --git a/datafiles/thrustcurves/Hypertek_L350.eng b/datafiles/thrustcurves/Hypertek_L350.eng new file mode 100644 index 000000000..e529c7a79 --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_L350.eng @@ -0,0 +1,30 @@ +; HyperTek L350 (1685CC125L) +; Copyright Tripoli Motor Testing 2001 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +L350 111 724 0 1.6025 3.90342 HT + 0.188 592.182 + 0.566 598.015 + 0.945 479.774 + 1.324 427.223 + 1.702 406.561 + 2.080 395.128 + 2.459 393.279 + 2.839 410.729 + 3.217 517.335 + 3.595 506.496 + 3.974 439.490 + 4.353 391.732 + 4.731 463.388 + 5.109 446.876 + 5.489 436.237 + 5.868 387.456 + 6.246 247.342 + 6.624 147.845 + 7.003 117.459 + 7.382 93.081 + 7.760 73.034 + 8.139 55.605 + 8.518 40.854 + 8.897 29.575 + 9.276 21.627 + 9.655 0.000 diff --git a/datafiles/thrustcurves/Hypertek_L355.eng b/datafiles/thrustcurves/Hypertek_L355.eng new file mode 100644 index 000000000..d58dc13e9 --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_L355.eng @@ -0,0 +1,30 @@ +; HyperTek L355 (1685CC125LFX) +; Copyright Tripoli Motor Testing 2001 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +L355 111 724 0 1.61952 3.95405 HT + 0.185 638.681 + 0.559 623.792 + 0.933 538.081 + 1.307 493.173 + 1.682 465.144 + 2.056 432.702 + 2.430 410.644 + 2.804 388.862 + 3.178 395.452 + 3.553 384.851 + 3.927 370.103 + 4.301 356.484 + 4.675 346.195 + 5.049 342.684 + 5.424 325.430 + 5.798 317.798 + 6.172 272.453 + 6.546 156.214 + 6.920 117.579 + 7.295 93.203 + 7.669 73.056 + 8.043 56.997 + 8.417 42.994 + 8.791 30.338 + 9.166 21.725 + 9.541 0.000 diff --git a/datafiles/thrustcurves/Hypertek_L475.eng b/datafiles/thrustcurves/Hypertek_L475.eng new file mode 100644 index 000000000..46bd6e347 --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_L475.eng @@ -0,0 +1,30 @@ +; HyperTek L475 (1685CC172L) +; Copyright Tripoli Motor Testing 2001 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +L475 111 724 0 1.52992 3.89805 HT + 0.129 640.755 + 0.391 638.069 + 0.652 619.018 + 0.914 609.661 + 1.176 597.937 + 1.437 596.251 + 1.699 594.039 + 1.961 572.245 + 2.223 578.304 + 2.484 589.224 + 2.747 578.752 + 3.008 612.426 + 3.270 646.188 + 3.531 674.617 + 3.793 652.574 + 4.055 555.384 + 4.317 299.749 + 4.578 220.284 + 4.841 170.007 + 5.102 127.011 + 5.364 94.998 + 5.626 70.208 + 5.888 49.458 + 6.149 32.102 + 6.411 18.382 + 6.674 0.000 diff --git a/datafiles/thrustcurves/Hypertek_L535.eng b/datafiles/thrustcurves/Hypertek_L535.eng new file mode 100644 index 000000000..9b2449a6b --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_L535.eng @@ -0,0 +1,30 @@ +; HyperTek L535 (1685CC172LFX) +; Copyright Tripoli Motor Testing 2001 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +L535 111 724 0 1.59667 3.94822 HT + 0.119 838.518 + 0.358 768.667 + 0.598 727.551 + 0.838 745.172 + 1.077 712.851 + 1.317 694.613 + 1.556 672.145 + 1.796 656.726 + 2.035 685.070 + 2.275 808.735 + 2.515 798.521 + 2.754 755.277 + 2.995 726.985 + 3.235 699.331 + 3.475 670.049 + 3.715 515.315 + 3.954 293.947 + 4.194 227.905 + 4.433 180.167 + 4.673 140.714 + 4.912 107.564 + 5.152 80.907 + 5.392 58.898 + 5.631 39.782 + 5.872 24.901 + 6.113 0.000 diff --git a/datafiles/thrustcurves/Hypertek_L540.eng b/datafiles/thrustcurves/Hypertek_L540.eng new file mode 100644 index 000000000..ea371f702 --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_L540.eng @@ -0,0 +1,30 @@ +; HyperTek L540O (2800CC172L) +; Copyright Tripoli Motor Testing 2001 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +L540O 111 876 0 2.5303 5.656 HT + 0.191 685.548 + 0.574 665.171 + 0.957 635.467 + 1.341 634.122 + 1.725 656.225 + 2.109 706.931 + 2.493 696.526 + 2.876 777.726 + 3.260 775.919 + 3.645 781.611 + 4.028 712.736 + 4.411 695.555 + 4.796 701.251 + 5.180 645.985 + 5.564 607.757 + 5.947 546.408 + 6.331 387.372 + 6.716 234.214 + 7.099 181.086 + 7.482 139.253 + 7.867 106.109 + 8.251 78.293 + 8.634 54.851 + 9.018 36.384 + 9.402 20.375 + 9.786 0.000 diff --git a/datafiles/thrustcurves/Hypertek_L540_1.eng b/datafiles/thrustcurves/Hypertek_L540_1.eng new file mode 100644 index 000000000..daf00e147 --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_L540_1.eng @@ -0,0 +1,29 @@ +; HyperTek L540 (2800/75-172-L) +; provided by ThrustCurve.org (www.thrustcurve.org) +L540 75 1387 0 2.52224 5.05792 HT + 0.191 685.548 + 0.574 665.171 + 0.957 635.467 + 1.341 634.122 + 1.725 656.225 + 2.109 706.931 + 2.493 696.526 + 2.876 777.726 + 3.260 775.919 + 3.645 781.611 + 4.028 712.736 + 4.411 695.555 + 4.796 701.251 + 5.180 645.985 + 5.564 607.757 + 5.947 546.408 + 6.331 387.372 + 6.716 234.214 + 7.099 181.086 + 7.482 139.253 + 7.867 106.109 + 8.251 78.293 + 8.634 54.851 + 9.018 36.384 + 9.402 20.375 + 9.786 0.000 diff --git a/datafiles/thrustcurves/Hypertek_L550.eng b/datafiles/thrustcurves/Hypertek_L550.eng new file mode 100644 index 000000000..0f3f217cc --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_L550.eng @@ -0,0 +1,30 @@ +; HyperTek L550 (1685CCRGL) +; Copyright Tripoli Motor Testing 2001 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +L550 111 724 0 1.53261 3.89805 HT + 0.124 816.849 + 0.375 796.043 + 0.626 781.861 + 0.877 767.440 + 1.129 759.627 + 1.380 735.948 + 1.631 714.454 + 1.883 701.582 + 2.134 674.667 + 2.385 656.493 + 2.637 636.076 + 2.889 612.409 + 3.140 587.801 + 3.391 567.170 + 3.642 559.971 + 3.894 534.157 + 4.145 444.562 + 4.396 280.510 + 4.648 216.702 + 4.899 163.136 + 5.150 120.571 + 5.402 86.544 + 5.653 59.990 + 5.904 39.527 + 6.156 25.914 + 6.408 0.000 diff --git a/datafiles/thrustcurves/Hypertek_L570.eng b/datafiles/thrustcurves/Hypertek_L570.eng new file mode 100644 index 000000000..b6bbdb8ab --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_L570.eng @@ -0,0 +1,30 @@ +; HyperTek L570O (2800CC172LFX) +; Copyright Tripoli Motor Testing 2001 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +L570O 111 876 0 2.57734 5.70618 HT + 0.181 793.916 + 0.547 800.029 + 0.914 811.765 + 1.280 761.283 + 1.647 725.674 + 2.014 733.246 + 2.380 783.159 + 2.747 795.348 + 3.112 823.178 + 3.478 831.812 + 3.845 805.614 + 4.211 780.534 + 4.578 741.917 + 4.945 628.980 + 5.311 547.886 + 5.678 537.830 + 6.044 330.850 + 6.409 230.792 + 6.776 180.510 + 7.143 140.226 + 7.509 108.348 + 7.876 81.342 + 8.243 59.608 + 8.609 41.592 + 8.976 25.536 + 9.343 0.000 diff --git a/datafiles/thrustcurves/Hypertek_L570_1.eng b/datafiles/thrustcurves/Hypertek_L570_1.eng new file mode 100644 index 000000000..f23932061 --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_L570_1.eng @@ -0,0 +1,29 @@ +; HyperTek L570 (2800/75-172-L-FX) +; provided by ThrustCurve.org (www.thrustcurve.org) +L570 75 1387 0 2.57152 5.10272 HT + 0.181 793.916 + 0.547 800.029 + 0.914 811.765 + 1.280 761.283 + 1.647 725.674 + 2.014 733.246 + 2.380 783.159 + 2.747 795.348 + 3.112 823.178 + 3.478 831.812 + 3.845 805.614 + 4.211 780.534 + 4.578 741.917 + 4.945 628.980 + 5.311 547.886 + 5.678 537.830 + 6.044 330.850 + 6.409 230.792 + 6.776 180.510 + 7.143 140.226 + 7.509 108.348 + 7.876 81.342 + 8.243 59.608 + 8.609 41.592 + 8.976 25.536 + 9.343 0.000 diff --git a/datafiles/thrustcurves/Hypertek_L575.eng b/datafiles/thrustcurves/Hypertek_L575.eng new file mode 100644 index 000000000..fb23337f7 --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_L575.eng @@ -0,0 +1,30 @@ +; HyperTek L575O (2800CCRGL) +; Copyright Tripoli Motor Testing 2001 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +L575O 111 876 0 2.52134 5.65286 HT + 0.190 705.343 + 0.572 723.437 + 0.955 738.414 + 1.337 749.383 + 1.720 735.169 + 2.103 725.088 + 2.486 733.751 + 2.869 700.454 + 3.251 690.771 + 3.634 682.897 + 4.017 674.825 + 4.399 687.463 + 4.782 675.411 + 5.166 645.685 + 5.548 643.612 + 5.930 634.693 + 6.314 559.731 + 6.696 304.009 + 7.078 229.423 + 7.461 167.643 + 7.845 121.036 + 8.227 83.673 + 8.609 54.311 + 8.993 33.029 + 9.376 19.886 + 9.759 0.000 diff --git a/datafiles/thrustcurves/Hypertek_L575_1.eng b/datafiles/thrustcurves/Hypertek_L575_1.eng new file mode 100644 index 000000000..e724caad4 --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_L575_1.eng @@ -0,0 +1,29 @@ +; HyperTek L575 (2800/75-RG-L) +; provided by ThrustCurve.org (www.thrustcurve.org) +L575 75 1387 0 2.51328 5.06688 HT + 0.190 705.343 + 0.572 723.437 + 0.955 738.414 + 1.337 749.383 + 1.720 735.169 + 2.103 725.088 + 2.486 733.751 + 2.869 700.454 + 3.251 690.771 + 3.634 682.897 + 4.017 674.825 + 4.399 687.463 + 4.782 675.411 + 5.166 645.685 + 5.548 643.612 + 5.930 634.693 + 6.314 559.731 + 6.696 304.009 + 7.078 229.423 + 7.461 167.643 + 7.845 121.036 + 8.227 83.673 + 8.609 54.311 + 8.993 33.029 + 9.376 19.886 + 9.759 0.000 diff --git a/datafiles/thrustcurves/Hypertek_L610.eng b/datafiles/thrustcurves/Hypertek_L610.eng new file mode 100644 index 000000000..d7ed8e43c --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_L610.eng @@ -0,0 +1,30 @@ +; HyperTek L610 (1685CCRGLFX) +; Copyright Tripoli Motor Testing 2001 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +L610 111 724 0 1.57696 3.95091 HT + 0.110 850.823 + 0.333 837.700 + 0.556 775.061 + 0.779 739.656 + 1.002 807.273 + 1.225 809.462 + 1.448 801.752 + 1.671 789.534 + 1.894 763.842 + 2.117 858.087 + 2.340 890.644 + 2.563 837.593 + 2.785 749.631 + 3.008 648.961 + 3.231 643.064 + 3.454 637.413 + 3.677 431.325 + 3.900 276.205 + 4.123 220.930 + 4.346 166.632 + 4.569 124.031 + 4.792 89.721 + 5.015 64.295 + 5.237 45.360 + 5.461 30.400 + 5.685 0.000 diff --git a/datafiles/thrustcurves/Hypertek_L625.eng b/datafiles/thrustcurves/Hypertek_L625.eng new file mode 100644 index 000000000..0cab431da --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_L625.eng @@ -0,0 +1,30 @@ +; HyperTek L625O (2800CCRGLFX) +; Copyright Tripoli Motor Testing 2001 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +L625O 111 876 0 2.56614 5.70618 HT + 0.169 855.024 + 0.509 896.169 + 0.851 905.926 + 1.193 902.118 + 1.534 853.016 + 1.876 953.210 + 2.218 904.246 + 2.559 856.525 + 2.901 743.522 + 3.243 758.646 + 3.584 751.619 + 3.926 745.737 + 4.267 751.907 + 4.607 728.305 + 4.949 691.703 + 5.291 658.843 + 5.632 504.450 + 5.974 279.745 + 6.316 216.661 + 6.657 164.957 + 6.999 123.426 + 7.341 89.428 + 7.682 62.955 + 8.024 43.519 + 8.366 27.956 + 8.707 0.000 diff --git a/datafiles/thrustcurves/Hypertek_L625_1.eng b/datafiles/thrustcurves/Hypertek_L625_1.eng new file mode 100644 index 000000000..d89ebe518 --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_L625_1.eng @@ -0,0 +1,29 @@ +; HyperTek L625 (2800/75-RG-L-FX) +; provided by ThrustCurve.org (www.thrustcurve.org) +L625 75 1387 0 2.56256 5.11616 HT + 0.169 855.024 + 0.509 896.169 + 0.851 905.926 + 1.193 902.118 + 1.534 853.016 + 1.876 953.210 + 2.218 904.246 + 2.559 856.525 + 2.901 743.522 + 3.243 758.646 + 3.584 751.619 + 3.926 745.737 + 4.267 751.907 + 4.607 728.305 + 4.949 691.703 + 5.291 658.843 + 5.632 504.450 + 5.974 279.745 + 6.316 216.661 + 6.657 164.957 + 6.999 123.426 + 7.341 89.428 + 7.682 62.955 + 8.024 43.519 + 8.366 27.956 + 8.707 0.000 diff --git a/datafiles/thrustcurves/Hypertek_L740.eng b/datafiles/thrustcurves/Hypertek_L740.eng new file mode 100644 index 000000000..c4bca4262 --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_L740.eng @@ -0,0 +1,32 @@ +; +; +L740 75 1422.4 100 2.667 6.416 HyperTek +0.02 767.76 +0.05 1084.9 +0.07 1166.87 +0.09 1198.96 +0.12 1183.37 +0.38 1088.96 +0.63 1139.56 +0.89 1130.73 +1.15 1109 +1.4 1096.6 +1.66 1048.51 +1.92 1026.67 +2.18 980.7 +2.43 949.74 +2.69 909.63 +2.95 893.62 +3.21 866.64 +3.46 825.26 +3.72 820.05 +3.98 789.78 +4.24 874.08 +4.49 804.36 +4.75 738.04 +5.01 383.77 +5.38 255.83 +5.75 183.27 +6.13 130.48 +6.5 94.03 +6.8 0 diff --git a/datafiles/thrustcurves/Hypertek_L970.eng b/datafiles/thrustcurves/Hypertek_L970.eng new file mode 100644 index 000000000..282a8e510 --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_L970.eng @@ -0,0 +1,33 @@ +; +; +L970 75 1422.4 100 2.532 6.323 HyperTek +0.01 534.31 +0.02 1007.71 +0.03 1320.67 +0.04 1463.43 +0.05 1482.44 +0.25 1315.87 +0.44 1362.79 +0.64 1441.44 +0.84 1452.58 +1.04 1418.39 +1.23 1403.65 +1.43 1337.52 +1.63 1311.22 +1.83 1257.09 +2.02 1279.26 +2.22 1229.81 +2.42 1174.23 +2.62 1162.77 +2.81 1122.04 +3.02 1108.55 +3.21 1058.31 +3.41 981.23 +3.61 959.35 +3.8 778.83 +4.09 437.55 +4.38 294.3 +4.66 194.71 +4.94 129.33 +5.23 86.31 +5.23 0 diff --git a/datafiles/thrustcurves/Hypertek_M1000.eng b/datafiles/thrustcurves/Hypertek_M1000.eng new file mode 100644 index 000000000..7e661dbdb --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_M1000.eng @@ -0,0 +1,30 @@ +; HyperTek M1000O (4630CCRGM) +; Copyright Tripoli Motor Testing 2001 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +M1000O 111 1147 0 4.17446 8.90714 HT + 0.197 1368.441 + 0.593 1448.113 + 0.989 1482.178 + 1.384 1431.137 + 1.780 1410.278 + 2.176 1399.905 + 2.573 1365.973 + 2.970 1338.653 + 3.366 1295.695 + 3.761 1280.192 + 4.157 1235.621 + 4.553 1212.944 + 4.950 1196.996 + 5.347 1172.466 + 5.743 1129.416 + 6.139 1051.999 + 6.534 635.308 + 6.930 474.427 + 7.327 359.958 + 7.724 272.962 + 8.120 205.206 + 8.516 149.099 + 8.911 103.639 + 9.307 70.124 + 9.704 48.706 + 10.101 0.000 diff --git a/datafiles/thrustcurves/Hypertek_M1000_1.eng b/datafiles/thrustcurves/Hypertek_M1000_1.eng new file mode 100644 index 000000000..b1ae51b25 --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_M1000_1.eng @@ -0,0 +1,29 @@ +; HyperTek M1000 (4630/98-RG-M) +; provided by ThrustCurve.org (www.thrustcurve.org) +M1000 98 1405 0 4.17536 8.72704 HT + 0.197 1368.441 + 0.593 1448.113 + 0.989 1482.178 + 1.384 1431.137 + 1.780 1410.278 + 2.176 1399.905 + 2.573 1365.973 + 2.970 1338.653 + 3.366 1295.695 + 3.761 1280.192 + 4.157 1235.621 + 4.553 1212.944 + 4.950 1196.996 + 5.347 1172.466 + 5.743 1129.416 + 6.139 1051.999 + 6.534 635.308 + 6.930 474.427 + 7.327 359.958 + 7.724 272.962 + 8.120 205.206 + 8.516 149.099 + 8.911 103.639 + 9.307 70.124 + 9.704 48.706 + 10.101 0.000 diff --git a/datafiles/thrustcurves/Hypertek_M1001.eng b/datafiles/thrustcurves/Hypertek_M1001.eng new file mode 100644 index 000000000..43c01a15c --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_M1001.eng @@ -0,0 +1,32 @@ +; +; +M1001 98 1493.5 100 5.161 10.092 HyperTek +0.04 1394.15 +0.08 1440.23 +0.12 1322.3 +0.16 1328.89 +0.2 1340.76 +0.57 1411.23 +0.95 1420.87 +1.32 1415.98 +1.69 1404.61 +2.07 1384.44 +2.44 1370.95 +2.82 1354.19 +3.19 1318.56 +3.57 1326.82 +3.94 1338.4 +4.32 1247.32 +4.69 1287.12 +5.07 1220.48 +5.44 1123.54 +5.82 1075.29 +6.19 1078.11 +6.56 996.41 +6.94 953.17 +7.31 676.72 +7.83 419.5 +8.35 285.78 +8.87 192.18 +9.39 128.7 +9.87 0 diff --git a/datafiles/thrustcurves/Hypertek_M1010.eng b/datafiles/thrustcurves/Hypertek_M1010.eng new file mode 100644 index 000000000..c01545b52 --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_M1010.eng @@ -0,0 +1,30 @@ +; HyperTek M1010O (4630CCRGMFX) +; Copyright Tripoli Motor Testing 2001 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +M1010O 111 1147 0 4.27571 8.99987 HT + 0.199 1473.475 + 0.599 1472.868 + 0.999 1463.740 + 1.399 1380.769 + 1.799 1408.210 + 2.199 1383.970 + 2.599 1332.771 + 2.999 1356.808 + 3.399 1339.075 + 3.799 1306.425 + 4.199 1266.222 + 4.599 1223.656 + 4.999 1190.978 + 5.399 1145.190 + 5.799 1103.440 + 6.199 1060.790 + 6.599 696.828 + 6.999 487.469 + 7.399 377.853 + 7.799 288.215 + 8.199 221.430 + 8.599 166.674 + 8.999 123.710 + 9.399 90.168 + 9.800 64.566 + 10.201 0.000 diff --git a/datafiles/thrustcurves/Hypertek_M1010_1.eng b/datafiles/thrustcurves/Hypertek_M1010_1.eng new file mode 100644 index 000000000..465642f36 --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_M1010_1.eng @@ -0,0 +1,29 @@ +; HyperTek M1010 (4630/98-RG-M-FX) +; provided by ThrustCurve.org (www.thrustcurve.org) +M1010 98 1405 0 4.23808 8.82112 HT + 0.199 1473.475 + 0.599 1472.868 + 0.999 1463.740 + 1.399 1380.769 + 1.799 1408.210 + 2.199 1383.970 + 2.599 1332.771 + 2.999 1356.808 + 3.399 1339.075 + 3.799 1306.425 + 4.199 1266.222 + 4.599 1223.656 + 4.999 1190.978 + 5.399 1145.190 + 5.799 1103.440 + 6.199 1060.790 + 6.599 696.828 + 6.999 487.469 + 7.399 377.853 + 7.799 288.215 + 8.199 221.430 + 8.599 166.674 + 8.999 123.710 + 9.399 90.168 + 9.800 64.566 + 10.201 0.000 diff --git a/datafiles/thrustcurves/Hypertek_M1015.eng b/datafiles/thrustcurves/Hypertek_M1015.eng new file mode 100644 index 000000000..5356c39c5 --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_M1015.eng @@ -0,0 +1,33 @@ +; +; +M1015 98 1150.6 100 3.25 7.158 HyperTek +0.04 1515.05 +0.08 1634.37 +0.12 1566.94 +0.16 1507.27 +0.2 1476.97 +0.41 1418.21 +0.63 1420.43 +0.85 1436.22 +1.06 1405.26 +1.28 1371.12 +1.49 1355 +1.71 1320.03 +1.93 1286.83 +2.14 1268.82 +2.36 1267.85 +2.58 1281.29 +2.79 1248.5 +3.01 1268.31 +3.23 1273.39 +3.44 1298.31 +3.66 1213.66 +3.87 1167.24 +4.09 1134.88 +4.31 1121.39 +4.69 544.64 +5.07 363.55 +5.46 234.8 +5.84 149.45 +6.22 94.73 +6.23 0 diff --git a/datafiles/thrustcurves/Hypertek_M1040.eng b/datafiles/thrustcurves/Hypertek_M1040.eng new file mode 100644 index 000000000..c2472ae58 --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_M1040.eng @@ -0,0 +1,32 @@ +; +; +M1040 98 1493.5 100 5.293 10.181 HyperTek +0.02 1288.12 +0.05 1545.28 +0.07 1583.82 +0.09 1530.98 +0.12 1497.57 +0.53 1421.58 +0.95 1430.43 +1.37 1412.05 +1.78 1398.74 +2.2 1371.89 +2.61 1382.63 +3.03 1406.27 +3.44 1478.77 +3.86 1420.86 +4.27 1377.59 +4.69 1333.74 +5.1 1289.09 +5.52 1274.62 +5.93 1176.1 +6.35 1152.46 +6.77 891.06 +7.18 582.18 +7.6 429.01 +8.01 320.71 +8.43 238.67 +8.84 175.92 +9.25 128.91 +9.66 92.08 +9.7 0 diff --git a/datafiles/thrustcurves/Hypertek_M740.eng b/datafiles/thrustcurves/Hypertek_M740.eng new file mode 100644 index 000000000..82a9354d5 --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_M740.eng @@ -0,0 +1,33 @@ +; +; +M740 75 1422.4 100 2.589 6.322 HyperTek +0.04 979.34 +0.08 1135.85 +0.12 1065.56 +0.16 1026.83 +0.2 1022.88 +0.44 982.86 +0.68 1065.61 +0.91 1100.92 +1.15 1067.05 +1.39 1072.92 +1.63 1013.29 +1.87 1016.51 +2.11 1012.36 +2.35 1007.02 +2.58 968.14 +2.82 948.67 +3.06 944 +3.3 905.49 +3.54 899.55 +3.77 866.23 +4.02 847.5 +4.25 822.68 +4.49 813.98 +4.73 789.25 +4.97 752.47 +5.21 344.76 +5.45 271.93 +5.84 195.15 +6.46 108.81 +6.97 0 diff --git a/datafiles/thrustcurves/Hypertek_M956.eng b/datafiles/thrustcurves/Hypertek_M956.eng new file mode 100644 index 000000000..cd5ab55d0 --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_M956.eng @@ -0,0 +1,32 @@ +; +; +M956 98 1150.6 100 3.162 7.061 HyperTek +0.04 1325.32 +0.08 1335.19 +0.12 1273.52 +0.16 1245.8 +0.2 1261.01 +0.46 1283.75 +0.71 1339.94 +0.97 1333.16 +1.23 1322.79 +1.49 1330.11 +1.75 1294.72 +2.01 1271.81 +2.27 1246.37 +2.52 1233.02 +2.78 1214.19 +3.04 1199.4 +3.3 1152.16 +3.56 1128.04 +3.81 1119.3 +4.08 1098.79 +4.33 1054.12 +4.59 1031.85 +4.85 964.95 +5.11 548.37 +5.45 373.37 +5.79 248.05 +6.14 160.88 +6.48 109.1 +6.7 0 diff --git a/datafiles/thrustcurves/Hypertek_M960.eng b/datafiles/thrustcurves/Hypertek_M960.eng new file mode 100644 index 000000000..28e272d66 --- /dev/null +++ b/datafiles/thrustcurves/Hypertek_M960.eng @@ -0,0 +1,32 @@ +; +; +M960 75 1422.4 100 2.629 6.414 HyperTek +0.01 508.27 +0.02 1058.4 +0.03 1393.68 +0.04 1534.58 +0.05 1585.27 +0.25 1375.21 +0.44 1354.52 +0.64 1365.14 +0.84 1385.36 +1.04 1418.42 +1.23 1358.94 +1.43 1347.56 +1.63 1291.46 +1.83 1241.32 +2.02 1235.83 +2.22 1221.66 +2.42 1181.59 +2.62 1150.77 +2.81 1105.59 +3.02 1040.1 +3.21 982.54 +3.41 999.65 +3.61 943.72 +3.8 871.92 +4.11 526.67 +4.42 330.98 +4.72 212.71 +5.03 135.8 +5.33 0 diff --git a/datafiles/thrustcurves/KBA_I170.eng b/datafiles/thrustcurves/KBA_I170.eng new file mode 100644 index 000000000..6e9c0a4e5 --- /dev/null +++ b/datafiles/thrustcurves/KBA_I170.eng @@ -0,0 +1,26 @@ +;Data entered by Tim Van Milligan +;Based on TRA Certification 6-19-2002 +;And Instructions provided by Aerotech. +I170S 38 258 14 0.1819 0.52 Kosdon-by-Aerotech +0.019 194.885 +0.131 190.481 +0.255 191.582 +0.513 199.289 +0.641 204.794 +0.753 206.996 +0.88 209.199 +1 208.098 +1.051 208.098 +1.147 206.996 +1.24 201.491 +1.391 198.188 +1.537 190.481 +1.707 181.672 +1.746 178.369 +1.781 173.96 +1.808 168.46 +1.854 132.12 +1.939 53.951 +2.005 22.02 +2.059 9.909 +2.13 0 diff --git a/datafiles/thrustcurves/KBA_I280.eng b/datafiles/thrustcurves/KBA_I280.eng new file mode 100644 index 000000000..4da376126 --- /dev/null +++ b/datafiles/thrustcurves/KBA_I280.eng @@ -0,0 +1,17 @@ +; +; +I280F 38 258 14 100.182 0.52 Kosdon-by-AeroTech +0.009 253.24 +0.055 255.442 +0.219 277.463 +0.482 301.686 +0.67 323.707 +0.735 330.314 +0.797 323.707 +1.001 297.282 +1.162 266.453 +1.205 259.847 +1.236 237.826 +1.363 50.6481 +1.428 26.4251 +1.5 0 diff --git a/datafiles/thrustcurves/KBA_I301.eng b/datafiles/thrustcurves/KBA_I301.eng new file mode 100644 index 000000000..b189e7bd0 --- /dev/null +++ b/datafiles/thrustcurves/KBA_I301.eng @@ -0,0 +1,25 @@ +; KBA I301W +I301W 38 369.6 18 0.295031 0.724 KBA + 0.0080 266.093 + 0.014 327.114 + 0.03 354.124 + 0.058 350.122 + 0.107 335.117 + 0.133 326.114 + 0.189 326.114 + 0.217 333.116 + 0.237 383.134 + 0.253 402.14 + 0.287 395.138 + 0.33 381.133 + 0.72 381.133 + 1.035 341.119 + 1.437 317.111 + 1.57 262.092 + 1.698 130.045 + 1.789 83.029 + 1.833 74.026 + 1.867 53.019 + 1.893 23.008 + 1.916 13.005 + 1.952 0.0 diff --git a/datafiles/thrustcurves/KBA_I310.eng b/datafiles/thrustcurves/KBA_I310.eng new file mode 100644 index 000000000..23f3c5c2e --- /dev/null +++ b/datafiles/thrustcurves/KBA_I310.eng @@ -0,0 +1,31 @@ +; +;Kosdon by AeroTech I310S +;Copyright Tripoli Motor Testing 2001 (www.tripoli.org) +;provided by ThrustCurve.org (www.thrustcurve.org) +I310S 38 368 6-0 0.312256 0.713216 Kosdon-by-AeroTech +0.045 334.66 +0.136 314.409 +0.228 322.556 +0.32 326.871 +0.411 331.851 +0.503 335.911 +0.595 336.933 +0.686 340.151 +0.778 342.066 +0.87 344.722 +0.961 348.578 +1.053 349.548 +1.146 351.943 +1.239 347.939 +1.33 345.079 +1.422 337.035 +1.514 333.332 +1.605 323.832 +1.697 289 +1.789 215.097 +1.88 136.596 +1.972 83.863 +2.064 37.922 +2.155 20.736 +2.248 5.943 +2.341 0 diff --git a/datafiles/thrustcurves/KBA_I370.eng b/datafiles/thrustcurves/KBA_I370.eng new file mode 100644 index 000000000..cb88f11ec --- /dev/null +++ b/datafiles/thrustcurves/KBA_I370.eng @@ -0,0 +1,31 @@ +; +;Kosdon by AeroTech I370F +;Copyright Tripoli Motor Testing 2001 (www.tripoli.org) +;provided by ThrustCurve.org (www.thrustcurve.org) +I370F 38 368 100 0.312256 0.705152 Kosdon-by-AeroTech +0.035 373.074 +0.109 389.927 +0.184 401.07 +0.259 416.613 +0.334 429.598 +0.409 438.025 +0.484 443.83 +0.559 447.326 +0.634 446.764 +0.709 447.263 +0.784 444.735 +0.859 441.302 +0.933 435.676 +1.007 425.29 +1.082 414.897 +1.157 404.222 +1.232 395.358 +1.307 382.062 +1.382 334.152 +1.457 275.974 +1.532 179.654 +1.607 83.023 +1.682 39.608 +1.757 16.105 +1.832 4.151 +1.907 0 diff --git a/datafiles/thrustcurves/KBA_I450.eng b/datafiles/thrustcurves/KBA_I450.eng new file mode 100644 index 000000000..b6d9ced19 --- /dev/null +++ b/datafiles/thrustcurves/KBA_I450.eng @@ -0,0 +1,18 @@ +; +; +I450F 38 370 14 0.3032 0.73 Kosdon-by-AeroTech +0.012 634.202 +0.037 550.523 +0.108 519.693 +0.241 510.885 +0.639 550.523 +0.729 554.927 +0.809 546.118 +0.939 497.672 +1.072 471.247 +1.128 440.418 +1.165 387.568 +1.211 206.996 +1.295 88.0836 +1.36 26.4251 +1.41 0 diff --git a/datafiles/thrustcurves/KBA_I550.eng b/datafiles/thrustcurves/KBA_I550.eng new file mode 100644 index 000000000..488b2002b --- /dev/null +++ b/datafiles/thrustcurves/KBA_I550.eng @@ -0,0 +1,28 @@ +; KBA I550R +I550R 38 369.6 20 0.295 0.713 KBA + 0.016 156.054 + 0.028 278.097 + 0.04 427.149 + 0.054 550.192 + 0.08 542.189 + 0.245 588.205 + 0.332 611.213 + 0.424 631.22 + 0.496 638.223 + 0.613 644.225 + 0.71 643.225 + 0.758 631.22 + 0.846 603.211 + 0.894 613.214 + 0.915 611.213 + 0.939 586.205 + 0.949 546.191 + 0.959 505.176 + 0.969 469.164 + 0.983 381.133 + 0.999 278.097 + 1.011 200.07 + 1.029 112.039 + 1.053 42.015 + 1.069 15.005 + 1.089 0.0 diff --git a/datafiles/thrustcurves/KBA_J405.eng b/datafiles/thrustcurves/KBA_J405.eng new file mode 100644 index 000000000..2a655c998 --- /dev/null +++ b/datafiles/thrustcurves/KBA_J405.eng @@ -0,0 +1,13 @@ +; +; +J405S 38 476 14 0.367 0.88 Kosdon-by-AeroTech +0.009 528.502 +0.024 488.864 +0.046 462.439 +0.136 462.439 +0.268 458.035 +0.986 453.631 +1.421 444.822 +1.523 255.442 +1.697 92.4878 +1.93 0 diff --git a/datafiles/thrustcurves/KBA_J605.eng b/datafiles/thrustcurves/KBA_J605.eng new file mode 100644 index 000000000..9716ec228 --- /dev/null +++ b/datafiles/thrustcurves/KBA_J605.eng @@ -0,0 +1,15 @@ +; +; +J605F 38 476 14 0.367 0.88 Kosdon-by-AeroTech +0.024 886.341 +0.037 704.669 +0.077 660.627 +0.438 704.669 +0.506 715.679 +0.59 710.174 +0.853 655.122 +0.973 594.564 +1.041 412.892 +1.091 324.808 +1.177 132.125 +1.3 0 diff --git a/datafiles/thrustcurves/KBA_K1750.eng b/datafiles/thrustcurves/KBA_K1750.eng new file mode 100644 index 000000000..826fc735b --- /dev/null +++ b/datafiles/thrustcurves/KBA_K1750.eng @@ -0,0 +1,26 @@ +; +K1750R 54.0 728.00 0 1.25300 2.56000 KBA + 0.02 1309.09 + 0.03 1679.77 + 0.05 1736.54 + 0.11 1689.79 + 0.26 1799.99 + 0.40 1913.54 + 0.46 1896.84 + 0.68 2023.74 + 0.90 2133.94 + 0.95 2097.21 + 1.00 2050.46 + 1.05 1920.21 + 1.10 1793.31 + 1.16 1676.43 + 1.21 1719.85 + 1.25 1526.15 + 1.27 1302.41 + 1.32 874.95 + 1.35 454.17 + 1.36 317.25 + 1.37 200.37 + 1.40 90.17 + 1.46 0.00 +; diff --git a/datafiles/thrustcurves/KBA_K400.eng b/datafiles/thrustcurves/KBA_K400.eng new file mode 100644 index 000000000..cb90df9ca --- /dev/null +++ b/datafiles/thrustcurves/KBA_K400.eng @@ -0,0 +1,29 @@ +; +; +K400S 54 403 6-10-14 0.713216 1.50931 Kosdon-by-AeroTech +0.074 465.928 +0.225 441.922 +0.377 442.414 +0.529 445.492 +0.681 449.048 +0.833 451.88 +0.985 454.481 +1.138 456.929 +1.29 458.237 +1.442 457.021 +1.594 455.62 +1.746 451.772 +1.897 446.421 +2.048 438.843 +2.2 429.377 +2.352 419.003 +2.504 408.274 +2.656 397.608 +2.808 388.018 +2.96 367.07 +3.113 263.666 +3.265 114.378 +3.417 46.238 +3.569 8.62 +3.721 2.401 +3.873 0 diff --git a/datafiles/thrustcurves/KBA_K600.eng b/datafiles/thrustcurves/KBA_K600.eng new file mode 100644 index 000000000..a52b6f192 --- /dev/null +++ b/datafiles/thrustcurves/KBA_K600.eng @@ -0,0 +1,29 @@ +; Kosdon by AeroTech K600F +; provided by ThrustCurve.org (www.thrustcurve.org) +K600F 54 403 0 0.68096 1.41568 KBA + 0.045 639.654 + 0.148 711.292 + 0.252 695.617 + 0.358 696.252 + 0.462 701.336 + 0.568 703.242 + 0.670 705.265 + 0.772 704.302 + 0.878 702.819 + 0.982 701.336 + 1.088 696.888 + 1.192 689.262 + 1.295 681.245 + 1.398 668.928 + 1.502 653.465 + 1.608 637.366 + 1.712 619.785 + 1.818 599.451 + 1.920 586.275 + 2.022 510.698 + 2.128 334.676 + 2.232 125.397 + 2.338 37.916 + 2.442 17.157 + 2.548 4.025 + 2.653 0.000 diff --git a/datafiles/thrustcurves/KBA_K750.eng b/datafiles/thrustcurves/KBA_K750.eng new file mode 100644 index 000000000..2a40ae62e --- /dev/null +++ b/datafiles/thrustcurves/KBA_K750.eng @@ -0,0 +1,29 @@ +; Kosdon by Aerotech K750 White Lightning. +K750W 54 728 0 1.315 2.62 KBA + 0.0080 266.075 + 0.012 457.102 + 0.02 750.467 + 0.032 999.485 + 0.044 1112.055 + 0.06 1180.279 + 0.095 1098.41 + 0.127 1057.476 + 0.163 1040.42 + 0.334 1050.653 + 0.62 1054.064 + 0.998 975.607 + 1.324 907.382 + 1.69 903.971 + 2.06 886.915 + 2.184 828.924 + 2.299 757.289 + 2.394 651.541 + 2.502 556.028 + 2.609 450.28 + 2.784 327.476 + 2.999 245.607 + 3.039 201.261 + 3.134 92.103 + 3.206 40.935 + 3.337 6.822 + 3.468 0.0 diff --git a/datafiles/thrustcurves/KBA_L1000.eng b/datafiles/thrustcurves/KBA_L1000.eng new file mode 100644 index 000000000..d03b16d3e --- /dev/null +++ b/datafiles/thrustcurves/KBA_L1000.eng @@ -0,0 +1,29 @@ +; Kosdon by AeroTech L1000S +; provided by ThrustCurve.org (www.thrustcurve.org) +L1000S 54 728 0 1.232 2.32512 KBA + 0.055 795.305 + 0.175 981.574 + 0.295 989.173 + 0.415 1008.634 + 0.535 1028.836 + 0.655 1048.483 + 0.775 1067.573 + 0.895 1087.034 + 1.015 1108.719 + 1.135 1131.516 + 1.255 1156.908 + 1.375 1177.296 + 1.498 1199.596 + 1.620 1212.881 + 1.740 1227.153 + 1.860 1232.342 + 1.980 1249.950 + 2.100 1026.056 + 2.220 737.107 + 2.340 565.851 + 2.460 313.414 + 2.580 89.706 + 2.700 20.758 + 2.820 8.526 + 2.942 5.338 + 3.065 0.000 diff --git a/datafiles/thrustcurves/KBA_L1400.eng b/datafiles/thrustcurves/KBA_L1400.eng new file mode 100644 index 000000000..559ae8038 --- /dev/null +++ b/datafiles/thrustcurves/KBA_L1400.eng @@ -0,0 +1,18 @@ +; +; +L1400F 54 727 100 1.248 2.502 Kosdon-by-AeroTech +0.037 1541.46 +0.061 1453.38 +0.166 1354.29 +1.001 1772.68 +1.279 1783.69 +1.329 1882.79 +1.387 1992.89 +1.486 1387.32 +1.604 869.826 +1.65 748.711 +1.666 726.69 +1.69 924.878 +1.697 594.564 +1.758 319.303 +1.88 0 diff --git a/datafiles/thrustcurves/KBA_M1450.eng b/datafiles/thrustcurves/KBA_M1450.eng new file mode 100644 index 000000000..2374eb289 --- /dev/null +++ b/datafiles/thrustcurves/KBA_M1450.eng @@ -0,0 +1,22 @@ +; KBA M1450W +M1450W 75 1038.9 0 4.15 7.6000000000000005 KBA + 0.035 1842.929 + 0.076 2287.088 + 0.146 1968.884 + 0.215 1882.704 + 0.291 1836.299 + 0.499 1862.816 + 1.005 1935.738 + 1.559 1889.333 + 2.155 1816.412 + 2.862 1750.119 + 3.493 1663.939 + 3.853 1358.994 + 4.221 1060.678 + 4.484 788.88 + 4.761 523.71 + 4.942 258.54 + 5.323 258.54 + 5.6 172.36 + 5.801 119.326 + 5.96 0.0 diff --git a/datafiles/thrustcurves/Loki_H144.eng b/datafiles/thrustcurves/Loki_H144.eng new file mode 100644 index 000000000..2712a8061 --- /dev/null +++ b/datafiles/thrustcurves/Loki_H144.eng @@ -0,0 +1,25 @@ +; +; +H144 38 178 5-8-10-13-17 0.12 0.335 Loki +0.02 209 +0.04 247.6 +0.05 241.2 +0.1 247.6 +0.15 244.4 +0.2 237.9 +0.25 231.54 +0.3 228.3 +0.4 215.32 +0.45 212.43 +0.5 204.48 +0.6 194.36 +0.7 189.7 +0.8 170.4 +0.9 154.3 +1 127.83 +1.1 109.3 +1.2 80.4 +1.3 64.6 +1.4 44.6 +1.5 32.1 +1.6 0 diff --git a/datafiles/thrustcurves/Loki_H500.eng b/datafiles/thrustcurves/Loki_H500.eng new file mode 100644 index 000000000..20c571c1d --- /dev/null +++ b/datafiles/thrustcurves/Loki_H500.eng @@ -0,0 +1,13 @@ +; +; +H500 38 292 5-7-9-12-15 0.16 0.454 Loki +0.001 189.286 +0.0116009 534.733 +0.099768 539.465 +0.199536 544.197 +0.302784 553.662 +0.402552 548.93 +0.50464 544.197 +0.584687 435.358 +0.61949 9.4643 +0.62 0 diff --git a/datafiles/thrustcurves/Loki_I405.eng b/datafiles/thrustcurves/Loki_I405.eng new file mode 100644 index 000000000..87015bc02 --- /dev/null +++ b/datafiles/thrustcurves/Loki_I405.eng @@ -0,0 +1,23 @@ +; +; +I405 38 292 5-8-10-13-17 0.24 0.54 Loki +0.01 151.1 +0.03 781.4 +0.05 800.7 +0.06 755.7 +0.09 724.3 +0.12 697.7 +0.15 701 +0.17 675.3 +0.2 643.1 +0.3 607.7 +0.4 569.2 +0.5 517.7 +0.6 472.7 +0.7 392.3 +0.8 318.3 +0.9 241.2 +1 151.1 +1.1 93.3 +1.15 40 +1.2 0 diff --git a/datafiles/thrustcurves/Loki_J525.eng b/datafiles/thrustcurves/Loki_J525.eng new file mode 100644 index 000000000..2aae0e317 --- /dev/null +++ b/datafiles/thrustcurves/Loki_J525.eng @@ -0,0 +1,28 @@ +; +; +J525 54 327 0 0.59 1.264 Loki +0.01 210.9 +0.03 499.3 +0.05 628.5 +0.1 594 +0.13 568.2 +0.15 559.6 +0.2 555.3 +0.3 572.5 +0.4 589.7 +0.5 606.9 +0.6 624.2 +0.7 637.1 +0.8 645.7 +0.9 650 +1 658.6 +1.1 637.1 +1.2 628.5 +1.3 615.5 +1.41 586.27 +1.52 561.52 +1.67 536.78 +1.78 517.74 +1.85 485.38 +1.92 91.37 +2 0 diff --git a/datafiles/thrustcurves/Loki_J528.eng b/datafiles/thrustcurves/Loki_J528.eng new file mode 100644 index 000000000..00424fe1b --- /dev/null +++ b/datafiles/thrustcurves/Loki_J528.eng @@ -0,0 +1,27 @@ +; +J528 38 406 5-8-10-13-17 0.372 0.752 Loki +0.01 704.2 +0.02 1019 +0.03 983.9 +0.05 881.1 +0.1 797.5 +0.15 771.7 +0.17 765.72 +0.21 765.72 +0.25 778.2 +0.42 789.28 +0.51 771.61 +0.6 756.89 +0.66 751 +0.71 762.78 +0.76 697.99 +0.8 665.59 +0.84 612.58 +0.92 488.88 +0.95 385.81 +1.02 282.73 +1.06 179.65 +1.14 53.01 +1.19 35.34 +1.23 32.2 +1.25 0 diff --git a/datafiles/thrustcurves/Loki_K250.eng b/datafiles/thrustcurves/Loki_K250.eng new file mode 100644 index 000000000..c873dd417 --- /dev/null +++ b/datafiles/thrustcurves/Loki_K250.eng @@ -0,0 +1,24 @@ +; +; +K250 54 498 0 0.952544 1.79169 Loki +0.03 800 +0.1 682 +0.125 574 +0.15 476 +0.175 447 +0.25 385 +0.45 340 +0.6 320 +1 313 +1.5 300 +2 297 +2.5 303 +3 294 +3.5 287 +4 248 +4.5 222 +5 187 +5.5 147 +6 114 +6.5 62 +7 0 diff --git a/datafiles/thrustcurves/Loki_K350.eng b/datafiles/thrustcurves/Loki_K350.eng new file mode 100644 index 000000000..b7df74cd4 --- /dev/null +++ b/datafiles/thrustcurves/Loki_K350.eng @@ -0,0 +1,24 @@ +; +; +K350 54 702 0 1.4 2.54012 Loki +0.025 1329 +0.0375 1061 +0.1 1006 +0.15 891 +0.2 768 +0.4 571 +0.5 542 +0.75 486 +1 486 +1.25 477 +1.5 481 +2.5058 460 +3.00464 427 +3.5 375 +4 333 +4.5 297 +5 249 +5.5 210 +6 164 +6.5 98 +7 0 diff --git a/datafiles/thrustcurves/Loki_K960.eng b/datafiles/thrustcurves/Loki_K960.eng new file mode 100644 index 000000000..306132297 --- /dev/null +++ b/datafiles/thrustcurves/Loki_K960.eng @@ -0,0 +1,26 @@ +; +; +K960 54 498 0 0.929864 1.74633 Loki +0.03 1210 +0.05 1512 +0.075 1535 +0.1 1502 +0.125 1437 +0.2 1237 +0.3 1175 +0.5 1139 +0.6 1130 +0.7 1156 +0.8 1182 +0.9 1192 +1 1166 +1.1 1139 +1.2 1101 +1.3 1091 +1.4 1026 +1.5 839 +1.6 790 +1.7 575 +1.8 284 +1.9 150 +2 0 diff --git a/datafiles/thrustcurves/Loki_L1400.eng b/datafiles/thrustcurves/Loki_L1400.eng new file mode 100644 index 000000000..4ded67e6f --- /dev/null +++ b/datafiles/thrustcurves/Loki_L1400.eng @@ -0,0 +1,15 @@ +; +; +L1400 54 726 0 1.4 2.54 Loki +0.00580046 1606.3 +0.11891 1535.7 +0.327726 1535.7 +0.49884 1588.65 +1.00058 1782.82 +1.40661 1906.38 +1.49942 1376.83 +1.60673 953.19 +1.74594 547.202 +1.90545 335.382 +1.99826 211.82 +2 0 diff --git a/datafiles/thrustcurves/Loki_L930.eng b/datafiles/thrustcurves/Loki_L930.eng new file mode 100644 index 000000000..59dbeb5ae --- /dev/null +++ b/datafiles/thrustcurves/Loki_L930.eng @@ -0,0 +1,24 @@ +; +; +L930 76 498 0 1.81437 3.53802 Loki +0.025 532 +0.05 1123 +0.075 1123 +0.125 1094 +0.2 930 +0.5 881 +0.6 878 +0.75 898 +1 921 +1.25 940 +1.5 1012 +1.75 1081 +2 1100 +2.25 1120 +2.5 1051 +2.75 980 +3 934 +3.25 826 +3.5 722 +3.75 280 +4 0 diff --git a/datafiles/thrustcurves/Loki_M1882.eng b/datafiles/thrustcurves/Loki_M1882.eng new file mode 100644 index 000000000..8d4d68779 --- /dev/null +++ b/datafiles/thrustcurves/Loki_M1882.eng @@ -0,0 +1,17 @@ +; +; +M1882 75 785 0 3.12979 5.53383 Loki +0.01 4.8 +0.0174014 2579.22 +0.0696056 2392.32 +0.232019 2298.87 +0.50464 2261.49 +0.771462 2298.87 +0.986079 2411.01 +1.1891 2579.22 +1.49652 2597.91 +1.72854 2485.77 +2.00116 2354.94 +2.5 1644.72 +2.99884 242.97 +3.25 0 diff --git a/datafiles/thrustcurves/PML_F50.eng b/datafiles/thrustcurves/PML_F50.eng new file mode 100644 index 000000000..6970c3e40 --- /dev/null +++ b/datafiles/thrustcurves/PML_F50.eng @@ -0,0 +1,20 @@ +; +F50T 29.0 98.00 4-6-9 0.03790 0.08490 AT + 0.01 37.97 + 0.02 56.27 + 0.03 65.08 + 0.12 71.86 + 0.23 75.25 + 0.33 77.29 + 0.35 77.70 + 0.45 75.25 + 0.59 71.86 + 0.72 65.76 + 0.83 58.98 + 1.01 43.39 + 1.19 25.76 + 1.25 15.59 + 1.30 8.81 + 1.36 4.75 + 1.42 0.00 +; diff --git a/datafiles/thrustcurves/PML_G40.eng b/datafiles/thrustcurves/PML_G40.eng new file mode 100644 index 000000000..8bdc364b3 --- /dev/null +++ b/datafiles/thrustcurves/PML_G40.eng @@ -0,0 +1,22 @@ +; PML G40W is the same as the Aerotech G40W. +G40W 29 124 4-7-10 0.0624 0.115 PML + 0.015 40.0 + 0.037 66.479 + 0.066 60.845 + 0.161 55.775 + 0.308 55.775 + 0.447 54.085 + 0.549 52.394 + 0.637 51.268 + 0.857 52.958 + 1.018 52.394 + 1.172 50.141 + 1.362 46.761 + 1.611 41.69 + 1.691 41.127 + 1.845 34.366 + 1.999 30.423 + 2.372 23.099 + 2.585 13.521 + 2.738 6.761 + 3.039 0.0 diff --git a/datafiles/thrustcurves/PML_G80.eng b/datafiles/thrustcurves/PML_G80.eng new file mode 100644 index 000000000..c30becead --- /dev/null +++ b/datafiles/thrustcurves/PML_G80.eng @@ -0,0 +1,19 @@ +; PML G80T is the same as the old Aerotech G480T. +G80T 29 124 4-7-10 0.0574 0.106 PML + 0.0070 82.746 + 0.018 104.754 + 0.051 104.754 + 0.095 97.711 + 0.245 94.19 + 0.458 95.07 + 0.6 93.31 + 0.86 84.507 + 1.003 76.585 + 1.139 69.542 + 1.252 57.218 + 1.303 51.056 + 1.34 37.852 + 1.38 19.366 + 1.424 7.923 + 1.461 2.641 + 1.497 0.0 diff --git a/datafiles/thrustcurves/PP_H70.eng b/datafiles/thrustcurves/PP_H70.eng new file mode 100644 index 000000000..192ca4ce1 --- /dev/null +++ b/datafiles/thrustcurves/PP_H70.eng @@ -0,0 +1,15 @@ +; +H70 38 462 100 0.318 0.627 Propulsion-Polymers +0.05 170.9 +0.09 122.3 +0.37 106.8 +0.74 97.9 +1.11 93.4 +1.48 89 +1.84 84.5 +2.21 77.1 +2.33 74 +2.58 41.5 +2.95 23.7 +3.32 15.6 +3.69 0 diff --git a/datafiles/thrustcurves/PP_I160.eng b/datafiles/thrustcurves/PP_I160.eng new file mode 100644 index 000000000..c56c2fcb6 --- /dev/null +++ b/datafiles/thrustcurves/PP_I160.eng @@ -0,0 +1,15 @@ +; +; +I160 38 646 20-100 0.31 0.856 Propulsion-Polymers +0.04 298.9 +0.08 272.5 +0.32 264.7 +0.65 241.3 +0.97 218 +1.29 194.6 +1.61 179 +2 163.5 +2.26 93.4 +2.58 62.3 +2.9 31.1 +3.23 0 diff --git a/datafiles/thrustcurves/PP_I80.eng b/datafiles/thrustcurves/PP_I80.eng new file mode 100644 index 000000000..f6e0132d9 --- /dev/null +++ b/datafiles/thrustcurves/PP_I80.eng @@ -0,0 +1,17 @@ +; +; +I80 38 646 100 0.332 0.842 Propulsion-Polymers +0.04 198.7 +0.08 158.4 +0.57 133.4 +0.9 114.5 +1.15 110 +1.73 108 +2.3 106 +2.88 104 +3.45 96.3 +3.75 66.7 +4.03 50 +4.6 35.6 +5.18 22.2 +5.75 0 diff --git a/datafiles/thrustcurves/PP_J140.eng b/datafiles/thrustcurves/PP_J140.eng new file mode 100644 index 000000000..3102b9964 --- /dev/null +++ b/datafiles/thrustcurves/PP_J140.eng @@ -0,0 +1,17 @@ +; Propulsion Polymers 664NS-J140 +; from CAR data sheet +; created by John Coker 5/2006 +J140 38 881 P 0.626 1.166 PP + 0.02 249 + 0.20 320 + 0.25 240 + 0.30 236 + 0.35 191 + 0.45 236 + 1.15 223 + 1.60 178 + 3.05 165 + 3.40 102 + 4.25 45 + 4.95 22 + 5.00 0 diff --git a/datafiles/thrustcurves/Quest_A6.eng b/datafiles/thrustcurves/Quest_A6.eng new file mode 100644 index 000000000..9ce8d4240 --- /dev/null +++ b/datafiles/thrustcurves/Quest_A6.eng @@ -0,0 +1,8 @@ +; +; +A6Q 18 70 4 0.0035 0.0153 Quest +0.1 4.8 +0.2 11.82 +0.23 7.9 +0.3 4.8 +0.41 0 diff --git a/datafiles/thrustcurves/Quest_B6.eng b/datafiles/thrustcurves/Quest_B6.eng new file mode 100644 index 000000000..97d8266ea --- /dev/null +++ b/datafiles/thrustcurves/Quest_B6.eng @@ -0,0 +1,14 @@ +; +; +B6Q 18 70 0-2-4 0.0065 0.0162 Quest +0.1 7 +0.18 14.38 +0.2 10.2 +0.24 6.6 +0.3 6 +0.4 6.1 +0.5 6.2 +0.6 6.3 +0.65 6.6 +0.7 3 +0.75 0 diff --git a/datafiles/thrustcurves/Quest_C6.eng b/datafiles/thrustcurves/Quest_C6.eng new file mode 100644 index 000000000..8234f2092 --- /dev/null +++ b/datafiles/thrustcurves/Quest_C6.eng @@ -0,0 +1,31 @@ +; Quest C6-0 from NAR data +C6-0 18 70 0 0.0083 0.0216 Q + 0.02 0.497 + 0.057 2.539 + 0.089 5.132 + 0.129 7.947 + 0.159 9.437 + 0.171 21.247 + 0.181 23.234 + 0.194 22.958 + 0.204 22.185 + 0.218 19.592 + 0.233 17.881 + 0.258 10.486 + 0.308 2.428 + 0.338 2.539 + 0.385 2.98 + 0.412 3.091 + 0.442 3.422 + 0.459 2.98 + 0.536 3.256 + 0.732 3.311 + 0.747 2.483 + 0.78 2.98 + 1.323 3.587 + 1.365 2.815 + 1.887 3.808 + 1.974 3.256 + 2.1 3.532 + 2.227 3.201 + 2.247 0.0 diff --git a/datafiles/thrustcurves/Quest_D5.eng b/datafiles/thrustcurves/Quest_D5.eng new file mode 100644 index 000000000..74c5a2092 --- /dev/null +++ b/datafiles/thrustcurves/Quest_D5.eng @@ -0,0 +1,24 @@ +; Quest D5-0 by Mark Koelsch from NAR data +D5-0 20 88 0 0.025 0.0384 Q + 0.096 1.241 + 0.252 5.897 + 0.304 8.586 + 0.357 10.552 + 0.391 11.483 + 0.435 9.828 + 0.557 6.103 + 0.583 5.172 + 0.67 5.172 + 1.078 4.966 + 1.2 4.345 + 1.73 4.759 + 1.8 4.759 + 1.887 4.138 + 2.391 5.069 + 2.626 4.966 + 3.009 5.379 + 3.357 5.276 + 3.661 5.69 + 3.835 3.103 + 3.887 1.655 + 3.983 0.0 diff --git a/datafiles/thrustcurves/RATT_H70.eng b/datafiles/thrustcurves/RATT_H70.eng new file mode 100644 index 000000000..536d3f02d --- /dev/null +++ b/datafiles/thrustcurves/RATT_H70.eng @@ -0,0 +1,30 @@ +; RATT Works H70H +; Copyright Tripoli Motor Testing 2002 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +H70H 29 457 0 0.106176 0.348992 RTW + 0.051 124.137 + 0.156 134.902 + 0.261 127.047 + 0.367 115.870 + 0.473 110.286 + 0.578 108.630 + 0.683 105.741 + 0.789 104.108 + 0.894 101.515 + 1.000 98.471 + 1.105 93.809 + 1.210 90.543 + 1.316 85.219 + 1.421 74.353 + 1.527 57.739 + 1.632 44.019 + 1.738 34.554 + 1.843 27.992 + 1.948 22.691 + 2.054 19.064 + 2.159 15.665 + 2.265 12.897 + 2.370 11.332 + 2.475 10.107 + 2.581 8.591 + 2.688 0.000 diff --git a/datafiles/thrustcurves/RATT_I80.eng b/datafiles/thrustcurves/RATT_I80.eng new file mode 100644 index 000000000..a9025a665 --- /dev/null +++ b/datafiles/thrustcurves/RATT_I80.eng @@ -0,0 +1,30 @@ +; RATT Works I80H +; Copyright Tripoli Motor Testing 2002 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +I80H 29 730 0 0.21952 0.549248 RTW + 0.093 84.101 + 0.280 102.075 + 0.469 118.708 + 0.657 117.130 + 0.846 114.829 + 1.034 112.225 + 1.222 109.818 + 1.410 107.622 + 1.599 105.945 + 1.787 102.168 + 1.976 100.449 + 2.164 99.265 + 2.352 96.272 + 2.541 91.951 + 2.729 89.380 + 2.918 86.430 + 3.105 54.544 + 3.294 41.902 + 3.482 33.368 + 3.671 26.516 + 3.859 21.324 + 4.047 16.951 + 4.235 14.387 + 4.424 13.456 + 4.612 12.777 + 4.801 0.000 diff --git a/datafiles/thrustcurves/RATT_I90.eng b/datafiles/thrustcurves/RATT_I90.eng new file mode 100644 index 000000000..194c6bf30 --- /dev/null +++ b/datafiles/thrustcurves/RATT_I90.eng @@ -0,0 +1,30 @@ +; RATT Works I90LH +; Copyright Tripoli Motor Testing 2002 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +I90LH 29 921 0 0.285376 0.684544 RTW + 0.127 137.737 + 0.383 121.431 + 0.640 117.048 + 0.897 121.354 + 1.154 118.436 + 1.410 116.538 + 1.668 114.826 + 1.925 112.358 + 2.181 110.303 + 2.439 107.768 + 2.696 105.749 + 2.952 104.733 + 3.209 102.802 + 3.467 95.410 + 3.723 68.281 + 3.980 55.000 + 4.237 43.620 + 4.494 33.784 + 4.751 31.704 + 5.008 27.714 + 5.265 24.875 + 5.522 22.930 + 5.779 21.379 + 6.035 20.884 + 6.293 18.637 + 6.550 0.000 diff --git a/datafiles/thrustcurves/RATT_J160.eng b/datafiles/thrustcurves/RATT_J160.eng new file mode 100644 index 000000000..e39baf83d --- /dev/null +++ b/datafiles/thrustcurves/RATT_J160.eng @@ -0,0 +1,26 @@ +; +;J160 data entered by Tim Van Milligan +;Thrust Curve based on TRA certification dated 10/24/2003. +;Propellant weight based on 80F degree day, 490cc Oxidizer + 4.25g AP weight. +J160 38 1219 100 0.327926 0.544 RATT_Works +0.015456 262.049 +0.185471 255.442 +0.278207 240.028 +0.278207 229.017 +0.386399 251.038 +0.510046 242.23 +0.64915 213.603 +0.788253 189.38 +1.00464 178.369 +1.45286 160.753 +2.00927 147.54 +2.44204 129.923 +2.90572 121.115 +2.96754 162.955 +3.15301 149.742 +3.32303 125.519 +3.67852 94.6899 +3.97218 74.8711 +4.32767 46.2439 +4.69861 19.8188 +5.1 0 diff --git a/datafiles/thrustcurves/RATT_K240.eng b/datafiles/thrustcurves/RATT_K240.eng new file mode 100644 index 000000000..e5805bf20 --- /dev/null +++ b/datafiles/thrustcurves/RATT_K240.eng @@ -0,0 +1,30 @@ +; RATT Works K240H +; Copyright Tripoli Motor Testing 2002 (www.tripoli.org) +; provided by ThrustCurve.org (www.thrustcurve.org) +K240H 64 908 0 1.29338 2.81434 RTW + 0.164 413.978 + 0.493 392.975 + 0.822 370.841 + 1.151 362.058 + 1.480 337.839 + 1.809 321.401 + 2.139 285.118 + 2.468 280.031 + 2.798 275.060 + 3.128 278.392 + 3.457 274.394 + 3.786 252.302 + 4.116 272.858 + 4.445 264.671 + 4.774 255.572 + 5.103 210.314 + 5.433 163.955 + 5.764 126.239 + 6.093 98.015 + 6.422 74.491 + 6.751 55.617 + 7.080 40.778 + 7.409 29.618 + 7.739 21.915 + 8.069 17.214 + 8.399 0.000 diff --git a/datafiles/thrustcurves/RATT_L600.eng b/datafiles/thrustcurves/RATT_L600.eng new file mode 100644 index 000000000..aa97415c9 --- /dev/null +++ b/datafiles/thrustcurves/RATT_L600.eng @@ -0,0 +1,35 @@ +; +;Rattworks L600 Hybrid +;prepared by Andrew MacMillen NAR 77472 2/28/04 +;based on TMT thrust data from Paul Holmes & TMT cert doc +;NOX weight calc'd at .7048gm/cc, 75 deg F @ 812 psi +;compiled with RockSim7 EngEdit +;within 3 percent for total impulse +;peak and average thrust are off due to data spike smoothing +;accurate thrust profile +;NOTE: NOT RATT, CAR, TMT OR NAR APPROVED +;Rattworks L600 +L600 64 1066 100 1.863 2.25 RATT_Works +0 88.62 +0.02 153.82 +0.19 158.02 +0.35 169.94 +0.4 1127.04 +0.78 1001.2 +2.23 778.11 +2.84 736.24 +3.07 704 +3.62 673.43 +3.81 435.29 +3.89 350.43 +4.14 240.05 +4.31 197.19 +4.49 165.4 +4.66 139.26 +4.82 119.21 +5.01 99.01 +5.34 70.99 +5.93 49.74 +6.49 38.96 +6.98 36.42 +7.1 0 diff --git a/datafiles/thrustcurves/RATT_M900.eng b/datafiles/thrustcurves/RATT_M900.eng new file mode 100644 index 000000000..df948e79b --- /dev/null +++ b/datafiles/thrustcurves/RATT_M900.eng @@ -0,0 +1,23 @@ +; +;Rattworks M900 Hybrid +;prepared by Andrew MacMillen NAR 77472 12/23/04 +;based on TMT thrust data from Paul Holmes & TMT cert doc +;NOX weight calc'd at .7048gm/cc, 75 deg F @ 812 psi +;compiled with RockSim6 EngEdit +;within 2 percent for total impulse +;peak and average thrust are off due to data spike smoothing +;accurate thrust profile +;NOTE: NOT RATT, CAR, TMT OR NAR APPROVED +;Rattworks M900 +M900 69 1828 100 3.288 5.956 RATT_Works +0.04 151.9 +0.43 241.76 +0.5 1054.61 +7.04 718.06 +7.22 346.42 +7.26 297.14 +7.35 241.24 +7.48 200.65 +7.66 164.8 +12.28 49.36 +12.3 0 diff --git a/datafiles/thrustcurves/RV_F32.eng b/datafiles/thrustcurves/RV_F32.eng new file mode 100644 index 000000000..9c1decaf8 --- /dev/null +++ b/datafiles/thrustcurves/RV_F32.eng @@ -0,0 +1,11 @@ +; Rocketvision F32 +; from NAR data sheet updated 11/2000 +; created by John Coker 5/2006 +F32 24 124 5-10-15 .0377 .0695 RV + 0.01 50 + 0.05 56 + 0.10 48 + 2.00 24 + 2.20 19 + 2.45 5 + 2.72 0 diff --git a/datafiles/thrustcurves/RV_F72.eng b/datafiles/thrustcurves/RV_F72.eng new file mode 100644 index 000000000..f53740d33 --- /dev/null +++ b/datafiles/thrustcurves/RV_F72.eng @@ -0,0 +1,30 @@ +; Same motor as the Aerotech F72T single use from NAR cert data +F72T 24 124 5-10-15 0.0368 0.0746 Rocketvision + 0.0040 37.671 + 0.01 78.082 + 0.017 97.26 + 0.027 91.781 + 0.043 89.726 + 0.06 80.822 + 0.087 84.932 + 0.101 78.767 + 0.132 81.507 + 0.143 78.767 + 0.171 81.507 + 0.192 78.082 + 0.215 80.822 + 0.24 78.082 + 0.264 81.507 + 0.279 78.767 + 0.298 80.137 + 0.517 76.027 + 0.68 70.548 + 0.855 58.219 + 0.934 49.315 + 0.961 43.151 + 0.996 31.507 + 1.025 21.233 + 1.054 14.384 + 1.103 7.534 + 1.147 3.425 + 1.196 0.0 diff --git a/datafiles/thrustcurves/RV_G55.eng b/datafiles/thrustcurves/RV_G55.eng new file mode 100644 index 000000000..b2366d295 --- /dev/null +++ b/datafiles/thrustcurves/RV_G55.eng @@ -0,0 +1,30 @@ +; Same motor as the Aerotech G55W single use from NAR cert data +G55W 24 177 5-10-15 0.0625 0.115 Rocketvision + 0.0040 74.648 + 0.012 85.211 + 0.054 81.69 + 0.128 73.239 + 0.182 73.944 + 0.231 69.718 + 0.508 69.014 + 0.868 67.606 + 1.037 65.493 + 1.07 67.606 + 1.091 64.085 + 1.14 63.38 + 1.161 66.901 + 1.19 62.676 + 1.269 62.676 + 1.397 59.155 + 1.496 57.042 + 1.583 52.113 + 1.653 45.07 + 1.719 38.028 + 1.76 31.69 + 1.831 24.648 + 1.901 19.014 + 1.988 12.676 + 2.083 9.155 + 2.169 5.634 + 2.252 3.521 + 2.36 0.0 diff --git a/datafiles/thrustcurves/Roadrunner_E25.eng b/datafiles/thrustcurves/Roadrunner_E25.eng new file mode 100644 index 000000000..5ad73138c --- /dev/null +++ b/datafiles/thrustcurves/Roadrunner_E25.eng @@ -0,0 +1,35 @@ +;ROADRUNNER E25R WRASP FILE +E25R 29 76 4-7 0.02 0.078 RR + 0.0 1.15995 + 0.0 4.0904 + 0.01 13.9194 + 0.02 24.481 + 0.025 28.327 + 0.04 33.028 + 0.045 33.639 + 0.07 33.516 + 0.12 35.287 + 0.195 36.569 + 0.245 38.278 + 0.28 37.668 + 0.315 38.657 + 0.35 37.729 + 0.385 37.973 + 0.45 36.691 + 0.56 36.569 + 0.73 32.295 + 0.82 29.487 + 0.9 26.068 + 0.92 25.824 + 0.945 24.176 + 0.995 22.772 + 1.035 20.024 + 1.08 18.5592 + 1.165 14.2247 + 1.19 13.6142 + 1.31 7.2039 + 1.395 4.2735 + 1.445 3.602 + 1.49 2.1978 + 1.505 0.79365 + 1.506 0 diff --git a/datafiles/thrustcurves/Roadrunner_F35.eng b/datafiles/thrustcurves/Roadrunner_F35.eng new file mode 100644 index 000000000..d2486dc63 --- /dev/null +++ b/datafiles/thrustcurves/Roadrunner_F35.eng @@ -0,0 +1,37 @@ +; ROADRUNNER F35 RASP.ENG FILE +; File produced April 5, 2006 +; The total impulse, peak thrust, average thrust and burn time are +; the same as the averaged static test data on the NAR web site in +; the certification file. The curve drawn with these data points is as +; close to the certification curve as can be with such a limited +; number of points (32) allowed with wRASP up to v1.6. +F35 29 112 6-10 0.040 0.111 RR +0.023 33.700 +0.040 44.462 +0.081 47.206 +0.121 48.579 +0.166 49.270 +0.242 49.550 +0.315 51.010 +0.411 50.111 +0.528 49.710 +0.664 48.208 +0.791 47.256 +0.896 46.986 +1.000 45.484 +1.097 44.943 +1.194 42.338 +1.277 40.400 +1.323 40.706 +1.356 37.691 +1.402 35.637 +1.451 32.753 +1.505 29.467 +1.578 25.491 +1.675 20.833 +1.750 17.137 +1.828 13.021 +1.907 8.638 +1.984 5.075 +2.049 2.610 +2.130 0.000 diff --git a/datafiles/thrustcurves/Roadrunner_F45.eng b/datafiles/thrustcurves/Roadrunner_F45.eng new file mode 100644 index 000000000..11833a21d --- /dev/null +++ b/datafiles/thrustcurves/Roadrunner_F45.eng @@ -0,0 +1,33 @@ +; ROADRUNNER F45R RASP ENG FILE +F45R 29 93 5-8-14 0.03 0.093 RR + 0.0 4.1971 + 0.019 45.500 + 0.038 53.070 + 0.057 52.402 + 0.095 54.741 + 0.113 55.298 + 0.132 56.744 + 0.151 57.190 + 0.227 60.419 + 0.284 61.754 + 0.416 62.422 + 0.491 60.753 + 0.510 61.420 + 0.567 60.307 + 0.624 58.414 + 0.662 58.080 + 0.737 55.298 + 0.775 52.959 + 0.813 51.513 + 0.888 46.169 + 0.983 34.701 + 1.002 31.807 + 1.096 23.902 + 1.134 21.453 + 1.210 14.106 + 1.229 12.992 + 1.285 8.4282 + 1.342 5.5328 + 1.361 5.4218 + 1.418 2.9723 + 1.420 0.0 diff --git a/datafiles/thrustcurves/Roadrunner_F60.eng b/datafiles/thrustcurves/Roadrunner_F60.eng new file mode 100644 index 000000000..d8cacf977 --- /dev/null +++ b/datafiles/thrustcurves/Roadrunner_F60.eng @@ -0,0 +1,34 @@ +; +; ROADRUNNER F60 RASP.ENG FILE +; The total impulse, peak thrust, average thrust and burn time are +; the same as the averaged static test data on the NAR web site in +; the certification file. The curve drawn with these data points is as +; close to the certification curve as can be with such a limited +; number of points (32) allowed with wRASP up to v1.6. +F60R 29 112 4-7-10 0.038 0.109 RR +0.013 45.860 +0.021 63.937 +0.029 72.291 +0.041 75.214 +0.061 74.374 +0.087 76.872 +0.155 83.122 +0.231 86.440 +0.309 88.088 +0.329 90.070 +0.345 88.33 +0.395 87.90 +0.454 87.208 +0.514 87.188 +0.616 82.141 +0.699 77.105 +0.765 70.400 +0.807 61.611 +0.859 51.983 +0.926 42.355 +0.978 33.556 +1.022 21.430 +1.061 13.056 +1.101 6.776 +1.133 3.423 +1.190 0.000 diff --git a/datafiles/thrustcurves/Roadrunner_G80.eng b/datafiles/thrustcurves/Roadrunner_G80.eng new file mode 100644 index 000000000..4d9ababc7 --- /dev/null +++ b/datafiles/thrustcurves/Roadrunner_G80.eng @@ -0,0 +1,35 @@ +; +; ROADRUNNER G80 RASP.ENG FILE +; The total impulse, peak thrust, average thrust and burn time are +; the same as the averaged static test data on the NAR web site in +; the certification file. The curve drawn with these data points is as +; close to the certification curve as can be with such a limited +; number of points (32) allowed with wRASP up to v1.6. +G80R 29 140 4-7-10 0.055 0.133 RR +0.012 63.563 +0.028 84.077 +0.057 89.563 +0.119 96.03 +0.206 102.518 +0.242 104.42 +0.297 106.923 +0.356 109.826 +0.422 111.829 +0.483 111.328 +0.558 112.632 +0.622 112.750 +0.683 112.129 +0.739 109.125 +0.796 102.017 +0.863 90.494 +0.901 82.750 +0.935 72.41 +0.976 59.869 +1.018 49.826 +1.028 44.321 +1.042 39.805 +1.073 28.272 +1.113 18.231 +1.170 11.176 +1.218 4.636 +1.310 0.000 diff --git a/datafiles/thrustcurves/SF_A8.eng b/datafiles/thrustcurves/SF_A8.eng new file mode 100644 index 000000000..6c9c9081b --- /dev/null +++ b/datafiles/thrustcurves/SF_A8.eng @@ -0,0 +1,15 @@ +; Sachsen Feuerwerk / WECO Feuerwerk A8-3 +; Created by Sampo Niskanen +; Data taken from: +; http://www.raketenmodellbautechnik.de/produkte/Motoren/SF-Motoren.pdf +A8 18 70 3 0.00312 0.0153 SF + 0.065 0.44 + 0.11 1.832 + 0.199 6.412 + 0.298 12.0 + 0.332 7.805 + 0.363 5.716 + 0.438 4.397 + 0.462 4.379 + 0.555 1.227 + 0.62 0.0 diff --git a/datafiles/thrustcurves/SF_B4.eng b/datafiles/thrustcurves/SF_B4.eng new file mode 100644 index 000000000..1a0db3464 --- /dev/null +++ b/datafiles/thrustcurves/SF_B4.eng @@ -0,0 +1,15 @@ +; Sachsen Feuerwerk / WECO Feuerwerk B4-0, B4-4 +; Created by Sampo Niskanen +; Data taken from: +; http://www.raketenmodellbautechnik.de/produkte/Motoren/SF-Motoren.pdf +B4 18 70 0-4 0.00833 0.0195 SF + 0.088 0.542 + 0.167 3.007 + 0.319 11.678 + 0.373 5.297 + 0.525 4.091 + 0.648 3.461 + 0.8 3.321 + 1.305 3.321 + 1.389 0.455 + 1.443 0.0 diff --git a/datafiles/thrustcurves/SF_C2.eng b/datafiles/thrustcurves/SF_C2.eng new file mode 100644 index 000000000..4698c6dd1 --- /dev/null +++ b/datafiles/thrustcurves/SF_C2.eng @@ -0,0 +1,29 @@ +; Sachsen Feuerwerk / WECO Feuerwerk Held 1000 +; Created by Sampo Niskanen +; True propellant weight unknown +; Data taken from: +; http://www.raketenmodellbautechnik.de/produkte/Motoren/SF-Motoren.pdf +C2 15 95 P 0.012 0.024 SF + 0.075 3.543 + 0.16 8.231 + 0.184 8.007 + 0.255 3.134 + 0.316 1.765 + 0.444 1.304 + 0.821 1.251 + 0.963 1.04 + 1.538 1.277 + 1.736 1.133 + 2.491 1.317 + 2.704 1.264 + 3.397 1.436 + 3.907 1.436 + 4.157 1.277 + 4.459 1.449 + 4.469 2.292 + 4.53 1.436 + 4.917 1.449 + 4.931 2.292 + 4.969 1.422 + 5.002 1.436 + 5.068 0.0 diff --git a/datafiles/thrustcurves/SF_C6.eng b/datafiles/thrustcurves/SF_C6.eng new file mode 100644 index 000000000..323945168 --- /dev/null +++ b/datafiles/thrustcurves/SF_C6.eng @@ -0,0 +1,20 @@ +; Sachsen Feuerwerk / WECO Feuerwerk C6-0, C6-3, C6-5 +; Created by Sampo Niskanen +; Data taken from: +; http://www.raketenmodellbautechnik.de/produkte/Motoren/SF-Motoren.pdf +C6 18 70 0-3-5 0.01248 0.022 SF + 0.096 0.579 + 0.152 2.441 + 0.184 4.372 + 0.312 11.642 + 0.354 11.589 + 0.395 6.269 + 0.441 5.127 + 0.537 4.091 + 0.643 3.529 + 0.983 3.301 + 1.162 3.249 + 1.217 3.02 + 1.882 3.652 + 1.919 1.141 + 1.997 0.0 diff --git a/datafiles/thrustcurves/SF_D7.eng b/datafiles/thrustcurves/SF_D7.eng new file mode 100644 index 000000000..9b424fb81 --- /dev/null +++ b/datafiles/thrustcurves/SF_D7.eng @@ -0,0 +1,18 @@ +; Sachsen Feuerwerk / WECO Feuerwerk D7-0, D7-3 +; Created by Sampo Niskanen +; Data taken from: +; http://www.raketenmodellbautechnik.de/produkte/Motoren/SF-Motoren.pdf +D7 25 70 0-3 0.019 0.043 SF + 0.079 1.625 + 0.179 6.979 + 0.326 18.992 + 0.355 19.407 + 0.372 20.426 + 0.422 20.331 + 0.48 14.085 + 0.538 11.536 + 0.68 9.815 + 0.96 7.839 + 1.34 8.253 + 1.461 2.167 + 1.582 0.0 diff --git a/datafiles/thrustcurves/SkyR_G125.eng b/datafiles/thrustcurves/SkyR_G125.eng new file mode 100644 index 000000000..475ed67d8 --- /dev/null +++ b/datafiles/thrustcurves/SkyR_G125.eng @@ -0,0 +1,17 @@ +; +; +; +G125 38.0 408.00 1 0.15800 0.53700 SRS + 0.01 346.87 + 0.04 325.72 + 0.07 324.57 + 0.08 415.57 + 0.09 219.61 + 0.13 194.84 + 0.20 177.77 + 0.40 157.13 + 0.60 131.89 + 0.80 88.31 + 1.00 42.44 + 1.09 14.64 + 1.20 0.00 diff --git a/datafiles/thrustcurves/SkyR_G63.eng b/datafiles/thrustcurves/SkyR_G63.eng new file mode 100644 index 000000000..988d871c4 --- /dev/null +++ b/datafiles/thrustcurves/SkyR_G63.eng @@ -0,0 +1,21 @@ +; +;Sky Ripper Systems 29/75 G63 +G63 29 304.80 0 .06500 .23600 SRS + 0.01 121.91 + 0.03 142.66 + 0.07 156.57 + 0.09 133.60 + 0.13 100.62 + 0.18 114.82 + 0.20 105.21 + 0.27 104.91 + 0.35 93.38 + 0.41 83.85 + 0.50 67.95 + 0.65 61.99 + 0.83 61.99 + 0.93 40.14 + 1.09 17.09 + 1.18 13.78 + 1.29 5.56 + 1.30 0.00 diff --git a/datafiles/thrustcurves/SkyR_G69.eng b/datafiles/thrustcurves/SkyR_G69.eng new file mode 100644 index 000000000..7c73a6a9e --- /dev/null +++ b/datafiles/thrustcurves/SkyR_G69.eng @@ -0,0 +1,29 @@ +; +; +;Sky Ripper Systems 29/125 G69 +G69 29 406.40 0 .10700 .33300 SRS + 0.01 99.80 + 0.04 137.51 + 0.07 103.01 + 0.13 94.13 + 0.30 80.09 + 0.49 72.20 + 0.57 70.72 + 0.65 71.96 + 0.74 80.83 + 0.81 81.81 + 0.94 73.43 + 1.01 74.42 + 1.06 85.02 + 1.11 83.78 + 1.19 62.10 + 1.24 60.62 + 1.32 65.30 + 1.37 64.81 + 1.43 52.98 + 1.49 48.55 + 1.55 44.85 + 1.64 28.59 + 1.85 17.74 + 1.99 14.29 + 2.00 0.00 diff --git a/datafiles/thrustcurves/SkyR_H124.eng b/datafiles/thrustcurves/SkyR_H124.eng new file mode 100644 index 000000000..88c0bcb1b --- /dev/null +++ b/datafiles/thrustcurves/SkyR_H124.eng @@ -0,0 +1,34 @@ +; +; Sky Ripper Systems 38mm hybrid motors +; prepared for SRS by Andrew MacMillen NAR 77472 8/10/04 +; +; based on TMT thrust data & cert docs +; NOX weight calc'd at 90 deg. F; 0.5469 gm/cc; 984 psi +; compiled with RockSim6 EngEdit +; +; +; SRS H124 38/220 PVC +H124_pvc 38.0 508.00 0 0.14200 0.66400 SRS + 0.01 198.24 + 0.02 300.84 + 0.03 314.34 + 0.03 316.00 + 0.04 305.39 + 0.08 280.35 + 0.11 221.43 + 0.69 168.38 + 0.78 180.85 + 0.81 158.64 + 0.84 167.59 + 0.89 152.32 + 0.92 120.70 + 0.95 123.55 + 1.06 82.23 + 1.35 48.36 + 1.55 25.63 + 1.60 21.88 + 1.62 23.66 + 1.66 18.58 + 1.67 12.46 + 1.68 0.00 +; diff --git a/datafiles/thrustcurves/SkyR_H155.eng b/datafiles/thrustcurves/SkyR_H155.eng new file mode 100644 index 000000000..d77c6a9cf --- /dev/null +++ b/datafiles/thrustcurves/SkyR_H155.eng @@ -0,0 +1,21 @@ +; +; Sky Ripper Systems 38mm hybrid motors +; prepared by Andrew MacMillen NAR 77472 8/10/04 +; +; based on TMT thrust data & cert docs +; NOX weight calc'd at 90 deg. F; 0.5469 gm/cc; 984 psi +; compiled with RockSim6 EngEdit +; +; +; SRS H155 38/220 PP +H155_pp 38.0 508.00 0 0.14200 0.66400 SRS + 0.03 308.75 + 0.05 362.22 + 0.09 252.41 + 1.02 102.11 + 1.08 119.98 + 1.45 33.45 + 1.66 19.06 + 1.68 12.59 + 1.69 0.00 +; diff --git a/datafiles/thrustcurves/SkyR_H78.eng b/datafiles/thrustcurves/SkyR_H78.eng new file mode 100644 index 000000000..3cc038238 --- /dev/null +++ b/datafiles/thrustcurves/SkyR_H78.eng @@ -0,0 +1,27 @@ +; +;Sky Ripper Systems 29/185 H78 +H78 29 520.70 0 .15800 .41800 SRS + 0.01 138.21 + 0.08 150.10 + 0.12 142.67 + 0.15 132.27 + 0.22 130.49 + 0.29 90.30 + 0.34 88.27 + 0.40 86.81 + 0.61 86.52 + 0.72 81.59 + 0.76 71.72 + 0.86 64.46 + 1.01 63.30 + 1.18 62.42 + 1.28 60.39 + 1.50 57.49 + 1.80 58.36 + 1.89 59.23 + 2.01 55.46 + 2.21 36.29 + 2.36 22.94 + 2.54 13.36 + 2.72 9.58 + 2.75 0.00 diff --git a/datafiles/thrustcurves/SkyR_I117.eng b/datafiles/thrustcurves/SkyR_I117.eng new file mode 100644 index 000000000..aad7b0df5 --- /dev/null +++ b/datafiles/thrustcurves/SkyR_I117.eng @@ -0,0 +1,32 @@ +; +; Sky Ripper Systems 38mm hybrid motors +; prepared by Andrew MacMillen NAR 77472 8/10/04 +; +; based on TMT thrust data & cert docs +; NOX weight calc'd at 90 deg. F; 0.5469 gm/cc; 984 psi +; compiled with RockSim6 EngEdit +; +; +; SRS I117 38/580 PVC +I117_pvc 38.0 914.00 0 0.37000 1.13300 SRS + 0.01 203.36 + 0.02 339.95 + 0.02 358.77 + 0.03 367.94 + 0.03 361.47 + 0.06 277.40 + 0.08 277.11 + 0.15 250.99 + 0.27 256.14 + 0.30 248.56 + 0.42 194.78 + 0.47 222.59 + 1.35 174.82 + 1.39 184.03 + 1.79 150.12 + 2.12 156.59 + 3.42 54.16 + 3.80 40.00 + 4.20 17.22 + 4.23 0.00 +; diff --git a/datafiles/thrustcurves/SkyR_I119.eng b/datafiles/thrustcurves/SkyR_I119.eng new file mode 100644 index 000000000..7821c00e4 --- /dev/null +++ b/datafiles/thrustcurves/SkyR_I119.eng @@ -0,0 +1,31 @@ +; +; Sky Ripper Systems 38mm hybrid motors +; prepared by Andrew MacMillen NAR 77472 8/10/04 +; +; based on TMT thrust data & cert docs +; NOX weight calc'd at 90 deg. F; 0.5469 gm/cc; 984 psi +; compiled with RockSim6 EngEdit +; +; +; SRS I119 38/400 PVC +I119_pvc 38.0 711.00 0 0.26400 0.96700 SRS + 0.00 192.14 + 0.01 262.20 + 0.02 341.53 + 0.03 338.42 + 0.06 221.25 + 0.07 195.93 + 0.11 189.23 + 0.15 202.20 + 0.18 233.36 + 0.47 230.26 + 0.99 200.90 + 1.18 165.36 + 1.27 172.11 + 1.48 162.61 + 2.18 74.18 + 2.92 24.69 + 2.97 10.98 + 3.10 10.06 + 3.12 0.00 +; diff --git a/datafiles/thrustcurves/SkyR_I147.eng b/datafiles/thrustcurves/SkyR_I147.eng new file mode 100644 index 000000000..921ed1a54 --- /dev/null +++ b/datafiles/thrustcurves/SkyR_I147.eng @@ -0,0 +1,28 @@ +; +; Sky Ripper Systems 38mm hybrid motors +; prepared by Andrew MacMillen NAR 77472 8/10/04 +; +; based on TMT thrust data & cert docs +; NOX weight calc'd at 90 deg. F; 0.5469 gm/cc; 984 psi +; compiled with RockSim6 EngEdit +; +; +; SRS I147 38/400 PP +I147_pp 38.0 711.00 0 0.26400 0.96700 SRS + 0.00 194.47 + 0.02 366.71 + 0.03 390.09 + 0.03 392.30 + 0.05 359.02 + 0.07 217.40 + 0.10 207.68 + 0.38 241.52 + 1.07 183.80 + 2.33 141.34 + 2.51 100.04 + 2.75 71.51 + 3.32 34.49 + 3.89 17.18 + 4.43 7.17 + 4.45 0.00 +; diff --git a/datafiles/thrustcurves/SkyR_J144.eng b/datafiles/thrustcurves/SkyR_J144.eng new file mode 100644 index 000000000..c4f4dadbe --- /dev/null +++ b/datafiles/thrustcurves/SkyR_J144.eng @@ -0,0 +1,29 @@ +; +; Sky Ripper Systems 38mm hybrid motors +; prepared by Andrew MacMillen NAR 77472 8/10/04 +; +; based on TMT thrust data & cert docs +; NOX weight calc'd at 90 deg. F; 0.5469 gm/cc; 984 psi +; compiled with RockSim6 EngEdit +; +; +; SRS J144 38/580 PP +J144_pp 38.0 914.00 0 0.37000 1.13300 SRS + 0.01 301.25 + 0.03 376.44 + 0.03 376.86 + 0.08 247.31 + 0.22 273.45 + 0.45 263.59 + 0.79 231.99 + 1.09 175.69 + 1.21 185.68 + 2.05 149.60 + 2.86 127.64 + 3.42 69.07 + 3.88 42.69 + 4.59 20.49 + 5.20 10.00 + 5.80 6.12 + 5.80 0.00 +; diff --git a/datafiles/thrustcurves/SkyR_J261.eng b/datafiles/thrustcurves/SkyR_J261.eng new file mode 100644 index 000000000..ebfa8323a --- /dev/null +++ b/datafiles/thrustcurves/SkyR_J261.eng @@ -0,0 +1,33 @@ +;Sky Ripper Systems 54/830 J261 Gold Insert +J261 54.0 731.50 0 0.56470 1.90250 SRS + 0.02 1303.39 + 0.09 903.88 + 0.21 591.22 + 0.42 349.57 + 0.61 304.62 + 0.81 329.59 + 1.01 277.16 + 1.21 294.64 + 1.39 232.21 + 1.61 197.26 + 1.82 255.99 + 2.00 274.66 + 2.20 269.67 + 2.41 189.77 + 2.61 294.64 + 2.82 212.24 + 3.01 254.69 + 3.21 225.29 + 3.41 194.76 + 3.59 156.32 + 3.74 229.72 + 3.85 237.71 + 3.90 156.32 + 4.00 112.76 + 4.20 140.19 + 4.40 192.26 + 4.48 106.66 + 4.61 54.86 + 4.76 33.52 + 4.95 0.00 +; diff --git a/datafiles/thrustcurves/SkyR_J263.eng b/datafiles/thrustcurves/SkyR_J263.eng new file mode 100644 index 000000000..963a225f8 --- /dev/null +++ b/datafiles/thrustcurves/SkyR_J263.eng @@ -0,0 +1,26 @@ +;Sky Ripper Systems 54/550 J263 Gold Insert +J263 54.0 606.50 0 0.37420 1.59830 SRS + 0.01 1359.46 + 0.09 794.32 + 0.15 559.93 + 0.21 419.30 + 0.40 296.89 + 0.61 286.48 + 0.81 265.64 + 1.01 244.81 + 1.20 247.41 + 1.40 244.81 + 1.60 231.79 + 1.81 200.53 + 2.01 205.74 + 2.21 205.74 + 2.41 195.32 + 2.60 174.49 + 2.80 166.68 + 2.91 161.47 + 3.00 138.03 + 3.08 98.96 + 3.21 62.50 + 3.41 28.74 + 3.60 0.00 +; diff --git a/datafiles/thrustcurves/SkyR_J337.eng b/datafiles/thrustcurves/SkyR_J337.eng new file mode 100644 index 000000000..a60b6e6dc --- /dev/null +++ b/datafiles/thrustcurves/SkyR_J337.eng @@ -0,0 +1,22 @@ +;Sky Ripper Systems 54/830 J337 Black Insert +J337 54.0 731.50 0 0.56470 1.90250 SRS + 0.00 8.88 + 0.01 1521.03 + 0.10 585.29 + 0.20 651.28 + 0.41 589.45 + 0.50 548.23 + 0.60 539.99 + 0.99 461.67 + 1.40 416.33 + 1.60 391.59 + 1.80 387.47 + 1.91 364.90 + 2.01 239.43 + 2.20 171.02 + 2.40 119.72 + 2.60 72.68 + 2.80 42.76 + 3.00 21.68 + 3.10 0.00 +; diff --git a/datafiles/thrustcurves/SkyR_J348.eng b/datafiles/thrustcurves/SkyR_J348.eng new file mode 100644 index 000000000..efd3d0fba --- /dev/null +++ b/datafiles/thrustcurves/SkyR_J348.eng @@ -0,0 +1,19 @@ +;Sky Ripper Systems 54/550 J348 Black Insert +J348 54.0 606.50 0 0.37420 1.59830 SRS + 0.00 8.88 + 0.02 451.80 + 0.13 557.60 + 0.14 1203.84 + 0.20 617.65 + 0.40 549.02 + 0.60 494.69 + 0.79 406.05 + 1.01 354.57 + 1.20 334.56 + 1.31 303.10 + 1.40 237.34 + 1.60 148.69 + 1.80 82.92 + 2.00 37.17 + 2.20 0.00 +; diff --git a/datafiles/thrustcurves/SkyR_K257.eng b/datafiles/thrustcurves/SkyR_K257.eng new file mode 100644 index 000000000..a926d86c2 --- /dev/null +++ b/datafiles/thrustcurves/SkyR_K257.eng @@ -0,0 +1,34 @@ +;Sky Ripper Systems 54/1130 K257 Gold Insert +K257 54.0 911.40 0 0.76880 2.31070 SRS + 0.07 1133.08 + 0.20 529.98 + 0.40 333.69 + 0.59 363.13 + 0.80 347.18 + 0.98 366.40 + 1.20 356.59 + 1.40 310.79 + 1.60 274.80 + 1.81 337.71 + 2.00 356.59 + 2.20 294.43 + 2.40 314.06 + 2.60 294.43 + 2.81 333.69 + 3.00 340.23 + 3.22 319.78 + 3.41 292.68 + 3.59 249.32 + 3.79 278.07 + 4.00 243.90 + 4.21 281.35 + 4.41 235.54 + 4.60 251.90 + 4.81 211.38 + 5.01 222.22 + 5.20 189.70 + 5.41 153.23 + 5.60 81.40 + 5.80 23.94 + 6.10 0.00 +; diff --git a/datafiles/thrustcurves/SkyR_K347.eng b/datafiles/thrustcurves/SkyR_K347.eng new file mode 100644 index 000000000..aee6041ef --- /dev/null +++ b/datafiles/thrustcurves/SkyR_K347.eng @@ -0,0 +1,28 @@ +;Sky Ripper Systems 54/1130 K347 Black Insert +K347 54.0 911.40 0 0.76880 2.31070 SRS + 0.00 8.88 + 0.03 1474.51 + 0.11 972.20 + 0.19 737.25 + 0.40 697.22 + 0.61 673.87 + 0.81 653.85 + 1.01 600.40 + 1.20 550.44 + 1.40 530.42 + 1.60 533.76 + 1.80 517.08 + 1.89 396.98 + 1.95 493.19 + 2.06 393.65 + 2.13 443.69 + 2.30 365.38 + 2.30 292.31 + 2.40 257.92 + 2.60 184.84 + 2.79 124.66 + 3.00 81.67 + 3.21 51.58 + 3.41 21.49 + 3.58 0.00 +; diff --git a/datafiles/thrustcurves/WCH_I110.eng b/datafiles/thrustcurves/WCH_I110.eng new file mode 100644 index 000000000..6f73ee8a2 --- /dev/null +++ b/datafiles/thrustcurves/WCH_I110.eng @@ -0,0 +1,32 @@ +; +; West Coast Hybrid motor +; prepared for WestCoast/Scott Harrison by +; Andrew MacMillen NAR 77472 9/13/02 +; +; based on CAR thrust curves & cert letters +; since the cert letters and test curves don't agree +; the data is hand entered data points from CAR thrust curves +; NOX weight calc'd at 90 deg. F; 0.5469 gm/cc; 984 psi +; compiled with RockSim5 EngEdit +; +; within 2-3 percent for total impulse, peak and average thrust +; accurate thrust profile +; +; NOTE: NOT WCH, CAR, TMT OR NAR APPROVED +; +; West Coast Hybrids I110 +; +I110H 38.0 606.00 0 0.32700 0.82400 WCH + 0.05 240.00 + 0.38 200.00 + 0.76 182.00 + 1.14 160.00 + 1.52 142.00 + 1.90 125.00 + 2.23 113.00 + 2.66 71.00 + 3.04 44.00 + 3.42 29.00 + 3.78 22.00 + 4.00 0.00 +; diff --git a/extra-lib/RXTXcomm.jar b/extra-lib/RXTXcomm.jar new file mode 100644 index 000000000..84e5f01df Binary files /dev/null and b/extra-lib/RXTXcomm.jar differ diff --git a/extra-src/altimeter/Alt15K.java b/extra-src/altimeter/Alt15K.java new file mode 100644 index 000000000..0fba13561 --- /dev/null +++ b/extra-src/altimeter/Alt15K.java @@ -0,0 +1,562 @@ +package altimeter; + +import gnu.io.CommPortIdentifier; +import gnu.io.PortInUseException; +import gnu.io.SerialPort; +import gnu.io.UnsupportedCommOperationException; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.nio.charset.Charset; +import java.text.DateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.Enumeration; +import java.util.TimeZone; + +/** + * Class to interface the PerfectFlite Alt15K/WD altimeter. + * + * Also includes a main method that retrieves all flight profiles and saves them to files. + * + * @author Sampo Niskanen + */ + +public class Alt15K { + public static final int TIMEOUT = 500; + public static final int RWDELAY = 5; + + private static final boolean DEBUG = false; + + private static final Charset CHARSET = Charset.forName("ISO-8859-1"); + + private final CommPortIdentifier portID; + private SerialPort port = null; + private InputStream is = null; + private OutputStream os = null; + + + + @SuppressWarnings("unchecked") + public static String[] getNames() { + ArrayList list = new ArrayList();; + + Enumeration pids = CommPortIdentifier.getPortIdentifiers(); + + while (pids.hasMoreElements()) { + CommPortIdentifier pid = (CommPortIdentifier) pids.nextElement(); + + if (pid.getPortType() == CommPortIdentifier.PORT_SERIAL) + list.add(pid.getName()); + } + return list.toArray(new String[0]); + } + + + + @SuppressWarnings("unchecked") + public Alt15K(String name) throws IOException { + CommPortIdentifier pID = null; + + Enumeration portIdentifiers = CommPortIdentifier.getPortIdentifiers(); + while (portIdentifiers.hasMoreElements()) { + CommPortIdentifier pid = (CommPortIdentifier) portIdentifiers.nextElement(); + + if(pid.getPortType() == CommPortIdentifier.PORT_SERIAL && + pid.getName().equals(name)) { + pID = pid; + break; + } + } + + if (pID==null) { + throw new IOException("Port '"+name+"' not found."); + } + this.portID = pID; + } + + + /** + * Get altimeter flight data. The flight profile is chosen by the parameter n, + * 0 = latest flight, 1 = second latest, etc. + * + * @param n Which flight profile to use (0=newest, 1=second newest, etc) + * @return The altimeter flight data + * @throws IOException in case of IOException + * @throws PortInUseException in case of PortInUseException + */ + public AltData getData(int n) throws IOException, PortInUseException { + AltData alt = new AltData(); + ArrayList data = new ArrayList(); + byte[] buf; + byte[] buf2 = new byte[0]; + boolean identical = false; // Whether identical lines have been read + + if (DEBUG) + System.out.println(" Retrieving altimeter data n="+n); + + try { + open(); + + // Get version and position data + byte[] ver = getVersionData(); + alt.setVersion(new byte[] { ver[0],ver[1] }); + + // Calculate the position requested + if (n > 2) + n = 2; + int position = ver[2] - n; + while (position < 0) + position += 3; + + if (DEBUG) + System.out.println(" Requesting data from position "+position); + + // Request the data + write("D"); + write((byte)position); + write("PS"); + + sleep(); + + // Read preliminary data + buf = read(4); + int msl_level = combine(buf[0],buf[1]); + int datacount = combine(buf[2],buf[3]); + + if (DEBUG) + System.out.println(" Preliminary data msl="+msl_level+" count="+datacount); + + alt.setMslLevel(msl_level-6000); + alt.setDataSamples(datacount); + + if (DEBUG) + System.out.println(" Retrieving "+datacount+" samples"); + + long t = System.currentTimeMillis(); + + int count = 0; + while (count < datacount) { + sleep(); + write("G"); + sleep(); + buf = read(17); + + if (buf.length == 17) { + // Checksum = sum of all bytes + 1 + // (signedness does not change the result) + byte checksum = 1; + for (int i=0; i<16; i++) + checksum += buf[i]; + if (checksum != buf[16]) { + printBytes("ERROR: Checksum fail on data (computed="+checksum+ + " orig="+buf[16]+")",buf); + System.out.println("Ignoring error"); + } + } else { + System.err.println("ERROR: Only "+buf.length+" bytes read, should be 17"); + } + + for (int i=0; i 0) { + System.err.println("ERROR: Data available after transfer! (length="+buf.length+")"); + } + + + + + + + // Create an int[] array and set it + int[] d = new int[data.size()]; + for (int i=0; i"); + System.err.println("Files will be saved -old.log, -med and -new"); + return; + } + + + String device = null; + String[] devices = Alt15K.getNames(); + for (int i=0; i= 0) + return b; + else + return 256 + b; + } + + @SuppressWarnings("unused") + static private int combine(int a, int b) { + return 256*a + b; + } + + static private int combine(byte a, byte b) { + int val = 256*unsign(a)+unsign(b); + if (val <= 32767) + return val; + else + return val-65536; + + } + +} diff --git a/extra-src/altimeter/AltData.java b/extra-src/altimeter/AltData.java new file mode 100644 index 000000000..63314c7aa --- /dev/null +++ b/extra-src/altimeter/AltData.java @@ -0,0 +1,81 @@ +package altimeter; + +public class AltData { + + private int mslLevel = 0; + private int samples = 0; + private int[] data = null; + private byte[] version = null; + + + public void setMslLevel(int msl) { + mslLevel = msl; + } + public int getMslLevel() { + return mslLevel; + } + + public void setDataSamples(int s) { + samples = s; + } + public int getDataSamples() { + return samples; + } + + public void setVersion(byte[] v) { + if (v==null) + version = null; + else + version = v.clone(); + } + public byte[] getVersion() { + if (version == null) + return null; + return version.clone(); + } + + public void setData(int[] data) { + if (data==null) + this.data = null; + else + this.data = data.clone(); + } + public int[] getData() { + if (data == null) + return null; + return data.clone(); + } + + public int getApogee() { + if (data == null || data.length==0) + return 0; + int max = Integer.MIN_VALUE; + for (int i=0; i max) + max = data[i]; + } + return max; + } + + @Override + public String toString() { + String s = "AltData("; + s += "MSL:"+getMslLevel()+","; + s += "Apogee:"+getApogee()+","; + s += "Samples:"+getDataSamples(); + s += ")"; + return s; + } + + public void printData() { + System.out.println(toString()+":"); + for (int i=0; i + */ + +public class RotationLogger { + private static final boolean DEBUG = false; + + private static final int BYTES = 65536; + + + private final CommPortIdentifier portID; + private SerialPort port = null; + private InputStream is = null; + private OutputStream os = null; + + + + @SuppressWarnings("unchecked") + public static String[] getNames() { + ArrayList list = new ArrayList();; + + Enumeration pids = CommPortIdentifier.getPortIdentifiers(); + + while (pids.hasMoreElements()) { + CommPortIdentifier pid = (CommPortIdentifier) pids.nextElement(); + + if (pid.getPortType() == CommPortIdentifier.PORT_SERIAL) + list.add(pid.getName()); + } + return list.toArray(new String[0]); + } + + + + + + @SuppressWarnings("unchecked") + public RotationLogger(String name) throws IOException { + CommPortIdentifier portID = null; + + Enumeration portIdentifiers = CommPortIdentifier.getPortIdentifiers(); + while (portIdentifiers.hasMoreElements()) { + CommPortIdentifier pid = (CommPortIdentifier) portIdentifiers.nextElement(); + + if(pid.getPortType() == CommPortIdentifier.PORT_SERIAL && + pid.getName().equals(name)) { + portID = pid; + break; + } + } + + if (portID==null) { + throw new IOException("Port '"+name+"' not found."); + } + this.portID = portID; + } + + + + + + + public void readData() throws IOException, PortInUseException { + int c; + + int[] data = new int[BYTES]; + + FileOutputStream rawdump = null; + + + try { + open(); + + System.err.println("Sending dump mode command..."); + + for (int i=0; i<16; i++) { + os.write('D'); + try { + Thread.sleep(10); + } catch (InterruptedException ignore) { } + } + + System.err.println("Waiting for response..."); + while (true) { + c = is.read(); + if (c == 'K') { + break; + } else { + System.err.printf("Received spurious c=%d\n",c); + } + } + + System.err.println("Received response."); + + + + System.err.println("Opening 'rawdump'..."); + rawdump = new FileOutputStream("rawdump"); + + + + System.err.println("Performing dump..."); + + os.write('A'); + + byte[] buffer = new byte[1024]; + int printCount = 0; + for (int count=0; count < BYTES; ) { + if ((BYTES-count) < buffer.length) { + buffer = new byte[BYTES-count]; + } + + int n = is.read(buffer); + if (n < 0) { + System.err.println("Error condition, n="+n); + return; + } + + rawdump.write(buffer, 0, n); + + for (int i=0; i 1024) { + System.err.println("Read "+count+" bytes..."); + printCount = count; + } + } + + + System.err.println("Verifying checksum..."); + int reported = is.read(); + + byte computed = 0; + for (int i=0; i < data.length; i++) { + computed += data[i]; + } + if (computed == reported) { + System.err.println("Checksum ok ("+computed+")"); + } else { + System.err.println("Error in checksum, computed="+computed+ + " reported="+reported); + } + + System.err.println("Communication done."); + + } catch (UnsupportedCommOperationException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } finally { + close(); + if (rawdump != null) + rawdump.close(); + } + + convertData(data); + + } + + + + //////////// Data interpretation ////////////// + + + private static void convertData(int[] data) { + + System.err.println("Converting data..."); + + int lastBuffer = data[0xffff]; + if (lastBuffer < 0 || lastBuffer > 3) { + System.err.println("Illegal last accessed buffer: "+lastBuffer); + return; + } + System.err.println("Last used buffer: "+lastBuffer); + + for (int i=4; i>0; i--) { + int n = (lastBuffer + i) % 4; + int bufNumber = 4-i; + + convertBuffer(data, n * (BYTES/4), bufNumber); + } + + } + + + private static void convertBuffer(int[] data, int position, int bufNumber) { + int startPosition; + + startPosition = data[position + 0xfd] << 8 + data[position+0xfe]; + + // 50 samples per 128 bytes + int startTime = (startPosition -position) * 50 / 128; + + System.err.println(" Buffer "+ bufNumber + " (at position "+position+")..."); + System.err.println(" Start position "+startPosition+" time "+startTime); + + System.out.println("# Buffer "+bufNumber); + System.out.println("# Start position t="+startTime); + + + int t = 0; + for (int page = 0; page < 128; page++) { + int pageStart = position + page * 128; + + if (pageStart == startPosition) { + System.out.println("# ---clip---"); + } + + for (int i=0; i<125; i += 5) { + int sample1, sample2; + + int start = pageStart + i; +// System.err.println("page="+page+" i="+i+ +// " position="+position+" pageStart="+pageStart+" start="+start); + + sample1 = (data[start] << 2) + (data[start+1] >> 6); + sample2 = ((data[start+1] & 0x3f) << 4) + (data[start+2] >> 4); + System.out.printf("%d %4d %4d %4d\n", bufNumber, t, sample1, sample2); + t++; + + sample1 = ((data[start+2] & 0x0f) << 6) + (data[start+3] >> 2); + sample2 = ((data[start+3] & 3) << 8) + data[start+4]; + System.out.printf("%d %4d %4d %4d\n", bufNumber, t, sample1, sample2); + t++; + } + } + } + + + + private void open() throws PortInUseException, IOException, + UnsupportedCommOperationException { + + if (port != null) { + System.err.println("ERROR: open() called with port="+port); + Thread.dumpStack(); + close(); + } + + if (DEBUG) { + System.err.println(" Opening port..."); + } + + port = (SerialPort)portID.open("OpenRocket",1000); + + port.setSerialPortParams(9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, + SerialPort.PARITY_NONE); + + port.setInputBufferSize(1); + port.setOutputBufferSize(1); + + is = port.getInputStream(); + os = port.getOutputStream(); + } + + + private void close() { + if (DEBUG) + System.err.println(" Closing port"); + + SerialPort p = port; + port = null; + is = null; + if (p != null) + p.close(); + } + + + + private static int unsign(byte b) { + if (b >= 0) + return b; + else + return 256 + b; + } + + + + + public static void main(String[] arg) throws Exception { + + if (arg.length > 2) { + System.err.println("Illegal arguments."); + return; + } + if (arg.length == 1) { + FileInputStream is = new FileInputStream(arg[0]); + byte[] buffer = new byte[BYTES]; + int n = is.read(buffer); + if (n != BYTES) { + System.err.println("Could read only "+n+" bytes"); + return; + } + + int[] data = new int[BYTES]; + for (int i=0; i + */ + +public class SerialDownload { + private static final boolean DEBUG = false; + + private static final int MAGIC = 666; + + private final CommPortIdentifier portID; + private SerialPort port = null; + private InputStream is = null; + + + + @SuppressWarnings("unchecked") + public static String[] getNames() { + ArrayList list = new ArrayList();; + + Enumeration pids = CommPortIdentifier.getPortIdentifiers(); + + while (pids.hasMoreElements()) { + CommPortIdentifier pid = (CommPortIdentifier) pids.nextElement(); + + if (pid.getPortType() == CommPortIdentifier.PORT_SERIAL) + list.add(pid.getName()); + } + return list.toArray(new String[0]); + } + + + + + + @SuppressWarnings("unchecked") + public SerialDownload(String name) throws IOException { + CommPortIdentifier portID = null; + + Enumeration portIdentifiers = CommPortIdentifier.getPortIdentifiers(); + while (portIdentifiers.hasMoreElements()) { + CommPortIdentifier pid = (CommPortIdentifier) portIdentifiers.nextElement(); + + if(pid.getPortType() == CommPortIdentifier.PORT_SERIAL && + pid.getName().equals(name)) { + portID = pid; + break; + } + } + + if (portID==null) { + throw new IOException("Port '"+name+"' not found."); + } + this.portID = portID; + } + + + + + + + public void readData() throws IOException, PortInUseException { + long t0 = -1; + long t; + + int previous = MAGIC; + + + try { + open(); + + System.err.println("Ready to read..."); + while (true) { + int c = is.read(); + t = System.nanoTime(); + if (t0 < 0) + t0 = t; + + System.out.printf("%10.6f %d\n", ((double)t-t0)/1000000000.0, c); + + if (previous == MAGIC) { + previous = c; + } else { + System.out.printf("# Altitude: %5d\n", previous*256 + c); + previous = MAGIC; + } + + if (c < 0) + break; + } + + + } catch (UnsupportedCommOperationException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } finally { + close(); + } + } + + + + private void open() throws PortInUseException, IOException, + UnsupportedCommOperationException { + + if (port != null) { + System.err.println("ERROR: open() called with port="+port); + Thread.dumpStack(); + close(); + } + + if (DEBUG) { + System.err.println(" Opening port..."); + } + + port = (SerialPort)portID.open("OpenRocket",1000); + + port.setSerialPortParams(9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, + SerialPort.PARITY_NONE); + + port.setInputBufferSize(1); + port.setOutputBufferSize(1); + + is = port.getInputStream(); + } + + + private void close() { + if (DEBUG) + System.err.println(" Closing port"); + + SerialPort p = port; + port = null; + is = null; + if (p != null) + p.close(); + } + + + + + + public static void main(String[] arg) throws Exception { + + String device = null; + String[] devices = SerialDownload.getNames(); + for (int i=0; i + + + OpenRocket—Support and contact information + + + + + + + + +

Support and contact information for OpenRocket

+ + + +
+ +

Mailing lists

+ +

If you would like to be notified when new versions of + OpenRocket are released, you can join the + OpenRocket-announce mailing list. The + list is moderated and meant only for OpenRocket related + announcements.

+ +

When more developers join the project, a development mailing + list will be created as well.

+ +

Unsubscribing from the lists can be performed + in the above links as well. Please do not send unsubscription + requests to the list.

+ + +

Support forums

+ +

The main support channel for the usage of OpenRocket is the + support forums. This way everybody can benefit from the answers + provided.

+ +

Go + to the support forums →

+ + +

Contact information

+ +

OpenRocket is developed by Sampo Niskanen. His contact + information can be found below.

+ +

If you would like to contribute something to OpenRocket, please + contact me!

+ +

Support requests should be sent to + the support + forums.
+ Bug reports and feature requests should + be reported separately.

+ +

Email:    + sampo.niskanen@iki.fi

+ +

WWW:    + http://www.iki.fi/sampo.niskanen/

+ +
+ +
+

Valid XHTML 1.0! + Valid CSS! +

+
+ + + + diff --git a/html/documentation.html b/html/documentation.html new file mode 100644 index 000000000..a11e8cec1 --- /dev/null +++ b/html/documentation.html @@ -0,0 +1,180 @@ + + + + OpenRocket—Documentation + + + + + + + + +

Documentation for OpenRocket

+ + + +
+ +

User documentation

+ +

No user's guide currently exists for OpenRocket. There is a + page on the wiki for creating a User's guide.

+

If you would like to help, please extend it!

+ + +

Technical documentation

+ +

Coming within the next few weeks.

+ + +

Resources

+ +

Below are resources that have been found useful in the analysis + of model rockets. Many useful scientific aerodynamic articles and + documents are available at the invaluable + NASA Technical Resources Server + (NTRS).

+ +
+
+ The Theoretical Prediction of the Center of + Pressure, James and Judith Barrowman, 1966.
+
The original NARAM R&D report explaining how to + calculate the CP position of a rocket.
+ +
+ The Practical Calculation of the Aerodynamic + Characteristics of Slender Finned Vehicles, James + Barrowman, 1967.
+
The more in-depth and technical thesis, where Barrowman + presents methods for calculating the CP position of a rocket at + both subsonic and supersonic velocities and its other + aerodynamic properties. Available on + NTRS.
+ +
+ Wind instability—What Barrowman left out, + Robert Galejs.
+
An extension to the Barrowman method to account for body + lift at large angles of attack.
+ +
Topics in Advanced Model Rocketry, Mandell, + Caporaso, Bengen, MIT Press, 1973.
+
An excellent theoretical study on the flight of model + rockets. Available as a reprint edition.
+ +
Fluid-dynamic drag, Sighard Hoerner, + published by the author, 1965.
+
An excellent resource for all kinds of experimental data + regarding drag. Available as a reprint edition.
+ +
Tactical missile design, 2nd edition, Eugene + L. Fleeman, AIAA, 2006.
+
Useful approximation methods for estimating the aerodynamic + properties of rockets.
+ +
Applied + Computational Aerodynamics, William Mason.
+
An online textbook on computational aerodynamics.
+ +
Design of aerodynamically stabilized free rockets, + MIL-HDBK-762, US Army Missile Command, 1990.
+
Military handbook on the design of rockets, a good resource + for aerodynamic estimation methods.
+ +
+ ThrustCurve.org, + John Coker.
+
An excellent resource for model rocket motor thrust curves.
+ +
+ Static stability investigation of a single-stage + sounding rocket at Mach numbers from 0.60 to 1.20, James + Ferris, NASA-TN-D-4013, 1967.
+
+ Static stability investigation of a sounding-rocket + vehicle at Mach numbers from 1.50 to 4.63, Donald Babb and + Dennis Fuller, NASA-TN-D-4014, 1967.
+
Experimental data of a wind tunnel investigation of a + sounding rocket at subsonic, transonic and supersonic + velocities. Available on + NTRS.
+ + + +
+ + +
+ +
+

Valid XHTML 1.0! + Valid CSS! +

+
+ + + + diff --git a/html/download.html b/html/download.html new file mode 100644 index 000000000..0795dc8a8 --- /dev/null +++ b/html/download.html @@ -0,0 +1,95 @@ + + + + OpenRocket—Download + + + + + + + + +

Download OpenRocket

+ + + +
+ +

Binary download

+ +

The binary download below is the recommended package for general + use. It is pre-packaged with motor thrust curves from + thrustcurve.org.

+ +

OpenRocket requires Java version 6 or + later. The Sun JRE is recommended.

+ +

+ Download OpenRocket 0.9.0

+ +

OpenRocket is still considered beta software. + If you encounter any problems, please + report them so they can be fixed!

+ +

OpenRocket can be started in graphical environments (such as + Windows) by double-clicking the package icon. No installation is + required. From the command line it can be started by

+
$ java -jar OpenRocket-0.9.0.jar
+ +

Older packages are available from the + SourceForge repository.

+ + +

Source code

+ +

The source code for OpenRocket is available from the + SourceForge SVN repository. + It can be retrieved simply using the command

+
$ svn co https://openrocket.svn.sourceforge.net/svnroot/openrocket openrocket
+

The above URL may be used to connect to the repository with + other Subversion clients as well.

+ + + +
+ +
+

Valid XHTML 1.0! + Valid CSS! +

+
+ + + + diff --git a/html/features.html b/html/features.html new file mode 100644 index 000000000..19f48a9e0 --- /dev/null +++ b/html/features.html @@ -0,0 +1,144 @@ + + + + OpenRocket—Features + + + + + + + + +

Features of OpenRocket

+ + + +
+ +

Current features

+ +

General

+ +
    +
  • Fully cross-platform, written in Java
  • +
  • Fully documented simulation + methods
  • +
  • Open Source, source code available under the + GNU GPL
  • +
+ +

User interface

+
    +
  • Easy-to-use user interface for + rocket design
  • +
  • Zoomable schematic view of rocket from the side or rear
  • +
  • Rocket rotation around center axis
  • +
  • Real-time view of CG and CP position
  • +
  • Real-time flight altitude, velocity and + acceleration information from a continuous simulation + performed in the background
  • +
+ +

Design

+ +
    +
  • A multitude of available components to + choose from
  • +
  • Trapezoidal, elliptical + and free-form fins supported
  • +
  • Support for canted fins (roll + stabilization)
  • +
  • Staging and clustering support
  • +
  • Automatic calculation of component mass and CG based on + shape and density
  • +
  • Ability to override mass and CG of + components or stages separately
  • +
+ +

Simulation and analysis

+ +
    +
  • Full six degree of freedom simulation
  • +
  • Rocket stability computed using extended Barrowman + method
  • +
  • Realistic wind modeling
  • +
  • Analysis of the effect of separate + components on the stability, drag and roll + characteristics of the rocket
  • +
  • Fully configurable plotting, with + various preset configurations
  • +
  • Simulation listeners allowing custom-made + code to interact with the rocket during flight simulation
  • +
+ + +

Planned future features

+ +

OpenRocket is under constant work, and anybody can help make + OpenRocket an even better simulator! Here are a few features that + have been planned...

+ +
    +
  • Aerodynamic computation using + CFD + (help needed!)
  • +
  • Better support for supersonic simulation + (help needed!)
  • +
  • 3D view of the rocket design + (help needed!)
  • +
  • Saving figures and exporting simulation data
  • +
  • Importing and plotting actual flight data from altimeters
  • +
  • Importing new motor thrust curves
  • +
  • Support for ready-made component databases
  • +
  • Customized support for hybrid rocket motors and water + rockets
  • +
  • Rocket flight animation
  • +
  • A "wizard" for creating new rocket designs
  • +
  • . . .
  • +
+ +

If you want to help make OpenRocket the best rocket simulator, + don't hesitate to contact us!

+ +
+ +
+

Valid XHTML 1.0! + Valid CSS! +

+
+ + + + diff --git a/html/index.html b/html/index.html new file mode 100644 index 000000000..996320457 --- /dev/null +++ b/html/index.html @@ -0,0 +1,106 @@ + + + + OpenRocket + + + + + + + + +

OpenRocket — an Open Source model rocket simulator

+ + + +
+ +

Introduction

+ +

OpenRocket is a Free, fully featured model + rocket simulator written in Java. It can be used to design and + simulate rockets before actually building and flying them. +

+

OpenRocket features a full six-degree-of-freedom simulation, + realistic wind modeling, a multitude of different components + including free-form fins and canted fins, clustering and + staging. Read more about its features. +

+

Best of all, OpenRocket is Open Source—its source code is + freely available to study and extend. Anybody wishing to + contribute to the project can do so according to the + GNU GPL. Simply + download the source code + and start hacking, or get the ready + package to begin designing and simulating. +

+

OpenRocket is still considered to be beta + software—there will still be bugs and occasional + problems. If you encounter problems, please + report them so they can be fixed. +

+ + +
+ + +

News

+ +

24.5.2009: First version 0.9.0 + released!

+ +
+ +
+

Valid XHTML 1.0! + Valid CSS! +

+
+ + + + diff --git a/html/layout.css b/html/layout.css new file mode 100644 index 000000000..d1e3de194 --- /dev/null +++ b/html/layout.css @@ -0,0 +1,230 @@ + +body { + margin: 0; + padding: 0; +} + +#iewarn { + width: 100%; + background-color: #fa0; + text-align: center; + padding: 1em 2em; + border-top: solid 1px black; + border-bottom: solid 1px black; +} + + +h1 { + margin: 0.75em 2em 1.25em 2em; +} + +h2 { + margin-top: 1.5em; + border-bottom: dotted 2px #f99; +} + +a { + text-decoration: none; + color: #00F; +} +a:hover { + color: #55c; +} + + +div.menucontainer { + position: relative; +} + +div.menu { + position: absolute; + left: 1.5em; + width: 12em; + margin: 0; + padding: 0 0; + background-color: #ccc; +} +div.menu ul { + position: relative; + left: -2px; + top: -2px; + right: 2px; + bottom: 2px; + background-color: #89cbe0; + border: solid 1px black; + list-style: none; + margin: 0; + padding: 0 0; +} + +div.menu li { + display: block; + left: 0; + right: 0; + margin: 0; + text-align: center; +} + +div.menu li:first-child { + padding: 0.5em 0; + font-size: 160%; +} + +div.menu li+li { + border-top: dashed 1px black; +} + +div.menu li a { + display: block; + left: 0; + right: 0; + font-style: normal; + text-decoration: none; + color: #00d; + padding: 0.75em 1em; + outline: none; +} +div.menu li a:focus { + background-color: #8fd5eb; +} + +div.menu li a:hover { + background-color: #ee9494; +} + +div.menu div.logo { + position: absolute; + top: 100%; + left: -2px; + margin-top: 15px; + width: 100%; +} + +div.menu div.logo img { + display: block; + margin: 0 auto; +} + + +.page_index div.menu a[href="index.html"], +.page_features div.menu a[href="features.html"], +.page_screenshots div.menu a[href="screenshots.html"], +.page_download div.menu a[href="download.html"], +.page_documentation div.menu a[href="documentation.html"], +.page_contact div.menu a[href="contact.html"], +.page_report div.menu a[href="report.html"], +.page_license div.menu a[href="license.html"] { + font-weight: bold; + font-size: 110%; +} + + +.content { + margin: 0em 2em 2em 15.5em; + min-height: 27em; +} + +img { + border: 0px; + outline: none; + font-size: 70%; +} + +.smallshot { + float: left; + margin-top:2em; + text-align: center; + font-style: italic; + margin-right: 2em; +} +.smallshot.last { + margin-right: 0; +} +.clear { + clear:both; +} + + +.smallshotconst { + float: left; + width: 270px; + height: 220px; + margin: 1em 1em; + text-align: center; + font-style: italic; +} +.smallshotconst em { + font-style: normal; +} + + + +a.help { + margin-left: 1em; + font-size: smaller; + font-style: italic; +} + + +pre.quote { + margin: 2em; + padding: 1em; + border: dashed 1px #888; + background-color: #ddd; +} + +p.quote { + margin: 2em; +} + +hr { + margin: 2em 0em; +} + +.right { + float: right; + margin: 0; +} + +li { + margin-top: 0.5em; +} + + +p.download { + margin: 2em; +} +p.download a { + font-size: 140%; + font-style: italic; + padding: 0.5em; + border: dashed 1px red; + background-color: #89cbe0; + outline: none; +} +p.download a:hover { + color: #00F; + background-color: #ee9494; +} +p.download a:focus { + background-color: #8fd5eb; +} + +div.valid { + float: right; + margin-right: 2em; +} + + + +ol.toc { + list-style-type: none; +} + +dt+dt { + margin-top: 0.5em; +} +dd { + margin-top: 0.2em; + margin-bottom: 1.4em; +} diff --git a/html/license.html b/html/license.html new file mode 100644 index 000000000..a7cc99972 --- /dev/null +++ b/html/license.html @@ -0,0 +1,769 @@ + + + + OpenRocket—License + + + + + + + + +

OpenRocket license

+ + + +
+ +

The license text is available also in the simulator under + Help → License and in the file + LICENSE.TXT.

+ +
+ +
+OpenRocket - A model rocket simulator
+
+Copyright (C) 2007-2009 Sampo Niskanen
+
+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 (below) for more details.
+
+
+Additional permission under GNU GPL version 3 section 7:
+
+The licensors grant additional permission to package this Program, or
+any covered work, along with any non-compilable data files (such as
+thrust curves or component databases) and convey the resulting work.
+
+
+------------------------------------------------------------------------
+
+
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ 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.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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 <http://www.gnu.org/licenses/>.
+
+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:
+
+    <program>  Copyright (C) <year>  <name of author>
+    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
+<http://www.gnu.org/licenses/>.
+
+  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
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
+    
+ +
+ +
+ +
+

Valid XHTML 1.0! + Valid CSS! +

+
+ + + + diff --git a/html/report.html b/html/report.html new file mode 100644 index 000000000..9ef2250a0 --- /dev/null +++ b/html/report.html @@ -0,0 +1,116 @@ + + + + OpenRocket—Support and contact information + + + + + + + + +

Support and contact information for OpenRocket

+ + + +
+ +

Bug reports

+ +

If you encounter problems with OpenRocket, please report them + so they can be fixed in future versions. Please follow the + instructions below to report a bug:

+ +
    +
  • Search the bug repository to see if the bug has already been + reported. If it is, please add extra information to that bug + report:
    +
    + + + + + + + + + + +
    +
  • + +
  • Report the bug using the + bug + tracker. Follow the instructions provided to fill in the + report.
  • + +
  • If you are unsure about some issue, you can discuss it in + the appropriate + support + forum.
  • +
+ +

Feature requests

+ +

Good ideas on how to make OpenRocket better are always welcome! + The features will be implemented as there is time. However, no + promises are made of when or whether some feature will be + provided.

+ +

If you would like to implement some feature yourself, patches + are sincerely welcome. Please contact + me in order to coordinate our efforts.

+ +

When requesting a feature:

+ + + +
+ +
+

Valid XHTML 1.0! + Valid CSS! +

+
+ + + + diff --git a/html/screenshots.html b/html/screenshots.html new file mode 100644 index 000000000..f7e26bc09 --- /dev/null +++ b/html/screenshots.html @@ -0,0 +1,95 @@ + + + + OpenRocket—Screenshots + + + + + + + + +

Screenshots of OpenRocket

+ + + + + +
+

Valid XHTML 1.0! + Valid CSS! +

+
+ + + + diff --git a/html/shots-small/dialog-analysis.jpg b/html/shots-small/dialog-analysis.jpg new file mode 100644 index 000000000..570c4846f Binary files /dev/null and b/html/shots-small/dialog-analysis.jpg differ diff --git a/html/shots-small/dialog-edit.jpg b/html/shots-small/dialog-edit.jpg new file mode 100644 index 000000000..c2dc593dc Binary files /dev/null and b/html/shots-small/dialog-edit.jpg differ diff --git a/html/shots-small/dialog-plot-options.jpg b/html/shots-small/dialog-plot-options.jpg new file mode 100644 index 000000000..ad977ab9b Binary files /dev/null and b/html/shots-small/dialog-plot-options.jpg differ diff --git a/html/shots-small/dialog-plot.jpg b/html/shots-small/dialog-plot.jpg new file mode 100644 index 000000000..d0fc66dbf Binary files /dev/null and b/html/shots-small/dialog-plot.jpg differ diff --git a/html/shots-small/main.jpg b/html/shots-small/main.jpg new file mode 100644 index 000000000..7fcd9464c Binary files /dev/null and b/html/shots-small/main.jpg differ diff --git a/html/shots/dialog-analysis.png b/html/shots/dialog-analysis.png new file mode 100644 index 000000000..b71f1aa8d Binary files /dev/null and b/html/shots/dialog-analysis.png differ diff --git a/html/shots/dialog-edit.png b/html/shots/dialog-edit.png new file mode 100644 index 000000000..9107859e4 Binary files /dev/null and b/html/shots/dialog-edit.png differ diff --git a/html/shots/dialog-plot-options.png b/html/shots/dialog-plot-options.png new file mode 100644 index 000000000..ca627fa3a Binary files /dev/null and b/html/shots/dialog-plot-options.png differ diff --git a/html/shots/dialog-plot.png b/html/shots/dialog-plot.png new file mode 100644 index 000000000..225dca813 Binary files /dev/null and b/html/shots/dialog-plot.png differ diff --git a/html/shots/main.png b/html/shots/main.png new file mode 100644 index 000000000..2b9af491e Binary files /dev/null and b/html/shots/main.png differ diff --git a/html/valid-xhtml10.png b/html/valid-xhtml10.png new file mode 100644 index 000000000..b81de9160 Binary files /dev/null and b/html/valid-xhtml10.png differ diff --git a/html/vcss.gif b/html/vcss.gif new file mode 100644 index 000000000..020c75a73 Binary files /dev/null and b/html/vcss.gif differ diff --git a/lib/jcommon-1.0.16.jar b/lib/jcommon-1.0.16.jar new file mode 100644 index 000000000..4cd680744 Binary files /dev/null and b/lib/jcommon-1.0.16.jar differ diff --git a/lib/jfreechart-1.0.13.jar b/lib/jfreechart-1.0.13.jar new file mode 100644 index 000000000..83c699318 Binary files /dev/null and b/lib/jfreechart-1.0.13.jar differ diff --git a/lib/miglayout15-swing.jar b/lib/miglayout15-swing.jar new file mode 100644 index 000000000..2e5301255 Binary files /dev/null and b/lib/miglayout15-swing.jar differ diff --git a/pix-src/componenticons/bodyoutline.xcf.gz b/pix-src/componenticons/bodyoutline.xcf.gz new file mode 100644 index 000000000..1a1f24514 Binary files /dev/null and b/pix-src/componenticons/bodyoutline.xcf.gz differ diff --git a/pix-src/componenticons/bodytube.xcf.gz b/pix-src/componenticons/bodytube.xcf.gz new file mode 100644 index 000000000..94c239592 Binary files /dev/null and b/pix-src/componenticons/bodytube.xcf.gz differ diff --git a/pix-src/componenticons/bulkhead.xcf.gz b/pix-src/componenticons/bulkhead.xcf.gz new file mode 100644 index 000000000..a188588df Binary files /dev/null and b/pix-src/componenticons/bulkhead.xcf.gz differ diff --git a/pix-src/componenticons/centeringring.xcf.gz b/pix-src/componenticons/centeringring.xcf.gz new file mode 100644 index 000000000..7cab6f4e2 Binary files /dev/null and b/pix-src/componenticons/centeringring.xcf.gz differ diff --git a/pix-src/componenticons/ellipticalfin.xcf.gz b/pix-src/componenticons/ellipticalfin.xcf.gz new file mode 100644 index 000000000..474af0547 Binary files /dev/null and b/pix-src/componenticons/ellipticalfin.xcf.gz differ diff --git a/pix-src/componenticons/engineblock.xcf.gz b/pix-src/componenticons/engineblock.xcf.gz new file mode 100644 index 000000000..03a018c19 Binary files /dev/null and b/pix-src/componenticons/engineblock.xcf.gz differ diff --git a/pix-src/componenticons/freeformfin.xcf.gz b/pix-src/componenticons/freeformfin.xcf.gz new file mode 100644 index 000000000..25cc27782 Binary files /dev/null and b/pix-src/componenticons/freeformfin.xcf.gz differ diff --git a/pix-src/componenticons/innertube.xcf.gz b/pix-src/componenticons/innertube.xcf.gz new file mode 100644 index 000000000..7cab6f4e2 Binary files /dev/null and b/pix-src/componenticons/innertube.xcf.gz differ diff --git a/pix-src/componenticons/launchlug.xcf.gz b/pix-src/componenticons/launchlug.xcf.gz new file mode 100644 index 000000000..7c1934905 Binary files /dev/null and b/pix-src/componenticons/launchlug.xcf.gz differ diff --git a/pix-src/componenticons/mass.xcf.gz b/pix-src/componenticons/mass.xcf.gz new file mode 100644 index 000000000..4199344eb Binary files /dev/null and b/pix-src/componenticons/mass.xcf.gz differ diff --git a/pix-src/componenticons/nosecone.xcf.gz b/pix-src/componenticons/nosecone.xcf.gz new file mode 100644 index 000000000..b7b7cd5e5 Binary files /dev/null and b/pix-src/componenticons/nosecone.xcf.gz differ diff --git a/pix-src/componenticons/parachute.xcf.gz b/pix-src/componenticons/parachute.xcf.gz new file mode 100644 index 000000000..ebc3ffbef Binary files /dev/null and b/pix-src/componenticons/parachute.xcf.gz differ diff --git a/pix-src/componenticons/shockcord.xcf.gz b/pix-src/componenticons/shockcord.xcf.gz new file mode 100644 index 000000000..f1bcfec7f Binary files /dev/null and b/pix-src/componenticons/shockcord.xcf.gz differ diff --git a/pix-src/componenticons/siiveke.fig b/pix-src/componenticons/siiveke.fig new file mode 100644 index 000000000..b7d000653 --- /dev/null +++ b/pix-src/componenticons/siiveke.fig @@ -0,0 +1,71 @@ +#FIG 3.2 Produced by xfig version 3.2.5 +Landscape +Center +Metric +A4 +100.00 +Single +-2 +1200 2 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 4 + 2520 4050 3780 2970 5580 2970 5130 4050 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 0 0 1.00 60.00 60.00 + 4185 2790 3780 2790 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 0 0 1.00 60.00 60.00 + 5220 2790 5580 2790 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 1 2 + 0 0 1.00 60.00 60.00 + 0 0 1.00 60.00 60.00 + 5715 2970 5715 4050 +2 1 1 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 + 2520 2880 2520 3870 +2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 1 0 2 + 0 0 1.00 60.00 60.00 + 3465 2790 3780 2790 +2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 1 0 2 + 0 0 1.00 60.00 60.00 + 2745 2790 2520 2790 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 1665 4050 6705 4050 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 0 0 1.00 60.00 60.00 + 3195 4230 2520 4230 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 0 0 1.00 60.00 60.00 + 4500 4230 5130 4230 +2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 + 3780 2745 3780 2835 +2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 + 2025 4050 1800 4275 +2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 + 2250 4050 2025 4275 +2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 + 2475 4050 2250 4275 +2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 + 2520 2745 2520 2835 +2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 + 5580 2745 5580 2835 +2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 + 5130 4185 5130 4275 +2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 + 2520 4185 2520 4275 +2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 + 5670 2970 5760 2970 +2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 + 5670 4050 5445 4275 +2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 + 5445 4050 5220 4275 +2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 + 5895 4050 5670 4275 +2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 + 6120 4050 5895 4275 +2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 + 6345 4050 6120 4275 +2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 + 6570 4050 6345 4275 +4 0 0 50 -1 0 11 0.0000 4 135 960 4185 2835 TIP CHORD\001 +4 0 0 50 -1 0 11 0.0000 4 135 630 2790 2835 SWEEP\001 +4 0 0 50 -1 0 11 0.0000 4 135 1185 3240 4275 ROOT CHORD\001 +4 0 0 50 -1 0 11 0.0000 4 135 690 5805 3555 HEIGHT\001 diff --git a/pix-src/componenticons/siiveke.svg b/pix-src/componenticons/siiveke.svg new file mode 100644 index 000000000..c2ca80b64 --- /dev/null +++ b/pix-src/componenticons/siiveke.svg @@ -0,0 +1,218 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +TIP CHORD + +SWEEP + +ROOT CHORD + +HEIGHT + + diff --git a/pix-src/componenticons/streamer.xcf.gz b/pix-src/componenticons/streamer.xcf.gz new file mode 100644 index 000000000..e3dd8cc04 Binary files /dev/null and b/pix-src/componenticons/streamer.xcf.gz differ diff --git a/pix-src/componenticons/transition.xcf.gz b/pix-src/componenticons/transition.xcf.gz new file mode 100644 index 000000000..e0bb80ef6 Binary files /dev/null and b/pix-src/componenticons/transition.xcf.gz differ diff --git a/pix-src/componenticons/trapezoidfin.xcf.gz b/pix-src/componenticons/trapezoidfin.xcf.gz new file mode 100644 index 000000000..e146920be Binary files /dev/null and b/pix-src/componenticons/trapezoidfin.xcf.gz differ diff --git a/pix-src/componenticons/tubecoupler.xcf.gz b/pix-src/componenticons/tubecoupler.xcf.gz new file mode 100644 index 000000000..f6d8f5886 Binary files /dev/null and b/pix-src/componenticons/tubecoupler.xcf.gz differ diff --git a/pix-src/spheres/blue-16x16.png b/pix-src/spheres/blue-16x16.png new file mode 100644 index 000000000..1f330f024 Binary files /dev/null and b/pix-src/spheres/blue-16x16.png differ diff --git a/pix-src/spheres/blue-cyan-large.png b/pix-src/spheres/blue-cyan-large.png new file mode 100644 index 000000000..129b7bd54 Binary files /dev/null and b/pix-src/spheres/blue-cyan-large.png differ diff --git a/pix-src/spheres/copyright.txt b/pix-src/spheres/copyright.txt new file mode 100644 index 000000000..366864b69 --- /dev/null +++ b/pix-src/spheres/copyright.txt @@ -0,0 +1 @@ +Originally from http://flyosity.com/tutorial/how-to-draw-a-mac-internet-globe-icon.php diff --git a/pix-src/spheres/gray-16x16.png b/pix-src/spheres/gray-16x16.png new file mode 100644 index 000000000..d0951a91a Binary files /dev/null and b/pix-src/spheres/gray-16x16.png differ diff --git a/pix-src/spheres/gray-large.xcf.gz b/pix-src/spheres/gray-large.xcf.gz new file mode 100644 index 000000000..e33d3c058 Binary files /dev/null and b/pix-src/spheres/gray-large.xcf.gz differ diff --git a/pix-src/spheres/green-16x16.png b/pix-src/spheres/green-16x16.png new file mode 100644 index 000000000..29d785dd6 Binary files /dev/null and b/pix-src/spheres/green-16x16.png differ diff --git a/pix-src/spheres/green-large.xcf.gz b/pix-src/spheres/green-large.xcf.gz new file mode 100644 index 000000000..c91a929e4 Binary files /dev/null and b/pix-src/spheres/green-large.xcf.gz differ diff --git a/pix-src/spheres/red-16x16.png b/pix-src/spheres/red-16x16.png new file mode 100644 index 000000000..79c71612e Binary files /dev/null and b/pix-src/spheres/red-16x16.png differ diff --git a/pix-src/spheres/red-large.xcf.gz b/pix-src/spheres/red-large.xcf.gz new file mode 100644 index 000000000..5e3f4a36a Binary files /dev/null and b/pix-src/spheres/red-large.xcf.gz differ diff --git a/pix-src/spheres/step4c.png b/pix-src/spheres/step4c.png new file mode 100644 index 000000000..129b7bd54 Binary files /dev/null and b/pix-src/spheres/step4c.png differ diff --git a/pix-src/spheres/yellow-16x16.png b/pix-src/spheres/yellow-16x16.png new file mode 100644 index 000000000..9ed11ea16 Binary files /dev/null and b/pix-src/spheres/yellow-16x16.png differ diff --git a/pix-src/spheres/yellow-large.xcf.gz b/pix-src/spheres/yellow-large.xcf.gz new file mode 100644 index 000000000..54d917f16 Binary files /dev/null and b/pix-src/spheres/yellow-large.xcf.gz differ diff --git a/pix-src/splashscreen.xcf.gz b/pix-src/splashscreen.xcf.gz new file mode 100644 index 000000000..a38f0f275 Binary files /dev/null and b/pix-src/splashscreen.xcf.gz differ diff --git a/pix/componenticons/bodytube-large.png b/pix/componenticons/bodytube-large.png new file mode 100644 index 000000000..517af76a9 Binary files /dev/null and b/pix/componenticons/bodytube-large.png differ diff --git a/pix/componenticons/bodytube-small.png b/pix/componenticons/bodytube-small.png new file mode 100644 index 000000000..450d1d28c Binary files /dev/null and b/pix/componenticons/bodytube-small.png differ diff --git a/pix/componenticons/bulkhead-large.png b/pix/componenticons/bulkhead-large.png new file mode 100644 index 000000000..1aeb8bcd9 Binary files /dev/null and b/pix/componenticons/bulkhead-large.png differ diff --git a/pix/componenticons/bulkhead-small.png b/pix/componenticons/bulkhead-small.png new file mode 100644 index 000000000..78bbdb2b4 Binary files /dev/null and b/pix/componenticons/bulkhead-small.png differ diff --git a/pix/componenticons/centeringring-large.png b/pix/componenticons/centeringring-large.png new file mode 100644 index 000000000..6505bc2e8 Binary files /dev/null and b/pix/componenticons/centeringring-large.png differ diff --git a/pix/componenticons/centeringring-small.png b/pix/componenticons/centeringring-small.png new file mode 100644 index 000000000..5bd9b79b6 Binary files /dev/null and b/pix/componenticons/centeringring-small.png differ diff --git a/pix/componenticons/ellipticalfin-large.png b/pix/componenticons/ellipticalfin-large.png new file mode 100644 index 000000000..cb186f3eb Binary files /dev/null and b/pix/componenticons/ellipticalfin-large.png differ diff --git a/pix/componenticons/ellipticalfin-small.png b/pix/componenticons/ellipticalfin-small.png new file mode 100644 index 000000000..1abd29e4c Binary files /dev/null and b/pix/componenticons/ellipticalfin-small.png differ diff --git a/pix/componenticons/engineblock-large.png b/pix/componenticons/engineblock-large.png new file mode 100644 index 000000000..2fc7549bb Binary files /dev/null and b/pix/componenticons/engineblock-large.png differ diff --git a/pix/componenticons/engineblock-small.png b/pix/componenticons/engineblock-small.png new file mode 100644 index 000000000..fbda45e91 Binary files /dev/null and b/pix/componenticons/engineblock-small.png differ diff --git a/pix/componenticons/freeformfin-large.png b/pix/componenticons/freeformfin-large.png new file mode 100644 index 000000000..a1f1e694d Binary files /dev/null and b/pix/componenticons/freeformfin-large.png differ diff --git a/pix/componenticons/freeformfin-small.png b/pix/componenticons/freeformfin-small.png new file mode 100644 index 000000000..e9e9bc8dc Binary files /dev/null and b/pix/componenticons/freeformfin-small.png differ diff --git a/pix/componenticons/innertube-large.png b/pix/componenticons/innertube-large.png new file mode 100644 index 000000000..4dbb3d19b Binary files /dev/null and b/pix/componenticons/innertube-large.png differ diff --git a/pix/componenticons/innertube-small.png b/pix/componenticons/innertube-small.png new file mode 100644 index 000000000..c6c448a90 Binary files /dev/null and b/pix/componenticons/innertube-small.png differ diff --git a/pix/componenticons/launchlug-large.png b/pix/componenticons/launchlug-large.png new file mode 100644 index 000000000..cc8aa7edc Binary files /dev/null and b/pix/componenticons/launchlug-large.png differ diff --git a/pix/componenticons/launchlug-small.png b/pix/componenticons/launchlug-small.png new file mode 100644 index 000000000..6134fd0ad Binary files /dev/null and b/pix/componenticons/launchlug-small.png differ diff --git a/pix/componenticons/mass-large.png b/pix/componenticons/mass-large.png new file mode 100644 index 000000000..bf14a1ce5 Binary files /dev/null and b/pix/componenticons/mass-large.png differ diff --git a/pix/componenticons/mass-small.png b/pix/componenticons/mass-small.png new file mode 100644 index 000000000..fa5ba8eec Binary files /dev/null and b/pix/componenticons/mass-small.png differ diff --git a/pix/componenticons/nosecone-large.png b/pix/componenticons/nosecone-large.png new file mode 100644 index 000000000..91c36442b Binary files /dev/null and b/pix/componenticons/nosecone-large.png differ diff --git a/pix/componenticons/nosecone-small.png b/pix/componenticons/nosecone-small.png new file mode 100644 index 000000000..f920c73d9 Binary files /dev/null and b/pix/componenticons/nosecone-small.png differ diff --git a/pix/componenticons/parachute-large.png b/pix/componenticons/parachute-large.png new file mode 100644 index 000000000..0828c4573 Binary files /dev/null and b/pix/componenticons/parachute-large.png differ diff --git a/pix/componenticons/parachute-small.png b/pix/componenticons/parachute-small.png new file mode 100644 index 000000000..eb4dea3ff Binary files /dev/null and b/pix/componenticons/parachute-small.png differ diff --git a/pix/componenticons/shockcord-large.png b/pix/componenticons/shockcord-large.png new file mode 100644 index 000000000..8a7586bcd Binary files /dev/null and b/pix/componenticons/shockcord-large.png differ diff --git a/pix/componenticons/shockcord-small.png b/pix/componenticons/shockcord-small.png new file mode 100644 index 000000000..13ff97e37 Binary files /dev/null and b/pix/componenticons/shockcord-small.png differ diff --git a/pix/componenticons/streamer-large.png b/pix/componenticons/streamer-large.png new file mode 100644 index 000000000..57a42d19f Binary files /dev/null and b/pix/componenticons/streamer-large.png differ diff --git a/pix/componenticons/streamer-small.png b/pix/componenticons/streamer-small.png new file mode 100644 index 000000000..317e46996 Binary files /dev/null and b/pix/componenticons/streamer-small.png differ diff --git a/pix/componenticons/transition-large.png b/pix/componenticons/transition-large.png new file mode 100644 index 000000000..7e5cbd052 Binary files /dev/null and b/pix/componenticons/transition-large.png differ diff --git a/pix/componenticons/transition-small.png b/pix/componenticons/transition-small.png new file mode 100644 index 000000000..da25735dc Binary files /dev/null and b/pix/componenticons/transition-small.png differ diff --git a/pix/componenticons/trapezoidfin-large.png b/pix/componenticons/trapezoidfin-large.png new file mode 100644 index 000000000..ee36f3103 Binary files /dev/null and b/pix/componenticons/trapezoidfin-large.png differ diff --git a/pix/componenticons/trapezoidfin-small.png b/pix/componenticons/trapezoidfin-small.png new file mode 100644 index 000000000..db3c28872 Binary files /dev/null and b/pix/componenticons/trapezoidfin-small.png differ diff --git a/pix/componenticons/tubecoupler-large.png b/pix/componenticons/tubecoupler-large.png new file mode 100644 index 000000000..ea927a68d Binary files /dev/null and b/pix/componenticons/tubecoupler-large.png differ diff --git a/pix/componenticons/tubecoupler-small.png b/pix/componenticons/tubecoupler-small.png new file mode 100644 index 000000000..7e08d674a Binary files /dev/null and b/pix/componenticons/tubecoupler-small.png differ diff --git a/pix/icons/application-exit.png b/pix/icons/application-exit.png new file mode 100644 index 000000000..63232417a Binary files /dev/null and b/pix/icons/application-exit.png differ diff --git a/pix/icons/copyright.txt b/pix/icons/copyright.txt new file mode 100644 index 000000000..aa0f8c750 --- /dev/null +++ b/pix/icons/copyright.txt @@ -0,0 +1,29 @@ + +Copyright of the icons: +----------------------- + + +From the "Nuvola" package by David Vignoni: +http://www.icon-king.com/projects/nuvola/ + +application-exit.png +document-close.png +document-new.png +document-open.png +document-save-as.png +document-save.png +edit-copy.png +edit-cut.png +edit-delete.png +edit-paste.png +edit-redo.png +edit-undo.png + + +From the "Crystal" project: +http://www.everaldo.com/crystal/ + +preferences.png +zoom-in.png +zoom-out.png + diff --git a/pix/icons/document-close.png b/pix/icons/document-close.png new file mode 100644 index 000000000..3bf502940 Binary files /dev/null and b/pix/icons/document-close.png differ diff --git a/pix/icons/document-new.png b/pix/icons/document-new.png new file mode 100644 index 000000000..f38d02ee5 Binary files /dev/null and b/pix/icons/document-new.png differ diff --git a/pix/icons/document-open.png b/pix/icons/document-open.png new file mode 100644 index 000000000..2d8e3ba2b Binary files /dev/null and b/pix/icons/document-open.png differ diff --git a/pix/icons/document-save-as.png b/pix/icons/document-save-as.png new file mode 100644 index 000000000..71602bc36 Binary files /dev/null and b/pix/icons/document-save-as.png differ diff --git a/pix/icons/document-save.png b/pix/icons/document-save.png new file mode 100644 index 000000000..fd0048ded Binary files /dev/null and b/pix/icons/document-save.png differ diff --git a/pix/icons/edit-copy.png b/pix/icons/edit-copy.png new file mode 100644 index 000000000..b7c938a99 Binary files /dev/null and b/pix/icons/edit-copy.png differ diff --git a/pix/icons/edit-cut.png b/pix/icons/edit-cut.png new file mode 100644 index 000000000..49f359147 Binary files /dev/null and b/pix/icons/edit-cut.png differ diff --git a/pix/icons/edit-delete.png b/pix/icons/edit-delete.png new file mode 100644 index 000000000..d33c34454 Binary files /dev/null and b/pix/icons/edit-delete.png differ diff --git a/pix/icons/edit-paste.png b/pix/icons/edit-paste.png new file mode 100644 index 000000000..4c43ddfd7 Binary files /dev/null and b/pix/icons/edit-paste.png differ diff --git a/pix/icons/edit-redo.png b/pix/icons/edit-redo.png new file mode 100644 index 000000000..f1e45cff9 Binary files /dev/null and b/pix/icons/edit-redo.png differ diff --git a/pix/icons/edit-undo.png b/pix/icons/edit-undo.png new file mode 100644 index 000000000..6129fa0c7 Binary files /dev/null and b/pix/icons/edit-undo.png differ diff --git a/pix/icons/preferences.png b/pix/icons/preferences.png new file mode 100644 index 000000000..a5f3308a9 Binary files /dev/null and b/pix/icons/preferences.png differ diff --git a/pix/icons/zoom-in.png b/pix/icons/zoom-in.png new file mode 100644 index 000000000..192d7ecdf Binary files /dev/null and b/pix/icons/zoom-in.png differ diff --git a/pix/icons/zoom-out.png b/pix/icons/zoom-out.png new file mode 100644 index 000000000..a33559e55 Binary files /dev/null and b/pix/icons/zoom-out.png differ diff --git a/pix/spheres/blue-16x16.png b/pix/spheres/blue-16x16.png new file mode 100644 index 000000000..1f330f024 Binary files /dev/null and b/pix/spheres/blue-16x16.png differ diff --git a/pix/spheres/gray-16x16.png b/pix/spheres/gray-16x16.png new file mode 100644 index 000000000..d0951a91a Binary files /dev/null and b/pix/spheres/gray-16x16.png differ diff --git a/pix/spheres/green-16x16.png b/pix/spheres/green-16x16.png new file mode 100644 index 000000000..29d785dd6 Binary files /dev/null and b/pix/spheres/green-16x16.png differ diff --git a/pix/spheres/red-16x16.png b/pix/spheres/red-16x16.png new file mode 100644 index 000000000..79c71612e Binary files /dev/null and b/pix/spheres/red-16x16.png differ diff --git a/pix/spheres/yellow-16x16.png b/pix/spheres/yellow-16x16.png new file mode 100644 index 000000000..9ed11ea16 Binary files /dev/null and b/pix/spheres/yellow-16x16.png differ diff --git a/pix/splashscreen.png b/pix/splashscreen.png new file mode 100644 index 000000000..816b84a26 Binary files /dev/null and b/pix/splashscreen.png differ diff --git a/src/net/sf/openrocket/aerodynamics/AerodynamicCalculator.java b/src/net/sf/openrocket/aerodynamics/AerodynamicCalculator.java new file mode 100644 index 000000000..efdd14b15 --- /dev/null +++ b/src/net/sf/openrocket/aerodynamics/AerodynamicCalculator.java @@ -0,0 +1,581 @@ +package net.sf.openrocket.aerodynamics; + +import static net.sf.openrocket.util.MathUtil.pow2; + +import java.util.Iterator; +import java.util.Map; + +import net.sf.openrocket.rocketcomponent.Configuration; +import net.sf.openrocket.rocketcomponent.Motor; +import net.sf.openrocket.rocketcomponent.MotorMount; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.MathUtil; + + +/** + * A class that is the base of all aerodynamical calculations. + *

+ * A {@link Configuration} object must be assigned to this class before any + * operations are allowed. This can be done using the constructor or using + * the {@link #setConfiguration(Configuration)} method. The default is a + * null configuration, in which case the calculation + * methods throw {@link NullPointerException}. + * + * @author Sampo Niskanen + */ + +public abstract class AerodynamicCalculator { + + private static final double MIN_MASS = 0.001 * MathUtil.EPSILON; + + /** Number of divisions used when calculating worst CP. */ + public static final int DIVISIONS = 360; + + /** + * A WarningSet that can be used if null is passed + * to a calculation method. + */ + protected WarningSet ignoreWarningSet = new WarningSet(); + + /** + * The Rocket currently being calculated. + */ + protected Rocket rocket = null; + + protected Configuration configuration = null; + + + /* + * Cached data. All CG data is in absolute coordinates. All moments of inertia + * are relative to their respective CG. + */ + private Coordinate[] cgCache = null; + private Coordinate[] origCG = null; // CG of non-overridden stage + private double longitudalInertiaCache[] = null; + private double rotationalInertiaCache[] = null; + + + // TODO: LOW: Do not void unnecessary data (mass/aero separately) + private int rocketModID = -1; +// private int aeroModID = -1; +// private int massModID = -1; + + /** + * No-options constructor. The rocket is left as null. + */ + public AerodynamicCalculator() { + + } + + + + /** + * A constructor that sets the Configuration to be used. + * + * @param config the configuration to use + */ + public AerodynamicCalculator(Configuration config) { + setConfiguration(config); + } + + + + public Configuration getConfiguration() { + return configuration; + } + + public void setConfiguration(Configuration config) { + this.configuration = config; + this.rocket = config.getRocket(); + } + + + public abstract AerodynamicCalculator newInstance(); + + + ////////////////// Mass property calculations /////////////////// + + + /** + * Get the CG and mass of the current configuration with motors at the specified + * time. The motor ignition times are taken from the configuration. + */ + public Coordinate getCG(double time) { + Coordinate totalCG; + + totalCG = getEmptyCG(); + + Iterator iterator = configuration.motorIterator(); + while (iterator.hasNext()) { + MotorMount mount = iterator.next(); + double ignition = configuration.getIgnitionTime(mount); + Motor motor = mount.getMotor(configuration.getMotorConfigurationID()); + RocketComponent component = (RocketComponent) mount; + + double position = (component.getLength() - motor.getLength() + + mount.getMotorOverhang()); + + for (Coordinate c: component.toAbsolute(motor.getCG(time-ignition). + add(position,0,0))) { + totalCG = totalCG.average(c); + } + } + + return totalCG; + } + + + /** + * Get the CG and mass of the current configuration without motors. + * + * @return the CG of the configuration + */ + public Coordinate getEmptyCG() { + checkCache(); + + if (cgCache == null) { + calculateStageCache(); + } + + Coordinate totalCG = null; + for (int stage: configuration.getActiveStages()) { + totalCG = cgCache[stage].average(totalCG); + } + + if (totalCG == null) + totalCG = Coordinate.NUL; + + return totalCG; + } + + + /** + * Return the longitudal inertia of the current configuration with motors at + * the specified time. The motor ignition times are taken from the configuration. + * + * @param time the time. + * @return the longitudal moment of inertia of the configuration. + */ + public double getLongitudalInertia(double time) { + checkCache(); + + if (cgCache == null) { + calculateStageCache(); + } + + final Coordinate totalCG = getCG(time); + double totalInertia = 0; + + // Stages + for (int stage: configuration.getActiveStages()) { + Coordinate stageCG = cgCache[stage]; + + totalInertia += (longitudalInertiaCache[stage] + + stageCG.weight * MathUtil.pow2(stageCG.x - totalCG.x)); + } + + + // Motors + Iterator iterator = configuration.motorIterator(); + while (iterator.hasNext()) { + MotorMount mount = iterator.next(); + double ignition = configuration.getIgnitionTime(mount); + Motor motor = mount.getMotor(configuration.getMotorConfigurationID()); + RocketComponent component = (RocketComponent) mount; + + double position = (component.getLength() - motor.getLength() + + mount.getMotorOverhang()); + + double inertia = motor.getLongitudalInertia(time - ignition); + for (Coordinate c: component.toAbsolute(motor.getCG(time-ignition). + add(position,0,0))) { + totalInertia += inertia + c.weight * MathUtil.pow2(c.x - totalCG.x); + } + } + + return totalInertia; + } + + + /** + * Return the rotational inertia of the configuration with motors at the specified time. + * The motor ignition times are taken from the configuration. + * + * @param time the time. + * @return the rotational moment of inertia of the configuration. + */ + public double getRotationalInertia(double time) { + checkCache(); + + if (cgCache == null) { + calculateStageCache(); + } + + final Coordinate totalCG = getCG(time); + double totalInertia = 0; + + // Stages + for (int stage: configuration.getActiveStages()) { + Coordinate stageCG = cgCache[stage]; + + totalInertia += rotationalInertiaCache[stage] + stageCG.weight * ( + MathUtil.pow2(stageCG.y-totalCG.y) + MathUtil.pow2(stageCG.z-totalCG.z) + ); + } + + + // Motors + Iterator iterator = configuration.motorIterator(); + while (iterator.hasNext()) { + MotorMount mount = iterator.next(); + double ignition = configuration.getIgnitionTime(mount); + Motor motor = mount.getMotor(configuration.getMotorConfigurationID()); + RocketComponent component = (RocketComponent) mount; + + double position = (component.getLength() - motor.getLength() + + mount.getMotorOverhang()); + + double inertia = motor.getRotationalInertia(time - ignition); + for (Coordinate c: component.toAbsolute(motor.getCG(time-ignition). + add(position,0,0))) { + totalInertia += inertia + c.weight * ( + MathUtil.pow2(c.y - totalCG.y) + MathUtil.pow2(c.z - totalCG.z) + ); + } + } + + return totalInertia; + } + + + + private void calculateStageCache() { + int stages = rocket.getStageCount(); + + cgCache = new Coordinate[stages]; + longitudalInertiaCache = new double[stages]; + rotationalInertiaCache = new double[stages]; + + for (int i=0; i < stages; i++) { + RocketComponent stage = rocket.getChild(i); + MassData data = calculateAssemblyMassData(stage); + cgCache[i] = stage.toAbsolute(data.cg)[0]; + longitudalInertiaCache[i] = data.longitudalInertia; + rotationalInertiaCache[i] = data.rotationalInetria; + } + } + + + +// /** +// * Updates the stage CGs. +// */ +// private void calculateStageCGs() { +// int stages = rocket.getStageCount(); +// +// cgCache = new Coordinate[stages]; +// origCG = new Coordinate[stages]; +// +// for (int i=0; i < stages; i++) { +// Stage stage = (Stage) rocket.getChild(i); +// Coordinate stageCG = null; +// +// Iterator iterator = stage.deepIterator(); +// while (iterator.hasNext()) { +// RocketComponent component = iterator.next(); +// +// for (Coordinate c: component.toAbsolute(component.getCG())) { +// stageCG = c.average(stageCG); +// } +// } +// +// if (stageCG == null) +// stageCG = Coordinate.NUL; +// +// origCG[i] = stageCG; +// +// if (stage.isMassOverridden()) { +// stageCG = stageCG.setWeight(stage.getOverrideMass()); +// } +// if (stage.isCGOverridden()) { +// stageCG = stageCG.setXYZ(stage.getOverrideCG()); +// } +// +//// System.out.println("Stage "+i+" CG:"+stageCG); +// +// cgCache[i] = stageCG; +// } +// } +// +// +// private Coordinate calculateCG(RocketComponent component) { +// Coordinate componentCG = Coordinate.NUL; +// +// // Compute CG of this component +// Coordinate cg = component.getCG(); +// if (cg.weight < MIN_MASS) +// cg = cg.setWeight(MIN_MASS); +// +// for (Coordinate c: component.toAbsolute(cg)) { +// componentCG = componentCG.average(c); +// } +// +// // Compute CG with subcomponents +// for (RocketComponent sibling: component.getChildren()) { +// componentCG = componentCG.average(calculateCG(sibling)); +// } +// +// // Override mass/CG if subcomponents are also overridden +// if (component.getOverrideSubcomponents()) { +// if (component.isMassOverridden()) { +// componentCG = componentCG.setWeight( +// MathUtil.max(component.getOverrideMass(), MIN_MASS)); +// } +// if (component.isCGOverridden()) { +// componentCG = componentCG.setXYZ(component.getOverrideCG()); +// } +// } +// +// return componentCG; +// } +// +// +// +// private void calculateStageInertias() { +// int stages = rocket.getStageCount(); +// +// if (cgCache == null) +// calculateStageCGs(); +// +// longitudalInertiaCache = new double[stages]; +// rotationalInertiaCache = new double[stages]; +// +// for (int i=0; i < stages; i++) { +// Coordinate stageCG = cgCache[i]; +// double stageLongitudalInertia = 0; +// double stageRotationalInertia = 0; +// +// Iterator iterator = rocket.getChild(i).deepIterator(); +// while (iterator.hasNext()) { +// RocketComponent component = iterator.next(); +// double li = component.getLongitudalInertia(); +// double ri = component.getRotationalInertia(); +// double mass = component.getMass(); +// +// for (Coordinate c: component.toAbsolute(component.getCG())) { +// stageLongitudalInertia += li + mass * MathUtil.pow2(c.x - stageCG.x); +// stageRotationalInertia += ri + mass * (MathUtil.pow2(c.y - stageCG.y) + +// MathUtil.pow2(c.z - stageCG.z)); +// } +// } +// +// // Check for mass override of complete stage +// if ((origCG[i].weight != cgCache[i].weight) && origCG[i].weight > 0.0000001) { +// stageLongitudalInertia = (stageLongitudalInertia * cgCache[i].weight / +// origCG[i].weight); +// stageRotationalInertia = (stageRotationalInertia * cgCache[i].weight / +// origCG[i].weight); +// } +// +// longitudalInertiaCache[i] = stageLongitudalInertia; +// rotationalInertiaCache[i] = stageRotationalInertia; +// } +// } +// + + + /** + * Returns the mass and inertia data for this component and all subcomponents. + * The inertia is returned relative to the CG, and the CG is in the coordinates + * of the specified component, not global coordinates. + */ + private MassData calculateAssemblyMassData(RocketComponent parent) { + MassData parentData = new MassData(); + + // Calculate data for this component + parentData.cg = parent.getComponentCG(); + if (parentData.cg.weight < MIN_MASS) + parentData.cg = parentData.cg.setWeight(MIN_MASS); + + + // Override only this component's data + if (!parent.getOverrideSubcomponents()) { + if (parent.isMassOverridden()) + parentData.cg = parentData.cg.setWeight(MathUtil.max(parent.getOverrideMass(),MIN_MASS)); + if (parent.isCGOverridden()) + parentData.cg = parentData.cg.setXYZ(parent.getOverrideCG()); + } + + parentData.longitudalInertia = parent.getLongitudalUnitInertia() * parentData.cg.weight; + parentData.rotationalInetria = parent.getRotationalUnitInertia() * parentData.cg.weight; + + + // Combine data for subcomponents + for (RocketComponent sibling: parent.getChildren()) { + Coordinate combinedCG; + double dx2, dr2; + + // Compute data of sibling + MassData siblingData = calculateAssemblyMassData(sibling); + Coordinate[] siblingCGs = sibling.toRelative(siblingData.cg, parent); + + for (Coordinate siblingCG: siblingCGs) { + + // Compute CG of this + sibling + combinedCG = parentData.cg.average(siblingCG); + + // Add effect of this CG change to parent inertia + dx2 = pow2(parentData.cg.x - combinedCG.x); + parentData.longitudalInertia += parentData.cg.weight * dx2; + + dr2 = pow2(parentData.cg.y - combinedCG.y) + pow2(parentData.cg.z - combinedCG.z); + parentData.rotationalInetria += parentData.cg.weight * dr2; + + + // Add inertia of sibling + parentData.longitudalInertia += siblingData.longitudalInertia; + parentData.rotationalInetria += siblingData.rotationalInetria; + + // Add effect of sibling CG change + dx2 = pow2(siblingData.cg.x - combinedCG.x); + parentData.longitudalInertia += siblingData.cg.weight * dx2; + + dr2 = pow2(siblingData.cg.y - combinedCG.y) + pow2(siblingData.cg.z - combinedCG.z); + parentData.rotationalInetria += siblingData.cg.weight * dr2; + + // Set combined CG + parentData.cg = combinedCG; + } + } + + // Override total data + if (parent.getOverrideSubcomponents()) { + if (parent.isMassOverridden()) { + double oldMass = parentData.cg.weight; + double newMass = MathUtil.max(parent.getOverrideMass(), MIN_MASS); + parentData.longitudalInertia = parentData.longitudalInertia * newMass / oldMass; + parentData.rotationalInetria = parentData.rotationalInetria * newMass / oldMass; + parentData.cg = parentData.cg.setWeight(newMass); + } + if (parent.isCGOverridden()) { + double oldx = parentData.cg.x; + double newx = parent.getOverrideCGX(); + parentData.longitudalInertia += parentData.cg.weight * pow2(oldx - newx); + parentData.cg = parentData.cg.setX(newx); + } + } + + return parentData; + } + + + private static class MassData { + public Coordinate cg = Coordinate.NUL; + public double longitudalInertia = 0; + public double rotationalInetria = 0; + } + + + + + + //////////////// Aerodynamic calculators //////////////// + + public abstract Coordinate getCP(FlightConditions conditions, WarningSet warnings); + + /* + public abstract List getCPAnalysis(FlightConditions conditions, + WarningSet warnings); + */ + + public abstract Map + getForceAnalysis(FlightConditions conditions, WarningSet warnings); + + public abstract AerodynamicForces getAerodynamicForces(double time, + FlightConditions conditions, WarningSet warnings); + + + /* Calculate only axial forces (and do not warn about insane AOA etc) */ + public abstract AerodynamicForces getAxialForces(double time, + FlightConditions conditions, WarningSet warnings); + + + + public Coordinate getWorstCP() { + return getWorstCP(new FlightConditions(configuration), ignoreWarningSet); + } + + /* + * The worst theta angle is stored in conditions. + */ + public Coordinate getWorstCP(FlightConditions conditions, WarningSet warnings) { + FlightConditions cond = conditions.clone(); + Coordinate worst = new Coordinate(Double.MAX_VALUE); + Coordinate cp; + double theta = 0; + + for (int i=0; i < DIVISIONS; i++) { + cond.setTheta(2*Math.PI*i/DIVISIONS); + cp = getCP(cond, warnings); + if (cp.x < worst.x) { + worst = cp; + theta = cond.getTheta(); + } + } + + conditions.setTheta(theta); + + return worst; + } + + + + + + /** + * Check the current cache consistency. This method must be called by all + * methods that may use any cached data before any other operations are + * performed. If the rocket has changed since the previous call to + * checkCache(), then either {@link #voidAerodynamicCache()} or + * {@link #voidMassCache()} (or both) are called. + *

+ * This method performs the checking based on the rocket's modification IDs, + * so that these method may be called from listeners of the rocket itself. + */ + protected final void checkCache() { + if (rocketModID != rocket.getModID()) { + rocketModID = rocket.getModID(); + voidMassCache(); + voidAerodynamicCache(); + } + } + + + /** + * Void cached mass data. This method is called whenever a change occurs in + * the rocket structure that affects the mass of the rocket and when a new + * Rocket is set. This method must be overridden to void any cached data + * necessary. The method must call super.voidMassCache() during its + * execution. + */ + protected void voidMassCache() { + cgCache = null; + longitudalInertiaCache = null; + rotationalInertiaCache = null; + } + + /** + * Void cached aerodynamic data. This method is called whenever a change occurs in + * the rocket structure that affects the aerodynamics of the rocket and when a new + * Rocket is set. This method must be overridden to void any cached data + * necessary. The method must call super.voidAerodynamicCache() during + * its execution. + */ + protected void voidAerodynamicCache() { + // No-op + } + + +} diff --git a/src/net/sf/openrocket/aerodynamics/AerodynamicForces.java b/src/net/sf/openrocket/aerodynamics/AerodynamicForces.java new file mode 100644 index 000000000..5a6abcb0d --- /dev/null +++ b/src/net/sf/openrocket/aerodynamics/AerodynamicForces.java @@ -0,0 +1,180 @@ +package net.sf.openrocket.aerodynamics; + +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.util.Coordinate; + +public class AerodynamicForces implements Cloneable { + + /** + * The component this data is referring to. Used in analysis methods. + * A total value is indicated by the component being the Rocket component. + */ + public RocketComponent component = null; + + + /** CG and mass */ + public Coordinate cg = null; + + /** Longitudal moment of inertia with reference to the CG. */ + public double longitudalInertia = Double.NaN; + + /** Rotational moment of inertia with reference to the CG. */ + public double rotationalInertia = Double.NaN; + + + + /** CP and CNa. */ + public Coordinate cp = null; + + + /** + * Normal force coefficient derivative. At values close to zero angle of attack + * this value may be poorly defined or NaN if the calculation method does not + * compute CNa directly. + */ + public double CNa = Double.NaN; + + + /** Normal force coefficient. */ + public double CN = Double.NaN; + + /** Pitching moment coefficient, relative to the coordinate origin. */ + public double Cm = Double.NaN; + + /** Side force coefficient, Cy */ + public double Cside = Double.NaN; + + /** Yaw moment coefficient, Cn, relative to the coordinate origin. */ + public double Cyaw = Double.NaN; + + /** Roll moment coefficient, Cl, relative to the coordinate origin. */ + public double Croll = Double.NaN; + + /** Roll moment damping coefficient */ + public double CrollDamp = Double.NaN; + + /** Roll moment forcing coefficient */ + public double CrollForce = Double.NaN; + + + + /** Axial drag coefficient, CA */ + public double Caxial = Double.NaN; + + /** Total drag force coefficient, parallel to the airflow. */ + public double CD = Double.NaN; + + /** Drag coefficient due to fore pressure drag. */ + public double pressureCD = Double.NaN; + + /** Drag coefficient due to base drag. */ + public double baseCD = Double.NaN; + + /** Drag coefficient due to friction drag. */ + public double frictionCD = Double.NaN; + + + public double pitchDampingMoment = Double.NaN; + public double yawDampingMoment = Double.NaN; + + + /** + * Reset all values to null/NaN. + */ + public void reset() { + component = null; + cg = null; + longitudalInertia = Double.NaN; + rotationalInertia = Double.NaN; + + cp = null; + CNa = Double.NaN; + CN = Double.NaN; + Cm = Double.NaN; + Cside = Double.NaN; + Cyaw = Double.NaN; + Croll = Double.NaN; + CrollDamp = Double.NaN; + CrollForce = Double.NaN; + Caxial = Double.NaN; + CD = Double.NaN; + pitchDampingMoment = Double.NaN; + yawDampingMoment = Double.NaN; + } + + /** + * Zero all values to 0 / Coordinate.NUL. Component is left as it was. + */ + public void zero() { + // component untouched + cg = Coordinate.NUL; + longitudalInertia = 0; + rotationalInertia = 0; + + cp = Coordinate.NUL; + CNa = 0; + CN = 0; + Cm = 0; + Cside = 0; + Cyaw = 0; + Croll = 0; + CrollDamp = 0; + CrollForce = 0; + Caxial = 0; + CD = 0; + pitchDampingMoment = 0; + yawDampingMoment = 0; + } + + + @Override + public AerodynamicForces clone() { + try { + return (AerodynamicForces)super.clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeException("CloneNotSupportedException?!?"); + } + } + + @Override + public String toString() { + String text="AerodynamicForces["; + + if (component != null) + text += "component:" + component + ","; + if (cg != null) + text += "cg:" + cg + ","; + if (cp != null) + text += "cp:" + cp + ","; + if (!Double.isNaN(longitudalInertia)) + text += "longIn:" + longitudalInertia + ","; + if (!Double.isNaN(rotationalInertia)) + text += "rotIn:" + rotationalInertia + ","; + + if (!Double.isNaN(CNa)) + text += "CNa:" + CNa + ","; + if (!Double.isNaN(CN)) + text += "CN:" + CN + ","; + if (!Double.isNaN(Cm)) + text += "Cm:" + Cm + ","; + + if (!Double.isNaN(Cside)) + text += "Cside:" + Cside + ","; + if (!Double.isNaN(Cyaw)) + text += "Cyaw:" + Cyaw + ","; + + if (!Double.isNaN(Croll)) + text += "Croll:" + Croll + ","; + if (!Double.isNaN(Caxial)) + text += "Caxial:" + Caxial + ","; + + if (!Double.isNaN(CD)) + text += "CD:" + CD + ","; + + if (text.charAt(text.length()-1) == ',') + text = text.substring(0, text.length()-1); + + text += "]"; + return text; + } +} diff --git a/src/net/sf/openrocket/aerodynamics/AtmosphericConditions.java b/src/net/sf/openrocket/aerodynamics/AtmosphericConditions.java new file mode 100644 index 000000000..c36c525f2 --- /dev/null +++ b/src/net/sf/openrocket/aerodynamics/AtmosphericConditions.java @@ -0,0 +1,105 @@ +package net.sf.openrocket.aerodynamics; + +public class AtmosphericConditions implements Cloneable { + + /** Specific gas constant of dry air. */ + public static final double R = 287.053; + + /** Specific heat ratio of air. */ + public static final double GAMMA = 1.4; + + /** The standard air pressure (1.01325 bar). */ + public static final double STANDARD_PRESSURE = 101325.0; + + /** The standard air temperature (20 degrees Celcius). */ + public static final double STANDARD_TEMPERATURE = 293.15; + + + + /** Air pressure, in Pascals. */ + public double pressure = STANDARD_PRESSURE; + + /** Air temperature, in Kelvins. */ + public double temperature = STANDARD_TEMPERATURE; + + + /** + * Construct standard atmospheric conditions. + */ + public AtmosphericConditions() { + + } + + /** + * Construct specified atmospheric conditions. + * + * @param temperature the temperature in Kelvins. + * @param pressure the pressure in Pascals. + */ + public AtmosphericConditions(double temperature, double pressure) { + this.temperature = temperature; + this.pressure = pressure; + } + + + + /** + * Return the current density of air for dry air. + * + * @return the current density of air. + */ + public double getDensity() { + return pressure / (R*temperature); + } + + + /** + * Return the current speed of sound for dry air. + *

+ * The speed of sound is calculated using the expansion around the temperature 0 C + * c = 331.3 + 0.606*T where T is in Celcius. The result is accurate + * to about 0.5 m/s for temperatures between -30 and 30 C, and within 2 m/s + * for temperatures between -55 and 30 C. + * + * @return the current speed of sound. + */ + public double getMachSpeed() { + return 165.77 + 0.606 * temperature; + } + + + /** + * Return the current kinematic viscosity of the air. + *

+ * The effect of temperature on the viscosity of a gas can be computed using + * Sutherland's formula. In the region of -40 ... 40 degrees Celcius the effect + * is highly linear, and thus a linear approximation is used in its stead. + * This is divided by the result of {@link #getDensity()} to achieve the + * kinematic viscosity. + * + * @return the current kinematic viscosity. + */ + public double getKinematicViscosity() { + double v = 3.7291e-06 + 4.9944e-08 * temperature; + return v / getDensity(); + } + + /** + * Return a copy of the atmospheric conditions. + */ + @Override + public AtmosphericConditions clone() { + try { + return (AtmosphericConditions) super.clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeException("BUG: CloneNotSupportedException encountered!"); + } + } + + + @Override + public String toString() { + return String.format("AtmosphericConditions[T=%.2f,P=%.2f]", temperature, pressure); + } + +} diff --git a/src/net/sf/openrocket/aerodynamics/AtmosphericModel.java b/src/net/sf/openrocket/aerodynamics/AtmosphericModel.java new file mode 100644 index 000000000..ea2ead066 --- /dev/null +++ b/src/net/sf/openrocket/aerodynamics/AtmosphericModel.java @@ -0,0 +1,41 @@ +package net.sf.openrocket.aerodynamics; + +public abstract class AtmosphericModel { + /** Layer thickness of interpolated altitude. */ + private static final double DELTA = 500; + + private AtmosphericConditions[] levels = null; + + + public AtmosphericConditions getConditions(double altitude) { + if (levels == null) + computeLayers(); + + if (altitude <= 0) + return levels[0]; + if (altitude >= DELTA*(levels.length-1)) + return levels[levels.length-1]; + + int n = (int)(altitude/DELTA); + double d = (altitude - n*DELTA)/DELTA; + AtmosphericConditions c = new AtmosphericConditions(); + c.temperature = levels[n].temperature * (1-d) + levels[n+1].temperature * d; + c.pressure = levels[n].pressure * (1-d) + levels[n+1].pressure * d; + + return c; + } + + + private void computeLayers() { + double max = getMaxAltitude(); + int n = (int)(max/DELTA) + 1; + levels = new AtmosphericConditions[n]; + for (int i=0; i < n; i++) { + levels[i] = getExactConditions(i*DELTA); + } + } + + + public abstract double getMaxAltitude(); + public abstract AtmosphericConditions getExactConditions(double altitude); +} diff --git a/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java b/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java new file mode 100644 index 000000000..8ace04ac7 --- /dev/null +++ b/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java @@ -0,0 +1,897 @@ +package net.sf.openrocket.aerodynamics; + +import static net.sf.openrocket.util.MathUtil.pow2; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; + +import net.sf.openrocket.aerodynamics.barrowman.FinSetCalc; +import net.sf.openrocket.aerodynamics.barrowman.RocketComponentCalc; +import net.sf.openrocket.rocketcomponent.Configuration; +import net.sf.openrocket.rocketcomponent.ExternalComponent; +import net.sf.openrocket.rocketcomponent.FinSet; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.rocketcomponent.SymmetricComponent; +import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish; +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.MathUtil; +import net.sf.openrocket.util.PolyInterpolator; +import net.sf.openrocket.util.Reflection; +import net.sf.openrocket.util.Test; + +/** + * An aerodynamic calculator that uses the extended Barrowman method to + * calculate the CP of a rocket. + * + * @author Sampo Niskanen + */ +public class BarrowmanCalculator extends AerodynamicCalculator { + + private static final String BARROWMAN_PACKAGE = "net.sf.openrocket.aerodynamics.barrowman"; + private static final String BARROWMAN_SUFFIX = "Calc"; + + + private Map calcMap = null; + + private double cacheDiameter = -1; + private double cacheLength = -1; + + + + public BarrowmanCalculator() { + + } + + public BarrowmanCalculator(Configuration config) { + super(config); + } + + + @Override + public BarrowmanCalculator newInstance() { + return new BarrowmanCalculator(); + } + + + /** + * Calculate the CP according to the extended Barrowman method. + */ + @Override + public Coordinate getCP(FlightConditions conditions, WarningSet warnings) { + AerodynamicForces forces = getNonAxial(conditions, null, warnings); + return forces.cp; + } + + + + @Override + public Map getForceAnalysis(FlightConditions conditions, + WarningSet warnings) { + + AerodynamicForces f; + Map map = + new LinkedHashMap(); + + // Add all components to the map + for (RocketComponent c: configuration) { + f = new AerodynamicForces(); + f.component = c; + + // Calculate CG + f.cg = Coordinate.NUL; + for (Coordinate coord: c.toAbsolute(c.getCG())) { + f.cg = f.cg.average(coord); + } + + map.put(c, f); + } + + + // Calculate non-axial force data + AerodynamicForces total = getNonAxial(conditions, map, warnings); + + + // Calculate friction data + total.frictionCD = calculateFrictionDrag(conditions, map, warnings); + total.pressureCD = calculatePressureDrag(conditions, map, warnings); + total.baseCD = calculateBaseDrag(conditions, map, warnings); + total.cg = getCG(0); + + total.component = rocket; + map.put(rocket, total); + + + for (RocketComponent c: map.keySet()) { + f = map.get(c); + if (Double.isNaN(f.baseCD) && Double.isNaN(f.pressureCD) && + Double.isNaN(f.frictionCD)) + continue; + if (Double.isNaN(f.baseCD)) + f.baseCD = 0; + if (Double.isNaN(f.pressureCD)) + f.pressureCD = 0; + if (Double.isNaN(f.frictionCD)) + f.frictionCD = 0; + f.CD = f.baseCD + f.pressureCD + f.frictionCD; + f.Caxial = calculateAxialDrag(conditions, f.CD); + } + + return map; + } + + + + @Override + public AerodynamicForces getAerodynamicForces(double time, FlightConditions conditions, + WarningSet warnings) { + + if (warnings == null) + warnings = ignoreWarningSet; + + // Calculate non-axial force data + AerodynamicForces total = getNonAxial(conditions, null, warnings); + + // Calculate friction data + total.frictionCD = calculateFrictionDrag(conditions, null, warnings); + total.pressureCD = calculatePressureDrag(conditions, null, warnings); + total.baseCD = calculateBaseDrag(conditions, null, warnings); + + total.CD = total.frictionCD + total.pressureCD + total.baseCD; + + total.Caxial = calculateAxialDrag(conditions, total.CD); + + + // Calculate CG and moments of inertia + total.cg = this.getCG(time); + total.longitudalInertia = this.getLongitudalInertia(time); + total.rotationalInertia = this.getRotationalInertia(time); + + + // Calculate pitch and yaw damping moments + calculateDampingMoments(conditions, total); + total.Cm -= total.pitchDampingMoment; + total.Cyaw -= total.yawDampingMoment; + + +// System.out.println("Conditions are "+conditions + " +// pitch rate="+conditions.getPitchRate()); +// System.out.println("Total Cm="+total.Cm+" damping effect="+ +// (12 * Math.signum(conditions.getPitchRate()) * +// MathUtil.pow2(conditions.getPitchRate()) / +// MathUtil.pow2(conditions.getVelocity()))); + +// double ef = Math.abs(12 * +// MathUtil.pow2(conditions.getPitchRate()) / +// MathUtil.pow2(conditions.getVelocity())); +// +//// System.out.println("maxEffect="+maxEffect); +// total.Cm -= 12 * Math.signum(conditions.getPitchRate()) * +// MathUtil.pow2(conditions.getPitchRate()) / +// MathUtil.pow2(conditions.getVelocity()); +// +// total.Cyaw -= 0.06 * Math.signum(conditions.getYawRate()) * +// MathUtil.pow2(conditions.getYawRate()) / +// MathUtil.pow2(conditions.getVelocity()); + + return total; + } + + + + @Override + public AerodynamicForces getAxialForces(double time, + FlightConditions conditions, WarningSet warnings) { + + if (warnings == null) + warnings = ignoreWarningSet; + + AerodynamicForces total = new AerodynamicForces(); + total.zero(); + + // Calculate friction data + total.frictionCD = calculateFrictionDrag(conditions, null, warnings); + total.pressureCD = calculatePressureDrag(conditions, null, warnings); + total.baseCD = calculateBaseDrag(conditions, null, warnings); + + total.CD = total.frictionCD + total.pressureCD + total.baseCD; + + total.Caxial = calculateAxialDrag(conditions, total.CD); + + // Calculate CG and moments of inertia + total.cg = this.getCG(time); + total.longitudalInertia = this.getLongitudalInertia(time); + total.rotationalInertia = this.getRotationalInertia(time); + + return total; + } + + + + + + /* + * Perform the actual CP calculation. + */ + private AerodynamicForces getNonAxial(FlightConditions conditions, + Map map, WarningSet warnings) { + + AerodynamicForces total = new AerodynamicForces(); + total.zero(); + + double radius = 0; // aft radius of previous component + double componentX = 0; // aft coordinate of previous component + AerodynamicForces forces = new AerodynamicForces(); + + if (warnings == null) + warnings = ignoreWarningSet; + + if (conditions.getAOA() > 17.5*Math.PI/180) + warnings.add(new Warning.LargeAOA(conditions.getAOA())); + + checkCache(); + + if (calcMap == null) + buildCalcMap(); + + for (RocketComponent component: configuration) { + + // Skip non-aerodynamic components + if (!component.isAerodynamic()) + continue; + + // Check for discontinuities + if (component instanceof SymmetricComponent) { + SymmetricComponent sym = (SymmetricComponent) component; + // TODO:LOW: Ignores other cluster components (not clusterable) + double x = component.toAbsolute(Coordinate.NUL)[0].x; + + // Check for lengthwise discontinuity + if (x > componentX + 0.0001){ + if (!MathUtil.equals(radius, 0)) { + warnings.add(Warning.DISCONTINUITY); + radius = 0; + } + } + componentX = component.toAbsolute(new Coordinate(component.getLength()))[0].x; + + // Check for radius discontinuity + if (!MathUtil.equals(sym.getForeRadius(), radius)) { + warnings.add(Warning.DISCONTINUITY); + // TODO: MEDIUM: Apply correction to values to cp and to map + } + radius = sym.getAftRadius(); + } + + // Call calculation method + forces.zero(); + calcMap.get(component).calculateNonaxialForces(conditions, forces, warnings); + forces.cp = component.toAbsolute(forces.cp)[0]; + forces.Cm = forces.CN * forces.cp.x / conditions.getRefLength(); +// System.out.println(" CN="+forces.CN+" cp.x="+forces.cp.x+" Cm="+forces.Cm); + + if (map != null) { + AerodynamicForces f = map.get(component); + + f.cp = forces.cp; + f.CNa = forces.CNa; + f.CN = forces.CN; + f.Cm = forces.Cm; + f.Cside = forces.Cside; + f.Cyaw = forces.Cyaw; + f.Croll = forces.Croll; + f.CrollDamp = forces.CrollDamp; + f.CrollForce = forces.CrollForce; + } + + total.cp = total.cp.average(forces.cp); + total.CNa += forces.CNa; + total.CN += forces.CN; + total.Cm += forces.Cm; + total.Cside += forces.Cside; + total.Cyaw += forces.Cyaw; + total.Croll += forces.Croll; + total.CrollDamp += forces.CrollDamp; + total.CrollForce += forces.CrollForce; + } + + return total; + } + + + + + //////////////// DRAG CALCULATIONS //////////////// + + + private double calculateFrictionDrag(FlightConditions conditions, + Map map, WarningSet set) { + double c1=1.0, c2=1.0; + + double mach = conditions.getMach(); + double Re; + double Cf; + + if (calcMap == null) + buildCalcMap(); + + Re = conditions.getVelocity() * configuration.getLength() / + conditions.getAtmosphericConditions().getKinematicViscosity(); + +// System.out.printf("Re=%.3e ", Re); + + // Calculate the skin friction coefficient (assume non-roughness limited) + if (configuration.getRocket().isPerfectFinish()) { + +// System.out.printf("Perfect finish: Re=%f ",Re); + // Assume partial laminar layer. Roughness-limitation is checked later. + if (Re < 1e4) { + // Too low, constant + Cf = 1.33e-2; +// System.out.printf("constant Cf=%f ",Cf); + } else if (Re < 5.39e5) { + // Fully laminar + Cf = 1.328 / Math.sqrt(Re); +// System.out.printf("basic Cf=%f ",Cf); + } else { + // Transitional + Cf = 1.0/pow2(1.50 * Math.log(Re) - 5.6) - 1700/Re; +// System.out.printf("transitional Cf=%f ",Cf); + } + + // Compressibility correction + + if (mach < 1.1) { + // Below Re=1e6 no correction + if (Re > 1e6) { + if (Re < 3e6) { + c1 = 1 - 0.1*pow2(mach)*(Re-1e6)/2e6; // transition to turbulent + } else { + c1 = 1 - 0.1*pow2(mach); + } + } + } + if (mach > 0.9) { + if (Re > 1e6) { + if (Re < 3e6) { + c2 = 1 + (1.0 / Math.pow(1+0.045*pow2(mach), 0.25) -1) * (Re-1e6)/2e6; + } else { + c2 = 1.0 / Math.pow(1+0.045*pow2(mach), 0.25); + } + } + } + +// System.out.printf("c1=%f c2=%f\n", c1,c2); + // Applying continuously around Mach 1 + if (mach < 0.9) { + Cf *= c1; + } else if (mach < 1.1) { + Cf *= (c2 * (mach-0.9)/0.2 + c1 * (1.1-mach)/0.2); + } else { + Cf *= c2; + } + +// System.out.printf("M=%f Cf=%f (smooth)\n",mach,Cf); + + } else { + + // Assume fully turbulent. Roughness-limitation is checked later. + if (Re < 1e4) { + // Too low, constant + Cf = 1.48e-2; +// System.out.printf("LOW-TURB "); + } else { + // Turbulent + Cf = 1.0/pow2(1.50 * Math.log(Re) - 5.6); +// System.out.printf("NORMAL-TURB "); + } + + // Compressibility correction + + if (mach < 1.1) { + c1 = 1 - 0.1*pow2(mach); + } + if (mach > 0.9) { + c2 = 1/Math.pow(1 + 0.15*pow2(mach), 0.58); + } + // Applying continuously around Mach 1 + if (mach < 0.9) { + Cf *= c1; + } else if (mach < 1.1) { + Cf *= c2 * (mach-0.9)/0.2 + c1 * (1.1-mach)/0.2; + } else { + Cf *= c2; + } + +// System.out.printf("M=%f, Cd=%f (turbulent)\n", mach,Cf); + + } + + // Roughness-limited value correction term + double roughnessCorrection; + if (mach < 0.9) { + roughnessCorrection = 1 - 0.1*pow2(mach); + } else if (mach > 1.1) { + roughnessCorrection = 1/(1 + 0.18*pow2(mach)); + } else { + c1 = 1 - 0.1*pow2(0.9); + c2 = 1.0/(1+0.18 * pow2(1.1)); + roughnessCorrection = c2 * (mach-0.9)/0.2 + c1 * (1.1-mach)/0.2; + } + +// System.out.printf("Cf=%.3f ", Cf); + + + /* + * Calculate the friction drag coefficient. + * + * The body wetted area is summed up and finally corrected with the rocket + * fineness ratio (calculated in the same iteration). The fins are corrected + * for thickness as we go on. + */ + + double finFriction = 0; + double bodyFriction = 0; + double maxR=0, len=0; + + double[] roughnessLimited = new double[Finish.values().length]; + Arrays.fill(roughnessLimited, Double.NaN); + + for (RocketComponent c: configuration) { + + // Consider only SymmetricComponents and FinSets: + if (!(c instanceof SymmetricComponent) && + !(c instanceof FinSet)) + continue; + + // Calculate the roughness-limited friction coefficient + Finish finish = ((ExternalComponent)c).getFinish(); + if (Double.isNaN(roughnessLimited[finish.ordinal()])) { + roughnessLimited[finish.ordinal()] = + 0.032 * Math.pow(finish.getRoughnessSize()/configuration.getLength(), 0.2) * + roughnessCorrection; + +// System.out.printf("roughness["+finish+"]=%.3f ", +// roughnessLimited[finish.ordinal()]); + } + + /* + * Actual Cf is maximum of Cf and the roughness-limited value. + * For perfect finish require additionally that Re > 1e6 + */ + double componentCf; + if (configuration.getRocket().isPerfectFinish()) { + + // For perfect finish require Re > 1e6 + if ((Re > 1.0e6) && (roughnessLimited[finish.ordinal()] > Cf)) { + componentCf = roughnessLimited[finish.ordinal()]; +// System.out.printf(" rl=%f Cf=%f (perfect=%b)\n", +// roughnessLimited[finish.ordinal()], +// Cf,rocket.isPerfectFinish()); + +// System.out.printf("LIMITED "); + } else { + componentCf = Cf; +// System.out.printf("NORMAL "); + } + + } else { + + // For fully turbulent use simple max + componentCf = Math.max(Cf, roughnessLimited[finish.ordinal()]); + + } + +// System.out.printf("compCf=%.3f ", componentCf); + + + + + // Calculate the friction drag: + if (c instanceof SymmetricComponent) { + + SymmetricComponent s = (SymmetricComponent)c; + + bodyFriction += componentCf * s.getComponentWetArea(); + + if (map != null) { + // Corrected later + map.get(c).frictionCD = componentCf * s.getComponentWetArea() + / conditions.getRefArea(); + } + + double r = Math.max(s.getForeRadius(), s.getAftRadius()); + if (r > maxR) + maxR = r; + len += c.getLength(); + + } else if (c instanceof FinSet) { + + FinSet f = (FinSet)c; + double mac = ((FinSetCalc)calcMap.get(c)).getMACLength(); + double cd = componentCf * (1 + 2*f.getThickness()/mac) * + 2*f.getFinCount() * f.getFinArea(); + finFriction += cd; + + if (map != null) { + map.get(c).frictionCD = cd / conditions.getRefArea(); + } + + } + + } + // fB may be POSITIVE_INFINITY, but that's ok for us + double fB = (len+0.0001) / maxR; + double correction = (1 + 1.0/(2*fB)); + + // Correct body data in map + if (map != null) { + for (RocketComponent c: map.keySet()) { + if (c instanceof SymmetricComponent) { + map.get(c).frictionCD *= correction; + } + } + } + +// System.out.printf("\n"); + return (finFriction + correction*bodyFriction) / conditions.getRefArea(); + } + + + + private double calculatePressureDrag(FlightConditions conditions, + Map map, WarningSet warnings) { + + double stagnation, base, total; + double radius = 0; + + if (calcMap == null) + buildCalcMap(); + + stagnation = calculateStagnationCD(conditions.getMach()); + base = calculateBaseCD(conditions.getMach()); + + total = 0; + for (RocketComponent c: configuration) { + if (!c.isAerodynamic()) + continue; + + // Pressure fore drag + double cd = calcMap.get(c).calculatePressureDragForce(conditions, stagnation, base, + warnings); + total += cd; + + if (map != null) { + map.get(c).pressureCD = cd; + } + + + // Stagnation drag + if (c instanceof SymmetricComponent) { + SymmetricComponent s = (SymmetricComponent)c; + + if (radius < s.getForeRadius()) { + double area = Math.PI*(pow2(s.getForeRadius()) - pow2(radius)); + cd = stagnation * area / conditions.getRefArea(); + total += cd; + if (map != null) { + map.get(c).pressureCD += cd; + } + } + + radius = s.getAftRadius(); + } + } + + return total; + } + + + private double calculateBaseDrag(FlightConditions conditions, + Map map, WarningSet warnings) { + + double base, total; + double radius = 0; + RocketComponent prevComponent = null; + + if (calcMap == null) + buildCalcMap(); + + base = calculateBaseCD(conditions.getMach()); + total = 0; + + for (RocketComponent c: configuration) { + if (!(c instanceof SymmetricComponent)) + continue; + + SymmetricComponent s = (SymmetricComponent)c; + + if (radius > s.getForeRadius()) { + double area = Math.PI*(pow2(radius) - pow2(s.getForeRadius())); + double cd = base * area / conditions.getRefArea(); + total += cd; + if (map != null) { + map.get(prevComponent).baseCD = cd; + } + } + + radius = s.getAftRadius(); + prevComponent = c; + } + + if (radius > 0) { + double area = Math.PI*pow2(radius); + double cd = base * area / conditions.getRefArea(); + total += cd; + if (map != null) { + map.get(prevComponent).baseCD = cd; + } + } + + return total; + } + + + + public static double calculateStagnationCD(double m) { + double pressure; + if (m <=1) { + pressure = 1 + pow2(m)/4 + pow2(pow2(m))/40; + } else { + pressure = 1.84 - 0.76/pow2(m) + 0.166/pow2(pow2(m)) + 0.035/pow2(m*m*m); + } + return 0.85 * pressure; + } + + + public static double calculateBaseCD(double m) { + if (m <= 1) { + return 0.12 + 0.13 * m*m; + } else { + return 0.25 / m; + } + } + + + + private static final double[] axialDragPoly1, axialDragPoly2; + static { + PolyInterpolator interpolator; + interpolator = new PolyInterpolator( + new double[] { 0, 17*Math.PI/180 }, + new double[] { 0, 17*Math.PI/180 } + ); + axialDragPoly1 = interpolator.interpolator(1, 1.3, 0, 0); + + interpolator = new PolyInterpolator( + new double[] { 17*Math.PI/180, Math.PI/2 }, + new double[] { 17*Math.PI/180, Math.PI/2 }, + new double[] { Math.PI/2 } + ); + axialDragPoly2 = interpolator.interpolator(1.3, 0, 0, 0, 0); + } + + + /** + * Calculate the axial drag from the total drag coefficient. + * + * @param conditions + * @param cd + * @return + */ + private double calculateAxialDrag(FlightConditions conditions, double cd) { + double aoa = MathUtil.clamp(conditions.getAOA(), 0, Math.PI); + double mul; + +// double sinaoa = conditions.getSinAOA(); +// return cd * (1 + Math.min(sinaoa, 0.25)); + + + if (aoa > Math.PI/2) + aoa = Math.PI - aoa; + if (aoa < 17*Math.PI/180) + mul = PolyInterpolator.eval(aoa, axialDragPoly1); + else + mul = PolyInterpolator.eval(aoa, axialDragPoly2); + + if (conditions.getAOA() < Math.PI/2) + return mul * cd; + else + return -mul * cd; + } + + + private void calculateDampingMoments(FlightConditions conditions, + AerodynamicForces total) { + + // Calculate pitch and yaw damping moments + if (conditions.getPitchRate() > 0.1 || conditions.getYawRate() > 0.1 || true) { + double mul = getDampingMultiplier(conditions, total.cg.x); + double pitch = conditions.getPitchRate(); + double yaw = conditions.getYawRate(); + double vel = conditions.getVelocity(); + +// double Cm = total.Cm - total.CN * total.cg.x / conditions.getRefLength(); +// System.out.printf("Damping pitch/yaw, mul=%.4f pitch rate=%.4f "+ +// "Cm=%.4f / %.4f effect=%.4f aoa=%.4f\n", mul, pitch, total.Cm, Cm, +// -(mul * MathUtil.sign(pitch) * pow2(pitch/vel)), +// conditions.getAOA()*180/Math.PI); + + mul *= 3; // TODO: Higher damping yields much more realistic apogee turn + +// total.Cm -= mul * pitch / pow2(vel); +// total.Cyaw -= mul * yaw / pow2(vel); + total.pitchDampingMoment = mul * MathUtil.sign(pitch) * pow2(pitch/vel); + total.yawDampingMoment = mul * MathUtil.sign(yaw) * pow2(yaw/vel); + } else { + total.pitchDampingMoment = 0; + total.yawDampingMoment = 0; + } + + } + + // TODO: MEDIUM: Are the rotation etc. being added correctly? sin/cos theta? + + + private double getDampingMultiplier(FlightConditions conditions, double cgx) { + if (cacheDiameter < 0) { + double area = 0; + cacheLength = 0; + cacheDiameter = 0; + + for (RocketComponent c: configuration) { + if (c instanceof SymmetricComponent) { + SymmetricComponent s = (SymmetricComponent)c; + area += s.getComponentPlanformArea(); + cacheLength += s.getLength(); + } + } + if (cacheLength > 0) + cacheDiameter = area / cacheLength; + } + + double mul; + + // Body + mul = 0.275 * cacheDiameter / (conditions.getRefArea() * conditions.getRefLength()); + mul *= (MathUtil.pow4(cgx) + MathUtil.pow4(cacheLength - cgx)); + + // Fins + // TODO: LOW: This could be optimized a lot... + for (RocketComponent c: configuration) { + if (c instanceof FinSet) { + FinSet f = (FinSet)c; + mul += 0.6 * Math.min(f.getFinCount(), 4) * f.getFinArea() * + MathUtil.pow3(Math.abs(f.toAbsolute(new Coordinate( + ((FinSetCalc)calcMap.get(f)).getMidchordPos()))[0].x + - cgx)) / + (conditions.getRefArea() * conditions.getRefLength()); + } + } + + return mul; + } + + + + //////// The calculator map + + @Override + protected void voidAerodynamicCache() { + super.voidAerodynamicCache(); + + calcMap = null; + cacheDiameter = -1; + cacheLength = -1; + } + + + private void buildCalcMap() { + Iterator iterator; + + calcMap = new HashMap(); + + iterator = rocket.deepIterator(); + while (iterator.hasNext()) { + RocketComponent c = iterator.next(); + + if (!c.isAerodynamic()) + continue; + + calcMap.put(c, (RocketComponentCalc) Reflection.construct(BARROWMAN_PACKAGE, + c, BARROWMAN_SUFFIX, c)); + } + } + + + + + public static void main(String[] arg) { + + PolyInterpolator interpolator; + + interpolator = new PolyInterpolator( + new double[] { 0, 17*Math.PI/180 }, + new double[] { 0, 17*Math.PI/180 } + ); + double[] poly1 = interpolator.interpolator(1, 1.3, 0, 0); + + interpolator = new PolyInterpolator( + new double[] { 17*Math.PI/180, Math.PI/2 }, + new double[] { 17*Math.PI/180, Math.PI/2 }, + new double[] { Math.PI/2 } + ); + double[] poly2 = interpolator.interpolator(1.3, 0, 0, 0, 0); + + + for (double a=0; a<=180.1; a++) { + double r = a*Math.PI/180; + if (r > Math.PI/2) + r = Math.PI - r; + + double value; + if (r < 18*Math.PI/180) + value = PolyInterpolator.eval(r, poly1); + else + value = PolyInterpolator.eval(r, poly2); + + System.out.println(""+a+" "+value); + } + + System.exit(0); + + + Rocket normal = Test.makeRocket(); + Rocket perfect = Test.makeRocket(); + normal.setPerfectFinish(false); + perfect.setPerfectFinish(true); + + Configuration confNormal = new Configuration(normal); + Configuration confPerfect = new Configuration(perfect); + + for (RocketComponent c: confNormal) { + if (c instanceof ExternalComponent) { + ((ExternalComponent)c).setFinish(Finish.NORMAL); + } + } + for (RocketComponent c: confPerfect) { + if (c instanceof ExternalComponent) { + ((ExternalComponent)c).setFinish(Finish.NORMAL); + } + } + + + confNormal.setToStage(0); + confPerfect.setToStage(0); + + + + BarrowmanCalculator calcNormal = new BarrowmanCalculator(confNormal); + BarrowmanCalculator calcPerfect = new BarrowmanCalculator(confPerfect); + + FlightConditions conditions = new FlightConditions(confNormal); + + for (double mach=0; mach < 3; mach += 0.1) { + conditions.setMach(mach); + + Map data = + calcNormal.getForceAnalysis(conditions, null); + AerodynamicForces forcesNormal = data.get(normal); + + data = calcPerfect.getForceAnalysis(conditions, null); + AerodynamicForces forcesPerfect = data.get(perfect); + + System.out.printf("%f %f %f %f %f %f %f\n",mach, + forcesNormal.pressureCD, forcesPerfect.pressureCD, + forcesNormal.frictionCD, forcesPerfect.frictionCD, + forcesNormal.CD, forcesPerfect.CD); + } + + + + } + +} diff --git a/src/net/sf/openrocket/aerodynamics/ConeDragTest.java b/src/net/sf/openrocket/aerodynamics/ConeDragTest.java new file mode 100644 index 000000000..b2d2c039c --- /dev/null +++ b/src/net/sf/openrocket/aerodynamics/ConeDragTest.java @@ -0,0 +1,103 @@ +package net.sf.openrocket.aerodynamics; + +import net.sf.openrocket.util.PolyInterpolator; + +public class ConeDragTest { + + private static final double DELTA = 0.01; + private static final double SUBSONIC = 0.0; + private static final double SUPERSONIC = 1.3; + + + private static final PolyInterpolator polyInt2 = new PolyInterpolator( + new double[] {1.0, SUPERSONIC} + ,new double[] {1.0, SUPERSONIC} + ,new double[] {SUPERSONIC} + ); + + private final double angle; + private final double sin; + private final double ratio; + + private final double[] int2; + + // Coefficients for subsonic interpolation a * M^b + c + private final double a, b, c; + + + + public ConeDragTest(double angle) { + this.angle = angle; + this.sin = Math.sin(angle); + this.ratio = 1.0 / (2*Math.tan(angle)); + + double dsuper = (supersonic(SUPERSONIC+DELTA) - supersonic(SUPERSONIC))/DELTA; + + + c = subsonic(0); + a = sonic() - c; + b = sonicDerivative() / a; + + + int2 = polyInt2.interpolator( + sonic(), supersonic(SUPERSONIC) + , sonicDerivative(), dsuper + ,0 + ); + + System.err.println("At mach1: CD="+sin+" dCD/dM="+(4.0/2.4*(1-0.5*sin))); + + } + + private double subsonic(double m) { + return 0.8*sin*sin/Math.sqrt(1-m*m); + } + + private double sonic() { + return sin; + } + + private double sonicDerivative() { + return 4.0/2.4*(1-0.5*sin); + } + + private double supersonic(double m) { + return 2.1 * sin*sin + 0.5*sin/Math.sqrt(m*m-1); + } + + + + + public double getCD(double m) { + if (m >= SUPERSONIC) + return supersonic(m); + + if (m <= 1.0) + return a * Math.pow(m,b) + c; + return PolyInterpolator.eval(m, int2); + +// return PolyInterpolator.eval(m, interpolator); + } + + + + public static void main(String[] arg) { + + ConeDragTest cone10 = new ConeDragTest(10.0*Math.PI/180); + ConeDragTest cone20 = new ConeDragTest(20.0*Math.PI/180); + ConeDragTest cone30 = new ConeDragTest(30.0*Math.PI/180); + + ConeDragTest coneX = new ConeDragTest(5.0*Math.PI/180); + + for (double m=0; m < 4.0001; m+=0.02) { + System.out.println(m + " " + + cone10.getCD(m) + " " + + cone20.getCD(m) + " " + + cone30.getCD(m) + " " +// + coneX.getCD(m) + ); + } + + } + +} diff --git a/src/net/sf/openrocket/aerodynamics/ExactAtmosphericConditions.java b/src/net/sf/openrocket/aerodynamics/ExactAtmosphericConditions.java new file mode 100644 index 000000000..3da8cf866 --- /dev/null +++ b/src/net/sf/openrocket/aerodynamics/ExactAtmosphericConditions.java @@ -0,0 +1,27 @@ +package net.sf.openrocket.aerodynamics; + +/** + * A class containing more accurate methods for computing the atmospheric properties. + * + * @author Sampo Niskanen + */ +public class ExactAtmosphericConditions extends AtmosphericConditions { + + @Override + public double getDensity() { + // TODO Auto-generated method stub + return super.getDensity(); + } + + @Override + public double getKinematicViscosity() { + // TODO Auto-generated method stub + return super.getKinematicViscosity(); + } + + @Override + public double getMachSpeed() { + return 331.3 * Math.sqrt(1 + (temperature - 273.15)/273.15); + } + +} diff --git a/src/net/sf/openrocket/aerodynamics/ExtendedISAModel.java b/src/net/sf/openrocket/aerodynamics/ExtendedISAModel.java new file mode 100644 index 000000000..fa8c8a526 --- /dev/null +++ b/src/net/sf/openrocket/aerodynamics/ExtendedISAModel.java @@ -0,0 +1,123 @@ +package net.sf.openrocket.aerodynamics; + +import static net.sf.openrocket.aerodynamics.AtmosphericConditions.R; +import net.sf.openrocket.util.MathUtil; + + +/** + * An atmospheric temperature/pressure model based on the International Standard Atmosphere + * (ISA). The no-argument constructor creates an {@link AtmosphericModel} that corresponds + * to the ISA model. It is extended by the other constructors to allow defining a custom + * first layer. The base temperature and pressure are as given, and all other values + * are calculated based on these. + *

+ * TODO: LOW: Values at altitudes over 32km differ from standard results by ~5%. + * + * @author Sampo Niskanen + */ +public class ExtendedISAModel extends AtmosphericModel { + + public static final double STANDARD_TEMPERATURE = 288.15; + public static final double STANDARD_PRESSURE = 101325; + + private static final double G = 9.80665; + + private final double[] layer = {0, 11000, 20000, 32000, 47000, 51000, 71000, 84852}; + private final double[] baseTemperature = { + 288.15, 216.65, 216.65, 228.65, 270.65, 270.65, 214.65, 186.95 + }; + private final double[] basePressure = new double[layer.length]; + + + /** + * Construct the standard ISA model. + */ + public ExtendedISAModel() { + this(STANDARD_TEMPERATURE, STANDARD_PRESSURE); + } + + /** + * Construct an extended model with the given temperature and pressure at MSL. + * + * @param temperature the temperature at MSL. + * @param pressure the pressure at MSL. + */ + public ExtendedISAModel(double temperature, double pressure) { + this(0, temperature, pressure); + } + + + /** + * Construct an extended model with the given temperature and pressure at the + * specified altitude. Conditions below the given altitude cannot be calculated, + * and the values at the specified altitude will be returned instead. The altitude + * must be lower than the altitude of the next ISA standard layer (below 11km). + * + * @param altitude the altitude of the measurements. + * @param temperature the temperature. + * @param pressure the pressure. + * @throws IllegalArgumentException if the altitude exceeds the second layer boundary + * of the ISA model (over 11km). + */ + public ExtendedISAModel(double altitude, double temperature, double pressure) { + if (altitude >= layer[1]) { + throw new IllegalArgumentException("Too high first altitude: "+altitude); + } + + layer[0] = altitude; + baseTemperature[0] = temperature; + basePressure[0] = pressure; + + for (int i=1; i < basePressure.length; i++) { + basePressure[i] = getExactConditions(layer[i]-1).pressure; + } + } + + + @Override + public AtmosphericConditions getExactConditions(double altitude) { + altitude = MathUtil.clamp(altitude, layer[0], layer[layer.length-1]); + int n; + for (n=0; n < layer.length-1; n++) { + if (layer[n+1] > altitude) + break; + } + + double rate = (baseTemperature[n+1] - baseTemperature[n]) / (layer[n+1] - layer[n]); + + double t = baseTemperature[n] + (altitude - layer[n]) * rate; + double p; + if (Math.abs(rate) > 0.001) { + p = basePressure[n] * + Math.pow(1 + (altitude-layer[n])*rate/baseTemperature[n], -G/(rate*R)); + } else { + p = basePressure[n] * + Math.exp(-(altitude-layer[n])*G/(R*baseTemperature[n])); + } + + return new AtmosphericConditions(t,p); + } + + @Override + public double getMaxAltitude() { + return layer[layer.length-1]; + } + + + public static void main(String foo[]) { + ExtendedISAModel model1 = new ExtendedISAModel(); + ExtendedISAModel model2 = new ExtendedISAModel(278.15,100000); + + for (double alt=0; alt < 80000; alt += 500) { + AtmosphericConditions cond1 = model1.getConditions(alt); + AtmosphericConditions cond2 = model2.getConditions(alt); + + AtmosphericConditions diff = new AtmosphericConditions(); + diff.pressure = (cond2.pressure - cond1.pressure)/cond1.pressure*100; + diff.temperature = (cond2.temperature - cond1.temperature)/cond1.temperature*100; + System.out.println("alt=" + alt + + ": std:" + cond1 + " mod:" + cond2 + " diff:" + diff); + } + } + +} diff --git a/src/net/sf/openrocket/aerodynamics/FlightConditions.java b/src/net/sf/openrocket/aerodynamics/FlightConditions.java new file mode 100644 index 000000000..58d964e19 --- /dev/null +++ b/src/net/sf/openrocket/aerodynamics/FlightConditions.java @@ -0,0 +1,395 @@ +package net.sf.openrocket.aerodynamics; + +import java.util.ArrayList; +import java.util.List; + +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import net.sf.openrocket.rocketcomponent.Configuration; +import net.sf.openrocket.util.ChangeSource; +import net.sf.openrocket.util.MathUtil; + + +public class FlightConditions implements Cloneable, ChangeSource { + + private List listenerList = new ArrayList(); + private ChangeEvent event = new ChangeEvent(this); + + /** Modification count */ + private int modCount = 0; + + /** Reference length used in calculations. */ + private double refLength = 1.0; + + /** Reference area used in calculations. */ + private double refArea = Math.PI * 0.25; + + + /** Angle of attack. */ + private double aoa = 0; + + /** Sine of the angle of attack. */ + private double sinAOA = 0; + + /** + * The fraction sin(aoa) / aoa. At an AOA of zero this value + * must be one. This value may be used in many cases to avoid checking for + * division by zero. + */ + private double sincAOA = 1.0; + + /** Lateral wind direction. */ + private double theta = 0; + + /** Current Mach speed. */ + private double mach = 0.3; + + /** + * Sqrt(1 - M^2) for M<1 + * Sqrt(M^2 - 1) for M>1 + */ + private double beta = Math.sqrt(1 - mach*mach); + + + /** Current roll rate. */ + private double rollRate = 0; + + private double pitchRate = 0; + private double yawRate = 0; + + + private AtmosphericConditions atmosphericConditions = new AtmosphericConditions(); + + + + /** + * Sole constructor. The reference length is initialized to the reference length + * of the Configuration, and the reference area accordingly. + * If config is null, then the reference length is set + * to 1 meter. + * + * @param config the configuration of which the reference length is taken. + */ + public FlightConditions(Configuration config) { + if (config != null) + setRefLength(config.getReferenceLength()); + } + + + /** + * Set the reference length from the given configuration. + * @param config the configuration from which to get the reference length. + */ + public void setReference(Configuration config) { + setRefLength(config.getReferenceLength()); + } + + + /** + * Set the reference length and area. + */ + public void setRefLength(double length) { + refLength = length; + + refArea = Math.PI * MathUtil.pow2(length/2); + fireChangeEvent(); + } + + /** + * Return the reference length. + */ + public double getRefLength() { + return refLength; + } + + /** + * Set the reference area and length. + */ + public void setRefArea(double area) { + refArea = area; + refLength = Math.sqrt(area / Math.PI)*2; + fireChangeEvent(); + } + + /** + * Return the reference area. + */ + public double getRefArea() { + return refArea; + } + + + /** + * Sets the angle of attack. It calculates values also for the methods + * {@link #getSinAOA()} and {@link #getSincAOA()}. + * + * @param aoa the angle of attack. + */ + public void setAOA(double aoa) { + aoa = MathUtil.clamp(aoa, 0, Math.PI); + if (MathUtil.equals(this.aoa, aoa)) + return; + + this.aoa = aoa; + if (aoa < 0.001) { + this.sinAOA = aoa; + this.sincAOA = 1.0; + } else { + this.sinAOA = Math.sin(aoa); + this.sincAOA = sinAOA / aoa; + } + fireChangeEvent(); + } + + + /** + * Sets the angle of attack with the sine. The value sinAOA is assumed + * to be the sine of aoa for cases in which this value is known. + * The AOA must still be specified, as the sine is not unique in the range + * of 0..180 degrees. + * + * @param aoa the angle of attack in radians. + * @param sinAOA the sine of the angle of attack. + */ + public void setAOA(double aoa, double sinAOA) { + aoa = MathUtil.clamp(aoa, 0, Math.PI); + sinAOA = MathUtil.clamp(sinAOA, 0, 1); + if (MathUtil.equals(this.aoa, aoa)) + return; + + assert(Math.abs(Math.sin(aoa) - sinAOA) < 0.0001) : + "Illegal sine: aoa="+aoa+" sinAOA="+sinAOA; + + this.aoa = aoa; + this.sinAOA = sinAOA; + if (aoa < 0.001) { + this.sincAOA = 1.0; + } else { + this.sincAOA = sinAOA / aoa; + } + fireChangeEvent(); + } + + + /** + * Return the angle of attack. + */ + public double getAOA() { + return aoa; + } + + /** + * Return the sine of the angle of attack. + */ + public double getSinAOA() { + return sinAOA; + } + + /** + * Return the sinc of the angle of attack (sin(AOA) / AOA). This method returns + * one if the angle of attack is zero. + */ + public double getSincAOA() { + return sincAOA; + } + + + /** + * Set the direction of the lateral airflow. + */ + public void setTheta(double theta) { + if (MathUtil.equals(this.theta, theta)) + return; + this.theta = theta; + fireChangeEvent(); + } + + /** + * Return the direction of the lateral airflow. + */ + public double getTheta() { + return theta; + } + + + /** + * Set the current Mach speed. This should be (but is not required to be) in + * reference to the speed of sound of the atmospheric conditions. + */ + public void setMach(double mach) { + mach = Math.max(mach, 0); + if (MathUtil.equals(this.mach, mach)) + return; + + this.mach = mach; + if (mach < 1) + this.beta = Math.sqrt(1 - mach*mach); + else + this.beta = Math.sqrt(mach*mach - 1); + fireChangeEvent(); + } + + /** + * Return the current Mach speed. + */ + public double getMach() { + return mach; + } + + /** + * Returns the current rocket velocity, calculated from the Mach number and the + * speed of sound. If either of these parameters are changed, the velocity changes + * as well. + * + * @return the velocity of the rocket. + */ + public double getVelocity() { + return mach * atmosphericConditions.getMachSpeed(); + } + + /** + * Sets the Mach speed according to the given velocity and the current speed of sound. + * + * @param velocity the current velocity. + */ + public void setVelocity(double velocity) { + setMach(velocity / atmosphericConditions.getMachSpeed()); + } + + + /** + * Return sqrt(abs(1 - Mach^2)). This is calculated in the setting call and is + * therefore fast. + */ + public double getBeta() { + return beta; + } + + + /** + * Return the current roll rate. + */ + public double getRollRate() { + return rollRate; + } + + + /** + * Set the current roll rate. + */ + public void setRollRate(double rate) { + if (MathUtil.equals(this.rollRate, rate)) + return; + + this.rollRate = rate; + fireChangeEvent(); + } + + + public double getPitchRate() { + return pitchRate; + } + + + public void setPitchRate(double pitchRate) { + if (MathUtil.equals(this.pitchRate, pitchRate)) + return; + this.pitchRate = pitchRate; + fireChangeEvent(); + } + + + public double getYawRate() { + return yawRate; + } + + + public void setYawRate(double yawRate) { + if (MathUtil.equals(this.yawRate, yawRate)) + return; + this.yawRate = yawRate; + fireChangeEvent(); + } + + + /** + * Return the current atmospheric conditions. Note that this method returns a + * reference to the {@link AtmosphericConditions} object used by this object. + * Changes made to the object will modify the encapsulated object, but will NOT + * generate change events. + * + * @return the current atmospheric conditions. + */ + public AtmosphericConditions getAtmosphericConditions() { + return atmosphericConditions; + } + + /** + * Set the current atmospheric conditions. This method will fire a change event + * if a change occurs. + */ + public void setAtmosphericConditions(AtmosphericConditions cond) { + if (atmosphericConditions == cond) + return; + atmosphericConditions = cond; + fireChangeEvent(); + } + + + /** + * Retrieve the modification count of this object. Each time it is modified + * the modification count is increased by one. + * + * @return the number of times this object has been modified since instantiation. + */ + public int getModCount() { + return modCount; + } + + + @Override + public String toString() { + return String.format("FlightConditions[aoa=%.2f\u00b0,theta=%.2f\u00b0,"+ + "mach=%.2f,rollRate=%.2f]", + aoa*180/Math.PI, theta*180/Math.PI, mach, rollRate); + } + + + /** + * Return a copy of the flight conditions. The copy has no listeners. The + * atmospheric conditions is also cloned. + */ + @Override + public FlightConditions clone() { + try { + FlightConditions cond = (FlightConditions) super.clone(); + cond.listenerList = new ArrayList(); + cond.event = new ChangeEvent(cond); + cond.atmosphericConditions = atmosphericConditions.clone(); + return cond; + } catch (CloneNotSupportedException e) { + throw new RuntimeException("BUG: clone not supported!",e); + } + } + + + + @Override + public void addChangeListener(ChangeListener listener) { + listenerList.add(0,listener); + } + + @Override + public void removeChangeListener(ChangeListener listener) { + listenerList.remove(listener); + } + + protected void fireChangeEvent() { + modCount++; + ChangeListener[] listeners = listenerList.toArray(new ChangeListener[0]); + for (ChangeListener l: listeners) { + l.stateChanged(event); + } + } +} diff --git a/src/net/sf/openrocket/aerodynamics/GravityModel.java b/src/net/sf/openrocket/aerodynamics/GravityModel.java new file mode 100644 index 000000000..1bc10c66c --- /dev/null +++ b/src/net/sf/openrocket/aerodynamics/GravityModel.java @@ -0,0 +1,28 @@ +package net.sf.openrocket.aerodynamics; + +/** + * A gravity model based on the International Gravity Formula of 1967. The gravity + * value is computed when the object is constructed and later returned as a static + * value. + * + * @author Sampo Niskanen + */ +public class GravityModel { + + private final double g; + + /** + * Construct the static gravity model at the specific latitude (in degrees). + * @param latitude the latitude in degrees (-90 ... 90) + */ + public GravityModel(double latitude) { + double sin = Math.sin(latitude * Math.PI/180); + double sin2 = Math.sin(2 * latitude * Math.PI/180); + g = 9.780327 * (1 + 0.0053024 * sin - 0.0000058 * sin2); + } + + public double getGravity() { + return g; + } + +} diff --git a/src/net/sf/openrocket/aerodynamics/Warning.java b/src/net/sf/openrocket/aerodynamics/Warning.java new file mode 100644 index 000000000..7981d37a6 --- /dev/null +++ b/src/net/sf/openrocket/aerodynamics/Warning.java @@ -0,0 +1,154 @@ +package net.sf.openrocket.aerodynamics; + +import net.sf.openrocket.unit.UnitGroup; + +public abstract class Warning { + + + /** + * Return a Warning with the specific text. + */ + public static Warning fromString(String text) { + return new Warning.Other(text); + } + + + /** + * Return true if the other warning should replace + * this warning. The method should return true if the other + * warning indicates a "worse" condition than the current warning. + * + * @param other the warning to compare to + * @return whether this warning should be replaced + */ + public abstract boolean replaceBy(Warning other); + + + /** + * Two Warnings are by default considered equal if they are of + * the same class. Therefore only one instance of a particular warning type + * is stored in a {@link WarningSet}. Subclasses may override this method for + * more specific functionality. + */ + @Override + public boolean equals(Object o) { + return (o.getClass() == this.getClass()); + } + + /** + * A hashCode method compatible with the equals method. + */ + @Override + public int hashCode() { + return this.getClass().hashCode(); + } + + + + + ///////////// Specific warning classes ///////////// + + + /** + * A Warning indicating a large angle of attack was encountered. + * + * @author Sampo Niskanen + */ + public static class LargeAOA extends Warning { + private double aoa; + + /** + * Sole constructor. The argument is the AOA that caused this warning. + * + * @param aoa the angle of attack that caused this warning + */ + public LargeAOA(double aoa) { + this.aoa = aoa; + } + + @Override + public String toString() { + if (Double.isNaN(aoa)) + return "Large angle of attack encountered."; + return ("Large angle of attack encountered (" + + UnitGroup.UNITS_ANGLE.getDefaultUnit().toString(aoa) + ")."); + } + + @Override + public boolean replaceBy(Warning other) { + if (!(other instanceof LargeAOA)) + return false; + + LargeAOA o = (LargeAOA)other; + if (Double.isNaN(this.aoa)) // If this has value NaN then replace + return true; + return (o.aoa > this.aoa); + } + } + + + + /** + * An unspecified warning type. This warning type holds a String + * describing it. Two warnings of this type are considered equal if the strings + * are identical. + * + * @author Sampo Niskanen + */ + public static class Other extends Warning { + private String description; + + public Other(String description) { + this.description = description; + } + + @Override + public String toString() { + return description; + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof Other)) + return false; + + Other o = (Other)other; + return (o.description.equals(this.description)); + } + + @Override + public int hashCode() { + return description.hashCode(); + } + + @Override + public boolean replaceBy(Warning other) { + return false; + } + } + + + /** A Warning that the body diameter is discontinuous. */ + public static final Warning DISCONTINUITY = + new Other("Discontinuity in rocket body diameter."); + + /** A Warning that the fins are thick compared to the rocket body. */ + public static final Warning THICK_FIN = + new Other("Thick fins may not be modeled accurately."); + + /** A Warning that the fins have jagged edges. */ + public static final Warning JAGGED_EDGED_FIN = + new Other("Jagged-edged fin predictions may be inaccurate."); + + /** A Warning that simulation listeners have affected the simulation */ + public static final Warning LISTENERS_AFFECTED = + new Other("Listeners modified the flight simulation"); + + public static final Warning RECOVERY_DEPLOYMENT_WHILE_BURNING = + new Other("Recovery device opened while motor still burning."); + + + + public static final Warning FILE_INVALID_PARAMETER = + new Other("Invalid parameter encountered, ignoring."); +} diff --git a/src/net/sf/openrocket/aerodynamics/WarningSet.java b/src/net/sf/openrocket/aerodynamics/WarningSet.java new file mode 100644 index 000000000..32355bc32 --- /dev/null +++ b/src/net/sf/openrocket/aerodynamics/WarningSet.java @@ -0,0 +1,91 @@ +package net.sf.openrocket.aerodynamics; + +import java.util.AbstractSet; +import java.util.ArrayList; +import java.util.Iterator; + +/** + * A set that contains multiple Warnings. When adding a + * {@link Warning} to this set, the contents is checked for a warning of the + * same type. If one is found, then the warning left in the set is determined + * by the method {@link #Warning.replaceBy(Warning)}. + * + * @author Sampo Niskanen + */ +public class WarningSet extends AbstractSet implements Cloneable { + + private ArrayList warnings = new ArrayList(); + + + /** + * Add a Warning to the set. If a warning of the same type + * exists in the set, the warning that is left in the set is defined by the + * method {@link Warning#replaceBy(Warning)}. + */ + @Override + public boolean add(Warning w) { + int index = warnings.indexOf(w); + + if (index < 0) { + warnings.add(w); + return false; + } + + Warning old = warnings.get(index); + if (old.replaceBy(w)) { + warnings.set(index, w); + } + + return true; + } + + /** + * Add a Warning with the specified text to the set. The Warning object + * is created using the {@link Warning#fromString(String)} method. If a warning of the + * same type exists in the set, the warning that is left in the set is defined by the + * method {@link Warning#replaceBy(Warning)}. + * + * @param s the warning text. + */ + public boolean add(String s) { + return add(Warning.fromString(s)); + } + + + @Override + public Iterator iterator() { + return warnings.iterator(); + } + + @Override + public int size() { + return warnings.size(); + } + + @SuppressWarnings("unchecked") + @Override + public WarningSet clone() { + try { + + WarningSet newSet = (WarningSet) super.clone(); + newSet.warnings = (ArrayList) this.warnings.clone(); + return newSet; + + } catch (CloneNotSupportedException e) { + throw new RuntimeException("CloneNotSupportedException occurred, report bug!",e); + } + } + + + @Override + public String toString() { + String s = ""; + + for (Warning w: warnings) { + if (s.length() > 0) + s = s+","; + s += w.toString(); + } + return "WarningSet[" + s + "]"; + } +} diff --git a/src/net/sf/openrocket/aerodynamics/WindSimulator.java b/src/net/sf/openrocket/aerodynamics/WindSimulator.java new file mode 100644 index 000000000..5bf08ef99 --- /dev/null +++ b/src/net/sf/openrocket/aerodynamics/WindSimulator.java @@ -0,0 +1,183 @@ +package net.sf.openrocket.aerodynamics; + +import java.util.Random; + +import net.sf.openrocket.util.MathUtil; +import net.sf.openrocket.util.PinkNoise; + + +public class WindSimulator { + + /** Source for seed numbers. */ + private static final Random seedSource = new Random(); + + /** Pink noise alpha parameter. */ + private static final double ALPHA = 5.0/3.0; + + /** Number of poles to use in the pink noise IIR filter. */ + private static final int POLES = 2; + + /** The standard deviation of the generated pink noise with the specified number of poles. */ + private static final double STDDEV = 2.252; + + /** Time difference between random samples. */ + private static final double DELTA_T = 0.05; + + + private double average = 0; + private double standardDeviation = 0; + + private int seed; + + private PinkNoise randomSource = null; + private double time1; + private double value1, value2; + + + /** + * Construct a new wind simulator with a random starting seed value. + */ + public WindSimulator() { + synchronized(seedSource) { + seed = seedSource.nextInt(); + } + } + + + + /** + * Return the average wind speed. + * + * @return the average wind speed. + */ + public double getAverage() { + return average; + } + /** + * Set the average wind speed. This method will also modify the + * standard deviation such that the turbulence intensity remains constant. + * + * @param average the average wind speed to set + */ + public void setAverage(double average) { + double intensity = getTurbulenceIntensity(); + this.average = Math.max(average, 0); + setTurbulenceIntensity(intensity); + } + + + + /** + * Return the standard deviation from the average wind speed. + * + * @return the standard deviation of the wind speed + */ + public double getStandardDeviation() { + return standardDeviation; + } + + /** + * Set the standard deviation of the average wind speed. + * + * @param standardDeviation the standardDeviation to set + */ + public void setStandardDeviation(double standardDeviation) { + this.standardDeviation = Math.max(standardDeviation, 0); + } + + + /** + * Return the turbulence intensity (standard deviation / average). + * + * @return the turbulence intensity + */ + public double getTurbulenceIntensity() { + if (MathUtil.equals(average, 0)) { + if (MathUtil.equals(standardDeviation, 0)) + return 0; + else + return 1000; + } + return standardDeviation / average; + } + + /** + * Set the standard deviation to match the turbulence intensity. + * + * @param intensity the turbulence intensity + */ + public void setTurbulenceIntensity(double intensity) { + setStandardDeviation(intensity * average); + } + + + + + + public int getSeed() { + return seed; + } + + public void setSeed(int seed) { + if (this.seed == seed) + return; + this.seed = seed; + } + + + + public double getWindSpeed(double time) { + if (time < 0) { + throw new IllegalArgumentException("Requesting wind speed at t="+time); + } + + if (randomSource == null) { + randomSource = new PinkNoise(ALPHA, POLES, new Random(seed)); + time1 = 0; + value1 = randomSource.nextValue(); + value2 = randomSource.nextValue(); + } + + if (time < time1) { + reset(); + return getWindSpeed(time); + } + + while (time1 + DELTA_T < time) { + value1 = value2; + value2 = randomSource.nextValue(); + time1 += DELTA_T; + } + + double a = (time - time1)/DELTA_T; + + + return average + (value1 * (1-a) + value2 * a) * standardDeviation / STDDEV; + } + + + private void reset() { + randomSource = null; + } + + + public static void main(String[] str) { + + WindSimulator sim = new WindSimulator(); + + sim.setAverage(2); + sim.setStandardDeviation(0.5); + + for (int i=0; i < 10000; i++) { + double t = 0.01*i; + double v = sim.getWindSpeed(t); + System.out.printf("%d.%03d %d.%03d\n", (int)t,((int)(t*1000))%1000, (int)v, ((int)(v*1000))%1000); +// if ((i % 5) == 0) +// System.out.println(" ***"); +// else +// System.out.println(""); + } + + } + +} diff --git a/src/net/sf/openrocket/aerodynamics/barrowman/FinSetCalc.java b/src/net/sf/openrocket/aerodynamics/barrowman/FinSetCalc.java new file mode 100644 index 000000000..05144ef5d --- /dev/null +++ b/src/net/sf/openrocket/aerodynamics/barrowman/FinSetCalc.java @@ -0,0 +1,693 @@ +package net.sf.openrocket.aerodynamics.barrowman; + +import static java.lang.Math.pow; +import static java.lang.Math.sqrt; +import static net.sf.openrocket.util.MathUtil.pow2; + +import java.util.Arrays; +import java.util.Iterator; + +import net.sf.openrocket.aerodynamics.AerodynamicForces; +import net.sf.openrocket.aerodynamics.FlightConditions; +import net.sf.openrocket.aerodynamics.Warning; +import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.rocketcomponent.Configuration; +import net.sf.openrocket.rocketcomponent.FinSet; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.rocketcomponent.TrapezoidFinSet; +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.LinearInterpolator; +import net.sf.openrocket.util.MathUtil; +import net.sf.openrocket.util.PolyInterpolator; +import net.sf.openrocket.util.Test; + + +public class FinSetCalc extends RocketComponentCalc { + private static final double STALL_ANGLE = (20 * Math.PI/180); + + /** Number of divisions in the fin chords. */ + protected static final int DIVISIONS = 48; + + + + + private FinSet component; + + + protected double macLength = Double.NaN; // MAC length + protected double macLead = Double.NaN; // MAC leading edge position + protected double macSpan = Double.NaN; // MAC spanwise position + protected double finArea = Double.NaN; // Fin area + protected double ar = Double.NaN; // Fin aspect ratio + protected double span = Double.NaN; // Fin span + protected double cosGamma = Double.NaN; // Cosine of midchord sweep angle + protected double cosGammaLead = Double.NaN; // Cosine of leading edge sweep angle + protected double rollSum = Double.NaN; // Roll damping sum term + + protected double[] chordLead = new double[DIVISIONS]; + protected double[] chordTrail = new double[DIVISIONS]; + protected double[] chordLength = new double[DIVISIONS]; + + protected final WarningSet geometryWarnings = new WarningSet(); + + private double[] poly = new double[6]; + + + public FinSetCalc(RocketComponent component) { + super(component); + if (!(component instanceof FinSet)) { + throw new IllegalArgumentException("Illegal component type "+component); + } + this.component = (FinSet) component; + } + + + /* + * Calculates the non-axial forces produced by the fins (normal and side forces, + * pitch, yaw and roll moments, CP position, CNa). + */ + @Override + public void calculateNonaxialForces(FlightConditions conditions, + AerodynamicForces forces, WarningSet warnings) { + + // Compute and cache the fin geometry + if (Double.isNaN(macLength)) { + calculateFinGeometry(); + calculatePoly(); + } + + if (span < 0.001) { + forces.Cm = 0; + forces.CN = 0; + forces.CNa = 0; + forces.cp = Coordinate.NUL; + forces.Croll = 0; + forces.CrollDamp = 0; + forces.CrollForce = 0; + forces.Cside = 0; + forces.Cyaw = 0; + return; + } + + + // Add warnings (radius/2 == diameter/4) + if (component.getThickness() > component.getBodyRadius()/2) { + warnings.add(Warning.THICK_FIN); + } + warnings.addAll(geometryWarnings); + + + + //////// Calculate CNa. ///////// + + // One fin without interference (both sub- and supersonic): + double cna1 = calculateFinCNa1(conditions); + + + + // Multiple fins with fin-fin interference + double cna; + + // TODO: MEDIUM: Take into account multiple fin sets + int fins = component.getFinCount(); + double theta = conditions.getTheta(); + double angle = component.getBaseRotation(); + + switch (fins) { + case 1: + case 2: + // from geometry + double mul = 0; + for (int i=0; i < fins; i++) { + mul += MathUtil.pow2(Math.sin(theta - angle)); + angle += 2 * Math.PI / fins; + } + cna = cna1*mul; + break; + + case 3: + // multiplier 1.5, sinusoidal reduction of 15% + cna = cna1 * 1.5 * (1 - 0.15*pow2(Math.cos(1.5 * (theta-angle)))); + break; + + case 4: + // multiplier 2.0, sinusoidal reduction of 6% + cna = cna1 * 2.0 * (1 - 0.06*pow2(Math.sin(2 * (theta-angle)))); + break; + + case 5: + cna = 2.37 * cna1; + break; + + case 6: + cna = 2.74 * cna1; + break; + + case 7: + cna = 2.99 * cna1; + break; + + case 8: + cna = 3.24 * cna1; + break; + + default: + // Assume N/2 * 3/4 efficiency for more fins + cna = cna1 * fins * 3.0/8.0; + break; + } + + + // Body-fin interference effect + double r = component.getBodyRadius(); + double tau = r / (span+r); + if (Double.isNaN(tau) || Double.isInfinite(tau)) + tau = 0; + cna *= 1 + tau; // Classical Barrowman +// cna *= pow2(1 + tau); // Barrowman thesis (too optimistic??) + + + + // TODO: LOW: check for fin tip mach cone interference + // (Barrowman thesis pdf-page 40) + + // TODO: LOW: fin-fin mach cone effect, MIL-HDBK page 5-25 + + + + // Calculate CP position + double x = macLead + calculateCPPos(conditions) * macLength; + + + + // Calculate roll forces, reduce forcing above stall angle + + // Without body-fin interference effect: +// forces.CrollForce = fins * (macSpan+r) * cna1 * component.getCantAngle() / +// conditions.getRefLength(); + // With body-fin interference effect: + forces.CrollForce = fins * (macSpan+r) * cna1 * (1+tau) * component.getCantAngle() / + conditions.getRefLength(); + + + + + if (conditions.getAOA() > STALL_ANGLE) { +// System.out.println("Fin stalling in roll"); + forces.CrollForce *= MathUtil.clamp( + 1-(conditions.getAOA() - STALL_ANGLE)/(STALL_ANGLE/2), 0, 1); + } + forces.CrollDamp = calculateDampingMoment(conditions); + forces.Croll = forces.CrollForce - forces.CrollDamp; + + + +// System.out.printf(component.getName() + ": roll rate:%.3f force:%.3f damp:%.3f " + +// "total:%.3f\n", +// conditions.getRollRate(), forces.CrollForce, forces.CrollDamp, forces.Croll); + + forces.CNa = cna; + forces.CN = cna * MathUtil.min(conditions.getAOA(), STALL_ANGLE); + forces.cp = new Coordinate(x, 0, 0, cna); + forces.Cm = forces.CN * x / conditions.getRefLength(); + + if (fins == 1) { + forces.Cside = cna1 * Math.cos(theta-angle) * Math.sin(theta-angle); + forces.Cyaw = forces.Cside * x / conditions.getRefLength(); + } else { + forces.Cside = 0; + forces.Cyaw = 0; + } + + } + + + /** + * Returns the MAC length of the fin. This is required in the friction drag + * computation. + * + * @return the MAC length of the fin. + */ + public double getMACLength() { + // Compute and cache the fin geometry + if (Double.isNaN(macLength)) { + calculateFinGeometry(); + calculatePoly(); + } + + return macLength; + } + + public double getMidchordPos() { + // Compute and cache the fin geometry + if (Double.isNaN(macLength)) { + calculateFinGeometry(); + calculatePoly(); + } + + return macLead + 0.5 * macLength; + } + + + + /** + * Pre-calculates the fin geometry values. + */ + protected void calculateFinGeometry() { + + span = component.getSpan(); + finArea = component.getFinArea(); + ar = 2 * pow2(span) / finArea; + + Coordinate[] points = component.getFinPoints(); + + // Check for jagged edges + geometryWarnings.clear(); + boolean down = false; + for (int i=1; i < points.length; i++) { + if ((points[i].y > points[i-1].y + 0.001) && down) { + geometryWarnings.add(Warning.JAGGED_EDGED_FIN); + break; + } + if (points[i].y < points[i-1].y - 0.001) { + down = true; + } + } + + + // Calculate the chord lead and trail positions and length + + Arrays.fill(chordLead, Double.POSITIVE_INFINITY); + Arrays.fill(chordTrail, Double.NEGATIVE_INFINITY); + Arrays.fill(chordLength, 0); + + for (int point=1; point < points.length; point++) { + double x1 = points[point-1].x; + double y1 = points[point-1].y; + double x2 = points[point].x; + double y2 = points[point].y; + + if (MathUtil.equals(y1, y2)) + continue; + + int i1 = (int)(y1*1.0001/span*(DIVISIONS-1)); + int i2 = (int)(y2*1.0001/span*(DIVISIONS-1)); + i1 = MathUtil.clamp(i1, 0, DIVISIONS-1); + i2 = MathUtil.clamp(i2, 0, DIVISIONS-1); + if (i1 > i2) { + int tmp = i2; + i2 = i1; + i1 = tmp; + } + + for (int i = i1; i <= i2; i++) { + // Intersection point (x,y) + double y = i*span/(DIVISIONS-1); + double x = (y-y2)/(y1-y2)*x1 + (y1-y)/(y1-y2)*x2; + if (x < chordLead[i]) + chordLead[i] = x; + if (x > chordTrail[i]) + chordTrail[i] = x; + + // TODO: LOW: If fin point exactly on chord line, might be counted twice: + if (y1 < y2) { + chordLength[i] -= x; + } else { + chordLength[i] += x; + } + } + } + + // Check and correct any inconsistencies + for (int i=0; i < DIVISIONS; i++) { + if (Double.isInfinite(chordLead[i]) || Double.isInfinite(chordTrail[i]) || + Double.isNaN(chordLead[i]) || Double.isNaN(chordTrail[i])) { + chordLead[i] = 0; + chordTrail[i] = 0; + } + if (chordLength[i] < 0 || Double.isNaN(chordLength[i])) { + chordLength[i] = 0; + } + if (chordLength[i] > chordTrail[i] - chordLead[i]) { + chordLength[i] = chordTrail[i] - chordLead[i]; + } + } + + + /* Calculate fin properties: + * + * macLength // MAC length + * macLead // MAC leading edge position + * macSpan // MAC spanwise position + * ar // Fin aspect ratio (already set) + * span // Fin span (already set) + */ + macLength = 0; + macLead = 0; + macSpan = 0; + cosGamma = 0; + cosGammaLead = 0; + rollSum = 0; + double area = 0; + double radius = component.getBodyRadius(); + + final double dy = span/(DIVISIONS-1); + for (int i=0; i < DIVISIONS; i++) { + double length = chordTrail[i] - chordLead[i]; + double y = i*dy; + + macLength += length * length; + macSpan += y * length; + macLead += chordLead[i] * length; + area += length; + rollSum += chordLength[i] * pow2(radius + y); + + if (i>0) { + double dx = (chordTrail[i]+chordLead[i])/2 - (chordTrail[i-1]+chordLead[i-1])/2; + cosGamma += dy/MathUtil.hypot(dx, dy); + + dx = chordLead[i] - chordLead[i-1]; + cosGammaLead += dy/MathUtil.hypot(dx, dy); + } + } + + macLength *= dy; + macSpan *= dy; + macLead *= dy; + area *= dy; + rollSum *= dy; + + macLength /= area; + macSpan /= area; + macLead /= area; + cosGamma /= (DIVISIONS-1); + cosGammaLead /= (DIVISIONS-1); + } + + + /////////////// CNa1 calculation //////////////// + + private static final double CNA_SUBSONIC = 0.9; + private static final double CNA_SUPERSONIC = 1.5; + private static final double CNA_SUPERSONIC_B = pow(pow2(CNA_SUPERSONIC)-1, 1.5); + private static final double GAMMA = 1.4; + private static final LinearInterpolator K1, K2, K3; + private static final PolyInterpolator cnaInterpolator = new PolyInterpolator( + new double[] { CNA_SUBSONIC, CNA_SUPERSONIC }, + new double[] { CNA_SUBSONIC, CNA_SUPERSONIC }, + new double[] { CNA_SUBSONIC } + ); + /* Pre-calculate the values for K1, K2 and K3 */ + static { + // Up to Mach 5 + int n = (int)((5.0-CNA_SUPERSONIC)*10); + double[] x = new double[n]; + double[] k1 = new double[n]; + double[] k2 = new double[n]; + double[] k3 = new double[n]; + for (int i=0; i= CNA_SUPERSONIC) { + + double vel = conditions.getVelocity(); + double k1 = K1.getValue(mach); + double k2 = K2.getValue(mach); + double k3 = K3.getValue(mach); + + double sum = 0; + + for (int i=0; i < DIVISIONS; i++) { + double y = i*span/(DIVISIONS-1); + double angle = rollRate * (radius+y) / vel; + + sum += (k1 * angle + k2 * angle*angle + k3 * angle*angle*angle) + * chordLength[i] * (radius+y); + } + + return component.getFinCount() * sum * span/(DIVISIONS-1) / + (conditions.getRefArea() * conditions.getRefLength()); + } + + // Transonic, do linear interpolation + + FlightConditions cond = conditions.clone(); + cond.setMach(CNA_SUBSONIC - 0.01); + double subsonic = calculateDampingMoment(cond); + cond.setMach(CNA_SUPERSONIC + 0.01); + double supersonic = calculateDampingMoment(cond); + + return subsonic * (CNA_SUPERSONIC - mach)/(CNA_SUPERSONIC - CNA_SUBSONIC) + + supersonic * (mach - CNA_SUBSONIC)/(CNA_SUPERSONIC - CNA_SUBSONIC); + } + + + + + /** + * Return the relative position of the CP along the mean aerodynamic chord. + * Below mach 0.5 it is at the quarter chord, above mach 2 calculated using an + * empirical formula, between these two using an interpolation polynomial. + * + * @param cond Mach speed used + * @return CP position along the MAC + */ + private double calculateCPPos(FlightConditions cond) { + double m = cond.getMach(); + if (m <= 0.5) { + // At subsonic speeds CP at quarter chord + return 0.25; + } + if (m >= 2) { + // At supersonic speeds use empirical formula + double beta = cond.getBeta(); + return (ar * beta - 0.67) / (2*ar*beta - 1); + } + + // In between use interpolation polynomial + double x = 1.0; + double val = 0; + + for (int i=0; i < poly.length; i++) { + val += poly[i] * x; + x *= m; + } + + return val; + } + + /** + * Calculate CP position interpolation polynomial coefficients from the + * fin geometry. This is a fifth order polynomial that satisfies + * + * p(0.5)=0.25 + * p'(0.5)=0 + * p(2) = f(2) + * p'(2) = f'(2) + * p''(2) = 0 + * p'''(2) = 0 + * + * where f(M) = (ar*sqrt(M^2-1) - 0.67) / (2*ar*sqrt(M^2-1) - 1). + * + * The values were calculated analytically in Mathematica. The coefficients + * are used as poly[0] + poly[1]*x + poly[2]*x^2 + ... + */ + private void calculatePoly() { + double denom = pow2(1 - 3.4641*ar); // common denominator + + poly[5] = (-1.58025 * (-0.728769 + ar) * (-0.192105 + ar)) / denom; + poly[4] = (12.8395 * (-0.725688 + ar) * (-0.19292 + ar)) / denom; + poly[3] = (-39.5062 * (-0.72074 + ar) * (-0.194245 + ar)) / denom; + poly[2] = (55.3086 * (-0.711482 + ar) * (-0.196772 + ar)) / denom; + poly[1] = (-31.6049 * (-0.705375 + ar) * (-0.198476 + ar)) / denom; + poly[0] = (9.16049 * (-0.588838 + ar) * (-0.20624 + ar)) / denom; + } + + + @SuppressWarnings("null") + public static void main(String arg[]) { + Rocket rocket = Test.makeRocket(); + FinSet finset = null; + + Iterator iter = rocket.deepIterator(); + while (iter.hasNext()) { + RocketComponent c = iter.next(); + if (c instanceof FinSet) { + finset = (FinSet)c; + break; + } + } + + ((TrapezoidFinSet)finset).setHeight(0.10); + ((TrapezoidFinSet)finset).setRootChord(0.10); + ((TrapezoidFinSet)finset).setTipChord(0.10); + ((TrapezoidFinSet)finset).setSweep(0.0); + + + FinSetCalc calc = new FinSetCalc(finset); + + calc.calculateFinGeometry(); + FlightConditions cond = new FlightConditions(new Configuration(rocket)); + for (double m=0; m < 3; m+=0.05) { + cond.setMach(m); + cond.setAOA(0.0*Math.PI/180); + double cna = calc.calculateFinCNa1(cond); + System.out.printf("%5.2f "+cna+"\n", m); + } + + } + + + @Override + public double calculatePressureDragForce(FlightConditions conditions, + double stagnationCD, double baseCD, WarningSet warnings) { + + // Compute and cache the fin geometry + if (Double.isNaN(cosGammaLead)) { + calculateFinGeometry(); + calculatePoly(); + } + + + FinSet.CrossSection profile = component.getCrossSection(); + double mach = conditions.getMach(); + double drag = 0; + + // Pressure fore-drag + if (profile == FinSet.CrossSection.AIRFOIL || + profile == FinSet.CrossSection.ROUNDED) { + + // Round leading edge + if (mach < 0.9) { + drag = Math.pow(1 - pow2(mach), -0.417) - 1; + } else if (mach < 1) { + drag = 1 - 1.785 * (mach-0.9); + } else { + drag = 1.214 - 0.502/pow2(mach) + 0.1095/pow2(pow2(mach)); + } + + } else if (profile == FinSet.CrossSection.SQUARE) { + drag = stagnationCD; + } else { + throw new UnsupportedOperationException("Unsupported fin profile: "+profile); + } + + // Slanted leading edge + drag *= pow2(cosGammaLead); + + // Trailing edge drag + if (profile == FinSet.CrossSection.SQUARE) { + drag += baseCD; + } else if (profile == FinSet.CrossSection.ROUNDED) { + drag += baseCD/2; + } + // Airfoil assumed to have zero base drag + + + // Scale to correct reference area + drag *= component.getFinCount() * component.getSpan() * component.getThickness() / + conditions.getRefArea(); + + return drag; + } + +} diff --git a/src/net/sf/openrocket/aerodynamics/barrowman/LaunchLugCalc.java b/src/net/sf/openrocket/aerodynamics/barrowman/LaunchLugCalc.java new file mode 100644 index 000000000..88cf91ffe --- /dev/null +++ b/src/net/sf/openrocket/aerodynamics/barrowman/LaunchLugCalc.java @@ -0,0 +1,39 @@ +package net.sf.openrocket.aerodynamics.barrowman; + +import net.sf.openrocket.aerodynamics.AerodynamicForces; +import net.sf.openrocket.aerodynamics.FlightConditions; +import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.rocketcomponent.LaunchLug; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.util.MathUtil; + +public class LaunchLugCalc extends RocketComponentCalc { + + private double CDmul; + private double refArea; + + public LaunchLugCalc(RocketComponent component) { + super(component); + + LaunchLug lug = (LaunchLug)component; + double ld = lug.getLength() / (2*lug.getRadius()); + + CDmul = Math.max(1.3 - ld, 1); + refArea = Math.PI * MathUtil.pow2(lug.getRadius()) - + Math.PI * MathUtil.pow2(lug.getInnerRadius()) * Math.max(1 - ld, 0); + } + + @Override + public void calculateNonaxialForces(FlightConditions conditions, + AerodynamicForces forces, WarningSet warnings) { + // Nothing to be done + } + + @Override + public double calculatePressureDragForce(FlightConditions conditions, + double stagnationCD, double baseCD, WarningSet warnings) { + + return CDmul*stagnationCD * refArea / conditions.getRefArea(); + } + +} diff --git a/src/net/sf/openrocket/aerodynamics/barrowman/RocketComponentCalc.java b/src/net/sf/openrocket/aerodynamics/barrowman/RocketComponentCalc.java new file mode 100644 index 000000000..71f4ef0cf --- /dev/null +++ b/src/net/sf/openrocket/aerodynamics/barrowman/RocketComponentCalc.java @@ -0,0 +1,42 @@ +package net.sf.openrocket.aerodynamics.barrowman; + +import net.sf.openrocket.aerodynamics.AerodynamicForces; +import net.sf.openrocket.aerodynamics.FlightConditions; +import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.rocketcomponent.RocketComponent; + +public abstract class RocketComponentCalc { + + public RocketComponentCalc(RocketComponent component) { + + } + + /** + * Calculate the non-axial forces produced by the component (normal and side forces, + * pitch, yaw and roll moments and CP position). The values are stored in the + * AerodynamicForces object. Additionally the value of CNa is computed + * and stored if possible without large amount of extra calculation, otherwise + * NaN is stored. The CP coordinate is stored in local coordinates and moments are + * computed around the local origin. + * + * @param conditions the flight conditions. + * @param forces the object in which to store the values. + * @param warnings set in which to store possible warnings. + */ + public abstract void calculateNonaxialForces(FlightConditions conditions, + AerodynamicForces forces, WarningSet warnings); + + + /** + * Calculates the pressure drag of the component. This component does NOT include + * the effect of discontinuities in the rocket body. + * + * @param conditions the flight conditions. + * @param stagnationCD the current stagnation drag coefficient + * @param baseCD the current base drag coefficient + * @param warnings set in which to store possible warnings + * @return the pressure drag of the component + */ + public abstract double calculatePressureDragForce(FlightConditions conditions, + double stagnationCD, double baseCD, WarningSet warnings); +} diff --git a/src/net/sf/openrocket/aerodynamics/barrowman/SymmetricComponentCalc.java b/src/net/sf/openrocket/aerodynamics/barrowman/SymmetricComponentCalc.java new file mode 100644 index 000000000..3f0e4e793 --- /dev/null +++ b/src/net/sf/openrocket/aerodynamics/barrowman/SymmetricComponentCalc.java @@ -0,0 +1,443 @@ +package net.sf.openrocket.aerodynamics.barrowman; + +import static net.sf.openrocket.aerodynamics.AtmosphericConditions.GAMMA; +import static net.sf.openrocket.util.MathUtil.pow2; +import net.sf.openrocket.aerodynamics.AerodynamicForces; +import net.sf.openrocket.aerodynamics.BarrowmanCalculator; +import net.sf.openrocket.aerodynamics.FlightConditions; +import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.rocketcomponent.BodyTube; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.rocketcomponent.SymmetricComponent; +import net.sf.openrocket.rocketcomponent.Transition; +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.LinearInterpolator; +import net.sf.openrocket.util.MathUtil; +import net.sf.openrocket.util.PolyInterpolator; + + + +/** + * Calculates the aerodynamic properties of a SymmetricComponent. + *

+ * CP and CNa are calculated by the Barrowman method extended to account for body lift + * by the method presented by Galejs. Supersonic CNa and CP are assumed to be the + * same as the subsonic values. + * + * + * @author Sampo Niskanen + */ +public class SymmetricComponentCalc extends RocketComponentCalc { + + public static final double BODY_LIFT_K = 1.1; + + private final SymmetricComponent component; + + private final double length; + private final double r1, r2; + private final double fineness; + private final Transition.Shape shape; + private final double param; + private final double area; + + public SymmetricComponentCalc(RocketComponent c) { + super(c); + if (!(c instanceof SymmetricComponent)) { + throw new IllegalArgumentException("Illegal component type "+c); + } + this.component = (SymmetricComponent) c; + + + length = component.getLength(); + r1 = component.getForeRadius(); + r2 = component.getAftRadius(); + + fineness = length / (2*Math.abs(r2-r1)); + + if (component instanceof BodyTube) { + shape = null; + param = 0; + area = 0; + } else if (component instanceof Transition) { + shape = ((Transition)component).getType(); + param = ((Transition)component).getShapeParameter(); + area = Math.abs(Math.PI * (r1*r1 - r2*r2)); + } else { + throw new UnsupportedOperationException("Unknown component type " + + component.getComponentName()); + } + } + + + private boolean isTube = false; + private double cnaCache = Double.NaN; + private double cpCache = Double.NaN; + + + /** + * Calculates the non-axial forces produced by the fins (normal and side forces, + * pitch, yaw and roll moments, CP position, CNa). + *

+ * This method uses the Barrowman method for CP and CNa calculation and the + * extension presented by Galejs for the effect of body lift. + *

+ * The CP and CNa at supersonic speeds are assumed to be the same as those at + * subsonic speeds. + */ + @Override + public void calculateNonaxialForces(FlightConditions conditions, + AerodynamicForces forces, WarningSet warnings) { + + // Pre-calculate and store the results + if (Double.isNaN(cnaCache)) { + final double r0 = component.getForeRadius(); + final double r1 = component.getAftRadius(); + + if (MathUtil.equals(r0, r1)) { + isTube = true; + cnaCache = 0; + } else { + isTube = false; + + final double A0 = Math.PI * pow2(r0); + final double A1 = Math.PI * pow2(r1); + + cnaCache = 2 * (A1 - A0); + System.out.println("cnaCache = "+cnaCache); + cpCache = (component.getLength() * A1 - component.getFullVolume()) / (A1 - A0); + } + } + + Coordinate cp; + + // If fore == aft, only body lift is encountered + if (isTube) { + cp = getLiftCP(conditions, warnings); + } else { + cp = new Coordinate(cpCache,0,0,cnaCache * conditions.getSincAOA() / + conditions.getRefArea()).average(getLiftCP(conditions,warnings)); + } + + forces.cp = cp; + forces.CNa = cp.weight; + forces.CN = forces.CNa * conditions.getAOA(); + forces.Cm = forces.CN * cp.x / conditions.getRefLength(); + forces.Croll = 0; + forces.CrollDamp = 0; + forces.CrollForce = 0; + forces.Cside = 0; + forces.Cyaw = 0; + + + // Add warning on supersonic flight + if (conditions.getMach() > 1.1) { + warnings.add("Body calculations may not be entirely accurate at supersonic speeds."); + } + + } + + + + /** + * Calculate the body lift effect according to Galejs. + */ + protected Coordinate getLiftCP(FlightConditions conditions, WarningSet warnings) { + double area = component.getComponentPlanformArea(); + double center = component.getComponentPlanformCenter(); + + /* + * Without this extra multiplier the rocket may become unstable at apogee + * when turning around, and begin oscillating horizontally. During the flight + * of the rocket this has no effect. It is effective only when AOA > 45 deg + * and the velocity is less than 15 m/s. + */ + double mul = 1; + if ((conditions.getMach() < 0.05) && (conditions.getAOA() > Math.PI/4)) { + mul = pow2(conditions.getMach() / 0.05); + } + + return new Coordinate(center, 0, 0, mul*BODY_LIFT_K * area/conditions.getRefArea() * + conditions.getSinAOA() * conditions.getSincAOA()); // sin(aoa)^2 / aoa + } + + + + private LinearInterpolator interpolator = null; + + @Override + public double calculatePressureDragForce(FlightConditions conditions, + double stagnationCD, double baseCD, WarningSet warnings) { + + if (component instanceof BodyTube) + return 0; + + if (!(component instanceof Transition)) { + throw new RuntimeException("Pressure calculation of unknown type: "+ + component.getComponentName()); + } + + // Check for simple cases first + if (r1 == r2) + return 0; + + if (length < 0.001) { + if (r1 < r2) { + return stagnationCD * area / conditions.getRefArea(); + } else { + return baseCD * area / conditions.getRefArea(); + } + } + + + // Boattail drag computed directly from base drag + if (r2 < r1) { + if (fineness >= 3) + return 0; + double cd = baseCD * area / conditions.getRefArea(); + if (fineness <= 1) + return cd; + return cd * (3-fineness)/2; + } + + + assert(r1 < r2); // Tube and boattail have been checked already + + + // All nose cones and shoulders from pre-calculated and interpolating + if (interpolator == null) { + calculateNoseInterpolator(); + } + + return interpolator.getValue(conditions.getMach()) * area / conditions.getRefArea(); + } + + + + /* + * Experimental values of pressure drag for different nose cone shapes with a fineness + * ratio of 3. The data is taken from 'Collection of Zero-Lift Drag Data on Bodies + * of Revolution from Free-Flight Investigations', NASA TR-R-100, NTRS 19630004995, + * page 16. + * + * This data is extrapolated for other fineness ratios. + */ + + private static final LinearInterpolator ellipsoidInterpolator = new LinearInterpolator( + new double[] { 1.2, 1.25, 1.3, 1.4, 1.6, 2.0, 2.4 }, + new double[] {0.110, 0.128, 0.140, 0.148, 0.152, 0.159, 0.162 /* constant */ } + ); + private static final LinearInterpolator x14Interpolator = new LinearInterpolator( + new double[] { 1.2, 1.3, 1.4, 1.6, 1.8, 2.2, 2.6, 3.0, 3.6}, + new double[] {0.140, 0.156, 0.169, 0.192, 0.206, 0.227, 0.241, 0.249, 0.252} + ); + private static final LinearInterpolator x12Interpolator = new LinearInterpolator( + new double[] {0.925, 0.95, 1.0, 1.05, 1.1, 1.2, 1.3, 1.7, 2.0}, + new double[] { 0, 0.014, 0.050, 0.060, 0.059, 0.081, 0.084, 0.085, 0.078} + ); + private static final LinearInterpolator x34Interpolator = new LinearInterpolator( + new double[] { 0.8, 0.9, 1.0, 1.06, 1.2, 1.4, 1.6, 2.0, 2.8, 3.4}, + new double[] { 0, 0.015, 0.078, 0.121, 0.110, 0.098, 0.090, 0.084, 0.078, 0.074} + ); + private static final LinearInterpolator vonKarmanInterpolator = new LinearInterpolator( + new double[] { 0.9, 0.95, 1.0, 1.05, 1.1, 1.2, 1.4, 1.6, 2.0, 3.0}, + new double[] { 0, 0.010, 0.027, 0.055, 0.070, 0.081, 0.095, 0.097, 0.091, 0.083} + ); + private static final LinearInterpolator lvHaackInterpolator = new LinearInterpolator( + new double[] { 0.9, 0.95, 1.0, 1.05, 1.1, 1.2, 1.4, 1.6, 2.0 }, + new double[] { 0, 0.010, 0.024, 0.066, 0.084, 0.100, 0.114, 0.117, 0.113 } + ); + private static final LinearInterpolator parabolicInterpolator = new LinearInterpolator( + new double[] {0.95, 0.975, 1.0, 1.05, 1.1, 1.2, 1.4, 1.7}, + new double[] { 0, 0.016, 0.041, 0.092, 0.109, 0.119, 0.113, 0.108} + ); + private static final LinearInterpolator parabolic12Interpolator = new LinearInterpolator( + new double[] { 0.8, 0.9, 0.95, 1.0, 1.05, 1.1, 1.3, 1.5, 1.8}, + new double[] { 0, 0.016, 0.042, 0.100, 0.126, 0.125, 0.100, 0.090, 0.088} + ); + private static final LinearInterpolator parabolic34Interpolator = new LinearInterpolator( + new double[] { 0.9, 0.95, 1.0, 1.05, 1.1, 1.2, 1.4, 1.7}, + new double[] { 0, 0.023, 0.073, 0.098, 0.107, 0.106, 0.089, 0.082} + ); + private static final LinearInterpolator bluntInterpolator = new LinearInterpolator(); + static { + for (double m=0; m<3; m+=0.05) + bluntInterpolator.addPoint(m, BarrowmanCalculator.calculateStagnationCD(m)); + } + + /** + * Calculate the LinearInterpolator 'interpolator'. After this call, if can be used + * to get the pressure drag coefficient at any Mach number. + * + * First, the transonic/supersonic region is computed. For conical and ogive shapes + * this is calculated directly. For other shapes, the values for fineness-ratio 3 + * transitions are taken from the experimental values stored above (for parameterized + * shapes the values are interpolated between the parameter values). These are then + * extrapolated to the current fineness ratio. + * + * Finally, if the first data points in the interpolator are not zero, the subsonic + * region is interpolated in the form Cd = a*M^b + Cd(M=0). + */ + @SuppressWarnings("null") + private void calculateNoseInterpolator() { + LinearInterpolator int1=null, int2=null; + double p = 0; + + interpolator = new LinearInterpolator(); + + double r = component.getRadius(0.99*length); + double sinphi = (r2-r)/MathUtil.hypot(r2-r, 0.01*length); + + /* + * Take into account nose cone shape. Conical and ogive generate the interpolator + * directly. Others store a interpolator for fineness ratio 3 into int1, or + * for parameterized shapes store the bounding fineness ratio 3 interpolators into + * int1 and int2 and set 0 <= p <= 1 according to the bounds. + */ + switch (shape) { + case CONICAL: + interpolator = calculateOgiveNoseInterpolator(0, sinphi); // param==0 -> conical + break; + + case OGIVE: + interpolator = calculateOgiveNoseInterpolator(param, sinphi); + break; + + case ELLIPSOID: + int1 = ellipsoidInterpolator; + break; + + case POWER: + if (param <= 0.25) { + int1 = bluntInterpolator; + int2 = x14Interpolator; + p = param*4; + } else if (param <= 0.5) { + int1 = x14Interpolator; + int2 = x12Interpolator; + p = (param-0.25)*4; + } else if (param <= 0.75) { + int1 = x12Interpolator; + int2 = x34Interpolator; + p = (param-0.5)*4; + } else { + int1 = x34Interpolator; + int2 = calculateOgiveNoseInterpolator(0, 1/Math.sqrt(1+4*pow2(fineness))); + p = (param-0.75)*4; + } + break; + + case PARABOLIC: + if (param <= 0.5) { + int1 = calculateOgiveNoseInterpolator(0, 1/Math.sqrt(1+4*pow2(fineness))); + int2 = parabolic12Interpolator; + p = param*2; + } else if (param <= 0.75) { + int1 = parabolic12Interpolator; + int2 = parabolic34Interpolator; + p = (param-0.5)*4; + } else { + int1 = parabolic34Interpolator; + int2 = parabolicInterpolator; + p = (param-0.75)*4; + } + break; + + case HAACK: + int1 = vonKarmanInterpolator; + int2 = lvHaackInterpolator; + p = param*3; + break; + + default: + throw new UnsupportedOperationException("Unknown transition shape: "+shape); + } + + assert(p >= 0); + assert(p <= 1.001); + + + // Check for parameterized shape and interpolate if necessary + if (int2 != null) { + LinearInterpolator int3 = new LinearInterpolator(); + for (double m: int1.getXPoints()) { + int3.addPoint(m, p*int2.getValue(m) + (1-p)*int1.getValue(m)); + } + for (double m: int2.getXPoints()) { + int3.addPoint(m, p*int2.getValue(m) + (1-p)*int1.getValue(m)); + } + int1 = int3; + } + + // Extrapolate for fineness ratio if necessary + if (int1 != null) { + double log4 = Math.log(fineness+1) / Math.log(4); + for (double m: int1.getXPoints()) { + double stag = bluntInterpolator.getValue(m); + interpolator.addPoint(m, stag*Math.pow(int1.getValue(m)/stag, log4)); + } + } + + + /* + * Now the transonic/supersonic region is ok. We still need to interpolate + * the subsonic region, if the values are non-zero. + */ + + double min = interpolator.getXPoints()[0]; + double minValue = interpolator.getValue(min); + if (minValue < 0.001) { + // No interpolation necessary + return; + } + + double cdMach0 = 0.8 * pow2(sinphi); + double minDeriv = (interpolator.getValue(min+0.01) - minValue)/0.01; + + // These should not occur, but might cause havoc for the interpolation + if ((cdMach0 >= minValue-0.01) || (minDeriv <= 0.01)) { + return; + } + + // Cd = a*M^b + cdMach0 + double a = minValue - cdMach0; + double b = minDeriv / a; + + for (double m=0; m < minValue; m+= 0.05) { + interpolator.addPoint(m, a*Math.pow(m, b) + cdMach0); + } + } + + + private static final PolyInterpolator conicalPolyInterpolator = + new PolyInterpolator(new double[] {1.0, 1.3}, new double[] {1.0, 1.3}); + + private static LinearInterpolator calculateOgiveNoseInterpolator(double param, + double sinphi) { + LinearInterpolator interpolator = new LinearInterpolator(); + + // In the range M = 1 ... 1.3 use polynomial approximation + double cdMach1 = 2.1*pow2(sinphi) + 0.6019*sinphi; + + double[] poly = conicalPolyInterpolator.interpolator( + 1.0*sinphi, cdMach1, + 4/(GAMMA+1) * (1 - 0.5*cdMach1), -1.1341*sinphi + ); + + // Shape parameter multiplier + double mul = 0.72 * pow2(param-0.5) + 0.82; + + for (double m = 1; m < 1.3001; m += 0.02) { + interpolator.addPoint(m, mul * PolyInterpolator.eval(m, poly)); + } + + // Above M = 1.3 use direct formula + for (double m = 1.32; m < 4; m += 0.02) { + interpolator.addPoint(m, mul * (2.1*pow2(sinphi) + 0.5*sinphi/Math.sqrt(m*m - 1))); + } + + return interpolator; + } + + + +} diff --git a/src/net/sf/openrocket/database/Database.java b/src/net/sf/openrocket/database/Database.java new file mode 100644 index 000000000..b7986cb7f --- /dev/null +++ b/src/net/sf/openrocket/database/Database.java @@ -0,0 +1,270 @@ +package net.sf.openrocket.database; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.security.CodeSource; +import java.util.AbstractSet; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.EventListenerList; + +import net.sf.openrocket.file.Loader; +import net.sf.openrocket.util.ChangeSource; + + + +/** + * A database set. This class functions as a Set that contains items + * of a specific type. Additionally, the items can be accessed via an index number. + * The elements are always kept in their natural order. + * + * @author Sampo Niskanen + */ + +// TODO: HIGH: Database saving +public class Database> extends AbstractSet implements ChangeSource { + + private final List list = new ArrayList(); + private final EventListenerList listenerList = new EventListenerList(); + private final Loader loader; + + + public Database() { + loader = null; + } + + public Database(Loader loader) { + this.loader = loader; + } + + + @Override + public Iterator iterator() { + return new DBIterator(); + } + + @Override + public int size() { + return list.size(); + } + + @Override + public boolean add(T element) { + int index; + + index = Collections.binarySearch(list, element); + if (index >= 0) { + // List might contain the element + if (list.contains(element)) { + return false; + } + } else { + index = -(index+1); + } + list.add(index,element); + fireChangeEvent(); + return true; + } + + + /** + * Get the element with the specified index. + * @param index the index to retrieve. + * @return the element at the index. + */ + public T get(int index) { + return list.get(index); + } + + /** + * Return the index of the given Motor, or -1 if not in the database. + * + * @param m the motor + * @return the index of the motor + */ + public int indexOf(T m) { + return list.indexOf(m); + } + + + @Override + public void addChangeListener(ChangeListener listener) { + listenerList .add(ChangeListener.class, listener); + } + + + @Override + public void removeChangeListener(ChangeListener listener) { + listenerList .remove(ChangeListener.class, listener); + } + + + protected void fireChangeEvent() { + Object[] listeners = listenerList.getListenerList(); + ChangeEvent e = null; + for (int i = listeners.length-2; i>=0; i-=2) { + if (listeners[i]==ChangeListener.class) { + // Lazily create the event: + if (e == null) + e = new ChangeEvent(this); + ((ChangeListener)listeners[i+1]).stateChanged(e); + } + } + } + + + + //////// Directory loading + + + + /** + * Load all files in a directory to the motor database. Only files with file + * names matching the given pattern (as matched by String.matches(String)) + * are processed. + * + * @param dir the directory to read. + * @param pattern the pattern to match the file names to. + * @throws IOException if an IO error occurs when reading the JAR archive + * (errors reading individual files are printed to stderr). + */ + public void loadDirectory(File dir, final String pattern) throws IOException { + if (loader == null) { + throw new IllegalStateException("no file loader set"); + } + + File[] files = dir.listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return name.matches(pattern); + } + }); + if (files == null) { + throw new IOException("not a directory: "+dir); + } + for (File file: files) { + try { + this.addAll(loader.load(new FileInputStream(file), file.getName())); + } catch (IOException e) { + System.err.println("Error loading file "+file+": " + e.getMessage()); + } + } + } + + + /** + * Read all files in a directory contained in the JAR file that this class belongs to. + * Only files whose names match the given pattern (as matched by + * String.matches(String)) will be read. + * + * @param dir the directory within the JAR archive to read. + * @param pattern the pattern to match the file names to. + * @throws IOException if an IO error occurs when reading the JAR archive + * (errors reading individual files are printed to stderr). + */ + public void loadJarDirectory(String dir, String pattern) throws IOException { + + // Process directory and extension + if (!dir.endsWith("/")) { + dir += "/"; + } + + // Find the jar file this class is contained in and open it + URL jarUrl = null; + CodeSource codeSource = Database.class.getProtectionDomain().getCodeSource(); + if (codeSource != null) + jarUrl = codeSource.getLocation(); + + if (jarUrl == null) { + throw new IOException("Could not find containing JAR file."); + } + File file = urlToFile(jarUrl); + JarFile jarFile = new JarFile(file); + + try { + + // Loop through JAR entries searching for files to load + Enumeration entries = jarFile.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String name = entry.getName(); + if (name.startsWith(dir) && name.matches(pattern)) { + try { + InputStream stream = jarFile.getInputStream(entry); + this.addAll(loader.load(stream, name)); + } catch (IOException e) { + System.err.println("Error loading file " + file + ": " + + e.getMessage()); + } + } + } + + } finally { + jarFile.close(); + } + } + + + static File urlToFile(URL url) { + URI uri; + try { + uri = url.toURI(); + } catch (URISyntaxException e) { + try { + uri = new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), + url.getPath(), url.getQuery(), url.getRef()); + } catch (URISyntaxException e1) { + throw new IllegalArgumentException("Broken URL: " + url); + } + } + return new File(uri); + } + + + + public void load(File file) throws IOException { + if (loader == null) { + throw new IllegalStateException("no file loader set"); + } + this.addAll(loader.load(new FileInputStream(file), file.getName())); + } + + + + /** + * Iterator class implementation that fires changes if remove() is called. + */ + private class DBIterator implements Iterator { + private Iterator iterator = list.iterator(); + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public T next() { + return iterator.next(); + } + + @Override + public void remove() { + iterator.remove(); + fireChangeEvent(); + } + } +} diff --git a/src/net/sf/openrocket/database/Databases.java b/src/net/sf/openrocket/database/Databases.java new file mode 100644 index 000000000..4efb545b4 --- /dev/null +++ b/src/net/sf/openrocket/database/Databases.java @@ -0,0 +1,205 @@ +package net.sf.openrocket.database; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; + +import net.sf.openrocket.file.MotorLoader; +import net.sf.openrocket.material.Material; +import net.sf.openrocket.rocketcomponent.Motor; +import net.sf.openrocket.util.MathUtil; + + +/** + * A class that contains single instances of {@link Database} for specific purposes. + * + * @author Sampo Niskanen + */ +public class Databases { + + /* Static implementations of specific databases: */ + /** + * The motor database. + */ + public static final Database MOTOR = new Database(new MotorLoader()); + + + /** + * A database of bulk materials (with bulk densities). + */ + public static final Database BULK_MATERIAL = new Database(); + /** + * A database of surface materials (with surface densities). + */ + public static final Database SURFACE_MATERIAL = new Database(); + /** + * A database of linear material (with length densities). + */ + public static final Database LINE_MATERIAL = new Database(); + + + + // TODO: HIGH: loading the thrust curves and other databases + static { + + try { + MOTOR.loadJarDirectory("datafiles/thrustcurves/", ".*\\.[eE][nN][gG]$"); + } catch (IOException e) { + System.out.println("Could not read thrust curves from JAR: "+e.getMessage()); + + try { + MOTOR.loadDirectory(new File("datafiles/thrustcurves/"),".*\\.[eE][nN][gG]$"); + } catch (IOException e1) { + System.out.println("Could not read thrust curves from directory either."); + throw new RuntimeException(e1); + } + } + } + + // TODO: HIGH: Move materials into data files + static { + + BULK_MATERIAL.add(new Material.Bulk("Acrylic", 1190)); + BULK_MATERIAL.add(new Material.Bulk("Balsa", 170)); + BULK_MATERIAL.add(new Material.Bulk("Birch", 670)); + BULK_MATERIAL.add(new Material.Bulk("Cardboard", 680)); + BULK_MATERIAL.add(new Material.Bulk("Carbon fiber", 1780)); + BULK_MATERIAL.add(new Material.Bulk("Cork", 240)); + BULK_MATERIAL.add(new Material.Bulk("Fiberglass", 1850)); + BULK_MATERIAL.add(new Material.Bulk("Kraft phenolic",950)); + BULK_MATERIAL.add(new Material.Bulk("Maple", 755)); + BULK_MATERIAL.add(new Material.Bulk("Paper (office)",820)); + BULK_MATERIAL.add(new Material.Bulk("Pine", 530)); + BULK_MATERIAL.add(new Material.Bulk("Plywood (birch)",630)); + BULK_MATERIAL.add(new Material.Bulk("Polycarbonate (Lexan)",1200)); + BULK_MATERIAL.add(new Material.Bulk("Polystyrene", 1050)); + BULK_MATERIAL.add(new Material.Bulk("PVC", 1390)); + BULK_MATERIAL.add(new Material.Bulk("Spruce", 450)); + BULK_MATERIAL.add(new Material.Bulk("Quantum tubing",1050)); + + SURFACE_MATERIAL.add(new Material.Surface("Ripstop nylon", 0.067)); + SURFACE_MATERIAL.add(new Material.Surface("Mylar", 0.021)); + SURFACE_MATERIAL.add(new Material.Surface("Polyethylene (thin)", 0.015)); + SURFACE_MATERIAL.add(new Material.Surface("Polyethylene (heavy)", 0.040)); + SURFACE_MATERIAL.add(new Material.Surface("Silk", 0.060)); + SURFACE_MATERIAL.add(new Material.Surface("Paper (office)", 0.080)); + SURFACE_MATERIAL.add(new Material.Surface("Cellophane", 0.018)); + SURFACE_MATERIAL.add(new Material.Surface("Cr\u00eape paper", 0.025)); + + LINE_MATERIAL.add(new Material.Line("Thread (heavy-duty)", 0.0003)); + LINE_MATERIAL.add(new Material.Line("Elastic cord (round 2mm, 1/16 in)",0.0018)); + LINE_MATERIAL.add(new Material.Line("Elastic cord (flat 6mm, 1/4 in)", 0.0043)); + LINE_MATERIAL.add(new Material.Line("Elastic cord (flat 12mm, 1/2 in)", 0.008)); + LINE_MATERIAL.add(new Material.Line("Elastic cord (flat 19mm, 3/4 in)", 0.0012)); + LINE_MATERIAL.add(new Material.Line("Elastic cord (flat 25mm, 1 in)", 0.0016)); + LINE_MATERIAL.add(new Material.Line("Braided nylon (2 mm, 1/16 in)", 0.001)); + LINE_MATERIAL.add(new Material.Line("Braided nylon (3 mm, 1/8 in)", 0.0035)); + LINE_MATERIAL.add(new Material.Line("Tubular nylon (11 mm, 7/16 in)", 0.013)); + LINE_MATERIAL.add(new Material.Line("Tubular nylon (14 mm, 9/16 in)", 0.016)); + LINE_MATERIAL.add(new Material.Line("Tubular nylon (25 mm, 1 in)", 0.029)); + } + + + /** + * Find a material from the database with the specified type and name. Returns + * null if the specified material could not be found. + * + * @param type the material type. + * @param name the material name in the database. + * @return the material, or null if not found. + */ + public static Material findMaterial(Material.Type type, String name) { + Database db; + switch (type) { + case BULK: + db = BULK_MATERIAL; + break; + case SURFACE: + db = SURFACE_MATERIAL; + break; + case LINE: + db = LINE_MATERIAL; + break; + default: + throw new IllegalArgumentException("Illegal material type: "+type); + } + + for (Material m: db) { + if (m.getName().equalsIgnoreCase(name)) { + return m; + } + } + return null; + } + + + /** + * Find a material from the database or return a new material if the specified + * material with the specified density is not found. + * + * @param type the material type. + * @param name the material name. + * @param density the density of the material. + * @return the material object from the database or a new material. + */ + public static Material findMaterial(Material.Type type, String name, double density) { + Database db; + switch (type) { + case BULK: + db = BULK_MATERIAL; + break; + case SURFACE: + db = SURFACE_MATERIAL; + break; + case LINE: + db = LINE_MATERIAL; + break; + default: + throw new IllegalArgumentException("Illegal material type: "+type); + } + + for (Material m: db) { + if (m.getName().equalsIgnoreCase(name) && MathUtil.equals(m.getDensity(), density)) { + return m; + } + } + return Material.newMaterial(type, name, density); + } + + + + /** + * Return all motor in the database matching a search criteria. Any search criteria that + * is null or NaN is ignored. + * + * @param type the motor type, or null. + * @param manufacturer the manufacturer, or null. + * @param designation the designation, or null. + * @param diameter the diameter, or NaN. + * @param length the length, or NaN. + * @return an array of all the matching motors. + */ + public static Motor[] findMotors(Motor.Type type, String manufacturer, String designation, double diameter, double length) { + ArrayList results = new ArrayList(); + + for (Motor m: MOTOR) { + boolean match = true; + if (type != null && type != m.getMotorType()) + match = false; + else if (manufacturer != null && !manufacturer.equalsIgnoreCase(m.getManufacturer())) + match = false; + else if (designation != null && !designation.equalsIgnoreCase(m.getDesignation())) + match = false; + else if (!Double.isNaN(diameter) && (Math.abs(diameter - m.getDiameter()) > 0.0015)) + match = false; + else if (!Double.isNaN(length) && (Math.abs(length - m.getLength()) > 0.0015)) + match = false; + + if (match) + results.add(m); + } + + return results.toArray(new Motor[0]); + } + +} diff --git a/src/net/sf/openrocket/document/OpenRocketDocument.java b/src/net/sf/openrocket/document/OpenRocketDocument.java new file mode 100644 index 000000000..ae6c886c8 --- /dev/null +++ b/src/net/sf/openrocket/document/OpenRocketDocument.java @@ -0,0 +1,370 @@ +package net.sf.openrocket.document; +//TODO: LOW: move class somewhere else? + +import java.awt.event.ActionEvent; +import java.io.File; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import javax.swing.AbstractAction; +import javax.swing.Action; + +import net.sf.openrocket.rocketcomponent.ComponentChangeEvent; +import net.sf.openrocket.rocketcomponent.ComponentChangeListener; +import net.sf.openrocket.rocketcomponent.Configuration; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.util.Icons; + + +public class OpenRocketDocument implements ComponentChangeListener { + /** + * The minimum number of undo levels that are stored. + */ + public static final int UNDO_LEVELS = 50; + /** + * The margin of the undo levels. After the number of undo levels exceeds + * UNDO_LEVELS by this amount the undo is purged to that length. + */ + public static final int UNDO_MARGIN = 10; + + + private final Rocket rocket; + private final Configuration configuration; + + private final ArrayList simulations = new ArrayList(); + + + private int undoPosition = -1; // Illegal position, init in constructor + private LinkedList undoHistory = new LinkedList(); + private LinkedList undoDescription = new LinkedList(); + + private String nextDescription = null; + + + private File file = null; + private int savedID = -1; + + private final StorageOptions storageOptions = new StorageOptions(); + + + /* These must be initialized after undo history is set up. */ + private final UndoRedoAction undoAction; + private final UndoRedoAction redoAction; + + + public OpenRocketDocument(Rocket rocket) { + this(rocket.getDefaultConfiguration()); + } + + + private OpenRocketDocument(Configuration configuration) { + this.configuration = configuration; + this.rocket = configuration.getRocket(); + + undoHistory.add(rocket.copy()); + undoDescription.add(null); + undoPosition = 0; + + undoAction = new UndoRedoAction(UndoRedoAction.UNDO); + redoAction = new UndoRedoAction(UndoRedoAction.REDO); + + rocket.addComponentChangeListener(this); + + + } + + + + + public Rocket getRocket() { + return rocket; + } + + + public Configuration getDefaultConfiguration() { + return configuration; + } + + + public File getFile() { + return file; + } + + public void setFile(File file) { + this.file = file; + } + + + public boolean isSaved() { + return rocket.getModID() == savedID; + } + + public void setSaved(boolean saved) { + if (saved == false) + this.savedID = -1; + else + this.savedID = rocket.getModID(); + } + + /** + * Retrieve the default storage options for this document. + * + * @return the storage options. + */ + public StorageOptions getDefaultStorageOptions() { + return storageOptions; + } + + + + + + @SuppressWarnings("unchecked") + public List getSimulations() { + return (ArrayList)simulations.clone(); + } + public int getSimulationCount() { + return simulations.size(); + } + public Simulation getSimulation(int n) { + return simulations.get(n); + } + public int getSimulationIndex(Simulation simulation) { + return simulations.indexOf(simulation); + } + public void addSimulation(Simulation simulation) { + simulations.add(simulation); + } + public void addSimulation(Simulation simulation, int n) { + simulations.add(n, simulation); + } + public void removeSimulation(Simulation simulation) { + simulations.remove(simulation); + } + public Simulation removeSimulation(int n) { + return simulations.remove(n); + } + + + + /** + * Adds an undo point at this position. This method should be called *before* any + * action that is to be undoable. All actions after the call will be undone by a + * single "Undo" action. + *

+ * The description should be a short, descriptive string of the actions that will + * follow. This is shown to the user e.g. in the Edit-menu, for example + * "Undo (Modify body tube)". If the actions are not known (in general should not + * be the case!) description may be null. + *

+ * If this method is called successively without any change events occurring between the + * calls, only the last call will have any effect. + * + * @param description A short description of the following actions. + */ + public void addUndoPosition(String description) { + + // Check whether modifications have been done since last call + if (isCleanState()) { + // No modifications + nextDescription = description; + return; + } + + + /* + * Modifications have been made to the rocket. We should be at the end of the + * undo history, but check for consistency. + */ + assert(undoPosition == undoHistory.size()-1): "Undo inconsistency, report bug!"; + while (undoPosition < undoHistory.size()-1) { + undoHistory.removeLast(); + undoDescription.removeLast(); + } + + + // Add the current state to the undo history + undoHistory.add(rocket.copy()); + undoDescription.add(description); + nextDescription = description; + undoPosition++; + + + // Maintain maximum undo size + if (undoHistory.size() > UNDO_LEVELS + UNDO_MARGIN) { + for (int i=0; i < UNDO_MARGIN+1; i++) { + undoHistory.removeFirst(); + undoDescription.removeFirst(); + undoPosition--; + } + } + } + + + public Action getUndoAction() { + return undoAction; + } + + + public Action getRedoAction() { + return redoAction; + } + + + @Override + public void componentChanged(ComponentChangeEvent e) { + + if (!e.isUndoChange()) { + // Remove any redo information if available + while (undoPosition < undoHistory.size()-1) { + undoHistory.removeLast(); + undoDescription.removeLast(); + } + + // Set the latest description + undoDescription.set(undoPosition, nextDescription); + } + + undoAction.setAllValues(); + redoAction.setAllValues(); + } + + + public boolean isUndoAvailable() { + if (undoPosition > 0) + return true; + + return !isCleanState(); + } + + public String getUndoDescription() { + if (!isUndoAvailable()) + return null; + + if (isCleanState()) { + return undoDescription.get(undoPosition-1); + } else { + return undoDescription.get(undoPosition); + } + } + + + public boolean isRedoAvailable() { + return undoPosition < undoHistory.size()-1; + } + + public String getRedoDescription() { + if (!isRedoAvailable()) + return null; + + return undoDescription.get(undoPosition); + } + + + + public void undo() { + if (!isUndoAvailable()) { + throw new IllegalStateException("Undo not available."); + } + + // Update history position + + if (isCleanState()) { + // We are in a clean state, simply move backwards in history + undoPosition--; + } else { + // Modifications have been made, save the state and restore previous state + undoHistory.add(rocket.copy()); + undoDescription.add(null); + } + + rocket.loadFrom(undoHistory.get(undoPosition).copy()); + } + + + public void redo() { + if (!isRedoAvailable()) { + throw new IllegalStateException("Redo not available."); + } + + undoPosition++; + + rocket.loadFrom(undoHistory.get(undoPosition).copy()); + } + + + private boolean isCleanState() { + return rocket.getModID() == undoHistory.get(undoPosition).getModID(); + } + + + + + + + /** + * Inner class to implement undo/redo actions. + */ + private class UndoRedoAction extends AbstractAction { + public static final int UNDO = 1; + public static final int REDO = 2; + + private final int type; + + // Sole constructor + public UndoRedoAction(int type) { + if (type != UNDO && type != REDO) { + throw new IllegalArgumentException("Unknown type = "+type); + } + this.type = type; + setAllValues(); + } + + + // Actual action to make + public void actionPerformed(ActionEvent e) { + switch (type) { + case UNDO: + undo(); + break; + + case REDO: + redo(); + break; + } + } + + + // Set all the values correctly (name and enabled/disabled status) + public void setAllValues() { + String name,desc; + boolean enabled; + + switch (type) { + case UNDO: + name = "Undo"; + desc = getUndoDescription(); + enabled = isUndoAvailable(); + this.putValue(SMALL_ICON, Icons.EDIT_UNDO); + break; + + case REDO: + name = "Redo"; + desc = getRedoDescription(); + enabled = isRedoAvailable(); + this.putValue(SMALL_ICON, Icons.EDIT_REDO); + break; + + default: + throw new RuntimeException("EEEK!"); + } + + if (desc != null) + name = name + " ("+desc+")"; + + putValue(NAME, name); + setEnabled(enabled); + } + } +} diff --git a/src/net/sf/openrocket/document/Simulation.java b/src/net/sf/openrocket/document/Simulation.java new file mode 100644 index 000000000..4c4cf2cfd --- /dev/null +++ b/src/net/sf/openrocket/document/Simulation.java @@ -0,0 +1,361 @@ +package net.sf.openrocket.document; + +import java.util.ArrayList; +import java.util.List; + +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import net.sf.openrocket.aerodynamics.AerodynamicCalculator; +import net.sf.openrocket.aerodynamics.BarrowmanCalculator; +import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.rocketcomponent.Configuration; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.simulation.FlightData; +import net.sf.openrocket.simulation.FlightSimulator; +import net.sf.openrocket.simulation.RK4Simulator; +import net.sf.openrocket.simulation.SimulationConditions; +import net.sf.openrocket.simulation.SimulationListener; +import net.sf.openrocket.simulation.exception.SimulationException; +import net.sf.openrocket.simulation.exception.SimulationListenerException; +import net.sf.openrocket.util.ChangeSource; + + +public class Simulation implements ChangeSource { + + public static enum Status { + /** Up-to-date */ + UPTODATE, + + /** Loaded from file, status probably up-to-date */ + LOADED, + + /** Data outdated */ + OUTDATED, + + /** Imported external data */ + EXTERNAL, + + /** Not yet simulated */ + NOT_SIMULATED + } + + + private final Rocket rocket; + + private String name = ""; + + private Status status = Status.NOT_SIMULATED; + + /** The conditions to use */ + private final SimulationConditions conditions; + + private List simulationListeners = new ArrayList(); + + private Class simulatorClass = RK4Simulator.class; + private Class calculatorClass = BarrowmanCalculator.class; + + + + /** Listeners for this object */ + private final List listeners = new ArrayList(); + + + /** The conditions actually used in the previous simulation, or null */ + private SimulationConditions simulatedConditions = null; + private String simulatedMotors = null; + private FlightData simulatedData = null; + private int simulatedRocketID = -1; + + + /** + * Create a new simulation for the rocket. The initial motor configuration is + * taken from the default rocket configuration. + * + * @param rocket the rocket associated with the simulation. + */ + public Simulation(Rocket rocket) { + this.rocket = rocket; + this.status = Status.NOT_SIMULATED; + + conditions = new SimulationConditions(rocket); + conditions.setMotorConfigurationID( + rocket.getDefaultConfiguration().getMotorConfigurationID()); + conditions.addChangeListener(new ConditionListener()); + } + + + public Simulation(Rocket rocket, Status status, String name, SimulationConditions conditions, + List listeners, FlightData data) { + + if (rocket == null) + throw new IllegalArgumentException("rocket cannot be null"); + if (status == null) + throw new IllegalArgumentException("status cannot be null"); + if (name == null) + throw new IllegalArgumentException("name cannot be null"); + if (conditions == null) + throw new IllegalArgumentException("conditions cannot be null"); + + this.rocket = rocket; + + if (status == Status.UPTODATE) { + this.status = Status.LOADED; + } else if (data == null) { + this.status = Status.NOT_SIMULATED; + } else { + this.status = status; + } + + this.name = name; + + this.conditions = conditions; + conditions.addChangeListener(new ConditionListener()); + + if (listeners != null) { + this.simulationListeners.addAll(listeners); + } + + + if (data != null && this.status != Status.NOT_SIMULATED) { + simulatedData = data; + if (this.status == Status.LOADED) { + simulatedConditions = conditions.clone(); + simulatedRocketID = rocket.getModID(); + } + } + + } + + + + + /** + * Return a newly created Configuration for this simulation. The configuration + * has the motor ID set and all stages active. + * + * @return a newly created Configuration of the launch conditions. + */ + public Configuration getConfiguration() { + Configuration c = new Configuration(rocket); + c.setMotorConfigurationID(conditions.getMotorConfigurationID()); + c.setAllStages(); + return c; + } + + /** + * Returns the simulation conditions attached to this simulation. The conditions + * may be modified freely, and the status of the simulation will change to reflect + * the changes. + * + * @return the simulation conditions. + */ + public SimulationConditions getConditions() { + return conditions; + } + + + /** + * Get the list of simulation listeners. The returned list is the one used by + * this object; changes to it will reflect changes in the simulation. + * + * @return the actual list of simulation listeners. + */ + public List getSimulationListeners() { + return simulationListeners; + } + + + /** + * Return the user-defined name of the simulation. + * + * @return the name for the simulation. + */ + public String getName() { + return name; + } + + /** + * Set the user-defined name of the simulation. Setting the name to + * null yields an empty name. + * + * @param name the name of the simulation. + */ + public void setName(String name) { + if (this.name.equals(name)) + return; + + if (name == null) + this.name = ""; + else + this.name = name; + + fireChangeEvent(); + } + + + /** + * Returns the status of this simulation. This method examines whether the + * simulation has been outdated and returns {@link Status#OUTDATED} accordingly. + * + * @return the status + * @see Status + */ + public Status getStatus() { + if (status == Status.UPTODATE || status == Status.LOADED) { + if (rocket.getFunctionalModID() != simulatedRocketID || + !conditions.equals(simulatedConditions)) + return Status.OUTDATED; + } + + return status; + } + + + + + public void simulate(SimulationListener ... additionalListeners) + throws SimulationException { + + if (this.status == Status.EXTERNAL) { + throw new SimulationException("Cannot simulate imported simulation."); + } + Configuration configuration; + AerodynamicCalculator calculator; + FlightSimulator simulator; + + try { + calculator = calculatorClass.newInstance(); + simulator = simulatorClass.newInstance(); + } catch (InstantiationException e) { + throw new IllegalStateException("Cannot instantiate calculator/simulator.",e); + } catch (IllegalAccessException e) { + throw new IllegalStateException("Cannot access calc/sim instance?! BUG!",e); + } catch (NullPointerException e) { + throw new IllegalStateException("Calculator or simulator null",e); + } + + configuration = this.getConfiguration(); + calculator.setConfiguration(configuration); + simulator.setCalculator(calculator); + + for (SimulationListener l: additionalListeners) { + simulator.addSimulationListener(l); + } + + for (String className: simulationListeners) { + SimulationListener l = null; + try { + Class c = Class.forName(className); + l = (SimulationListener)c.newInstance(); + } catch (Exception e) { + throw new SimulationListenerException("Could not instantiate listener of " + + "class: " + className); + } + simulator.addSimulationListener(l); + } + + long t1, t2; + System.out.println("Simulation: calling simulator"); + t1 = System.currentTimeMillis(); + simulatedData = simulator.simulate(conditions); + t2 = System.currentTimeMillis(); + System.out.println("Simulation: returning from simulator, " + + "simulation took "+(t2-t1)+"ms"); + + // Set simulated info after simulation, will not be set in case of exception + simulatedConditions = conditions.clone(); + simulatedMotors = configuration.getMotorConfigurationDescription(); + simulatedRocketID = rocket.getFunctionalModID(); + + status = Status.UPTODATE; + fireChangeEvent(); + } + + + /** + * Return the conditions used in the previous simulation, or null + * if this simulation has not been run. + * + * @return the conditions used in the previous simulation, or null. + */ + public SimulationConditions getSimulatedConditions() { + return simulatedConditions; + } + + /** + * Return the warnings generated in the previous simulation, or + * null if this simulation has not been run. This is the same + * warning set as contained in the FlightData object. + * + * @return the warnings during the previous simulation, or null. + * @see FlightData#getWarningSet() + */ + public WarningSet getSimulatedWarnings() { + if (simulatedData == null) + return null; + return simulatedData.getWarningSet(); + } + + + /** + * Return a string describing the motor configuration of the previous simulation, + * or null if this simulation has not been run. + * + * @return a description of the motor configuration of the previous simulation, or + * null. + * @see Rocket#getMotorConfigurationDescription(String) + */ + public String getSimulatedMotorDescription() { + return simulatedMotors; + } + + /** + * Return the flight data of the previous simulation, or null if + * this simulation has not been run. + * + * @return the flight data of the previous simulation, or null. + */ + public FlightData getSimulatedData() { + return simulatedData; + } + + + + + + + @Override + public void addChangeListener(ChangeListener listener) { + listeners.add(listener); + } + + @Override + public void removeChangeListener(ChangeListener listener) { + listeners.remove(listener); + } + + protected void fireChangeEvent() { + ChangeListener[] ls = listeners.toArray(new ChangeListener[0]); + ChangeEvent e = new ChangeEvent(this); + for (ChangeListener l: ls) { + l.stateChanged(e); + } + } + + + + + private class ConditionListener implements ChangeListener { + + private Status oldStatus = null; + + @Override + public void stateChanged(ChangeEvent e) { + if (getStatus() != oldStatus) { + oldStatus = getStatus(); + fireChangeEvent(); + } + } + } +} diff --git a/src/net/sf/openrocket/document/StorageOptions.java b/src/net/sf/openrocket/document/StorageOptions.java new file mode 100644 index 000000000..3a817ca52 --- /dev/null +++ b/src/net/sf/openrocket/document/StorageOptions.java @@ -0,0 +1,51 @@ +package net.sf.openrocket.document; + +public class StorageOptions implements Cloneable { + + public static final double SIMULATION_DATA_NONE = Double.POSITIVE_INFINITY; + public static final double SIMULATION_DATA_ALL = 0; + + private boolean compressionEnabled = true; + + private double simulationTimeSkip = SIMULATION_DATA_NONE; + + private boolean explicitlySet = false; + + + public boolean isCompressionEnabled() { + return compressionEnabled; + } + + public void setCompressionEnabled(boolean compression) { + this.compressionEnabled = compression; + } + + public double getSimulationTimeSkip() { + return simulationTimeSkip; + } + + public void setSimulationTimeSkip(double simulationTimeSkip) { + this.simulationTimeSkip = simulationTimeSkip; + } + + + + public boolean isExplicitlySet() { + return explicitlySet; + } + + public void setExplicitlySet(boolean explicitlySet) { + this.explicitlySet = explicitlySet; + } + + + + @Override + public StorageOptions clone() { + try { + return (StorageOptions)super.clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeException("CloneNotSupportedException?!?", e); + } + } +} diff --git a/src/net/sf/openrocket/file/GeneralRocketLoader.java b/src/net/sf/openrocket/file/GeneralRocketLoader.java new file mode 100644 index 000000000..bba71d29c --- /dev/null +++ b/src/net/sf/openrocket/file/GeneralRocketLoader.java @@ -0,0 +1,71 @@ +package net.sf.openrocket.file; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.GZIPInputStream; + +import net.sf.openrocket.document.OpenRocketDocument; + + +/** + * A rocket loader that auto-detects the document type and uses the appropriate + * loading. Supports loading of GZIPed files as well with transparent + * uncompression. + * + * @author Sampo Niskanen + */ +public class GeneralRocketLoader extends RocketLoader { + + private static final int READ_BYTES = 300; + + private static final byte[] GZIP_SIGNATURE = { 31, -117 }; // 0x1f, 0x8b + private static final byte[] OPENROCKET_SIGNATURE = " { + + public Collection load(InputStream stream, String filename) throws IOException; + +} diff --git a/src/net/sf/openrocket/file/MotorLoader.java b/src/net/sf/openrocket/file/MotorLoader.java new file mode 100644 index 000000000..056f23b7b --- /dev/null +++ b/src/net/sf/openrocket/file/MotorLoader.java @@ -0,0 +1,445 @@ +package net.sf.openrocket.file; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import net.sf.openrocket.rocketcomponent.Motor; +import net.sf.openrocket.rocketcomponent.ThrustCurveMotor; +import net.sf.openrocket.util.Coordinate; + + +public class MotorLoader implements Loader { + + /** The charset used when reading RASP files. */ + public static final String RASP_CHARSET = "ISO-8859-1"; + + + + public List load(InputStream stream, String filename) throws IOException { + return loadMotor(stream, filename); + } + + + /** + * Load Motor objects from the specified InputStream. + * The file type is detected based on the filename extension. + * + * @param stream the stream from which to read the file. + * @param filename the file name, by which the format is detected. + * @return a list of Motor objects defined in the file. + * @throws IOException if an I/O exception occurs, the file format is unknown + * or illegal. + */ + public static List loadMotor(InputStream stream, String filename) throws IOException { + if (filename == null) { + throw new IOException("Unknown file type."); + } + + String ext = ""; + int point = filename.lastIndexOf('.'); + + if (point > 0) + ext = filename.substring(point+1); + + if (ext.equalsIgnoreCase("eng")) { + return loadRASP(stream); + } + + throw new IOException("Unknown file type."); + } + + + + + ////////////// RASP file format ////////////// + + + /** Manufacturer codes to expand in RASP files */ + private static final Map manufacturerCodes = + new HashMap(); + static { + manufacturerCodes.put("A", "AeroTech"); + manufacturerCodes.put("AT", "AeroTech"); + manufacturerCodes.put("AT-RMS", "AeroTech"); + manufacturerCodes.put("AT/RCS", "AeroTech"); + manufacturerCodes.put("AERO", "AeroTech"); + manufacturerCodes.put("AEROT", "AeroTech"); + manufacturerCodes.put("ISP", "AeroTech"); + manufacturerCodes.put("AEROTECH", "AeroTech"); + manufacturerCodes.put("AEROTECH/APOGEE", "AeroTech"); + manufacturerCodes.put("AMW", "Animal Motor Works"); + manufacturerCodes.put("AW", "Animal Motor Works"); + manufacturerCodes.put("ANIMAL", "Animal Motor Works"); + manufacturerCodes.put("AP", "Apogee"); + manufacturerCodes.put("APOG", "Apogee"); + manufacturerCodes.put("P", "Apogee"); + manufacturerCodes.put("CES", "Cesaroni"); + manufacturerCodes.put("CTI", "Cesaroni"); + manufacturerCodes.put("CS", "Cesaroni"); + manufacturerCodes.put("CSR", "Cesaroni"); + manufacturerCodes.put("PRO38", "Cesaroni"); + manufacturerCodes.put("CR", "Contrail Rocket"); + manufacturerCodes.put("CONTR", "Contrail Rocket"); + manufacturerCodes.put("E", "Estes"); + manufacturerCodes.put("ES", "Estes"); + manufacturerCodes.put("EM", "Ellis Mountain"); + manufacturerCodes.put("ELLIS", "Ellis Mountain"); + manufacturerCodes.put("GR", "Gorilla Rocket Motors"); + manufacturerCodes.put("GORILLA", "Gorilla Rocket Motors"); + manufacturerCodes.put("H", "HyperTEK"); + manufacturerCodes.put("HT", "HyperTEK"); + manufacturerCodes.put("HYPER", "HyperTEK"); + manufacturerCodes.put("HYPERTEK", "HyperTEK"); + manufacturerCodes.put("K", "Kosdon by AeroTech"); + manufacturerCodes.put("KBA", "Kosdon by AeroTech"); + manufacturerCodes.put("K/AT", "Kosdon by AeroTech"); + manufacturerCodes.put("KOSDON", "Kosdon by AeroTech"); + manufacturerCodes.put("KOSDON/AT", "Kosdon by AeroTech"); + manufacturerCodes.put("KOSDON-BY-AEROTECH", "Kosdon by AeroTech"); + manufacturerCodes.put("LOKI", "Loki Research"); + manufacturerCodes.put("LR", "Loki Research"); + manufacturerCodes.put("PM", "Public Missiles"); + manufacturerCodes.put("PML", "Public Missiles"); + manufacturerCodes.put("PP", "Propulsion Polymers"); + manufacturerCodes.put("PROP", "Propulsion Polymers"); + manufacturerCodes.put("PROPULSION", "Propulsion Polymers"); + manufacturerCodes.put("PROPULSION-POLYMERS", "Propulsion Polymers"); + manufacturerCodes.put("Q", "Quest"); + manufacturerCodes.put("QU", "Quest"); + manufacturerCodes.put("RATT", "RATT Works"); + manufacturerCodes.put("RT", "RATT Works"); + manufacturerCodes.put("RTW", "RATT Works"); + manufacturerCodes.put("RR", "Roadrunner Rocketry"); + manufacturerCodes.put("ROADRUNNER", "Roadrunner Rocketry"); + manufacturerCodes.put("RV", "Rocketvision"); + manufacturerCodes.put("SR", "Sky Ripper Systems"); + manufacturerCodes.put("SRS", "Sky Ripper Systems"); + manufacturerCodes.put("SKYR", "Sky Ripper Systems"); + manufacturerCodes.put("SKYRIPPER", "Sky Ripper Systems"); + manufacturerCodes.put("WCH", "West Coast Hybrids"); + manufacturerCodes.put("WCR", "West Coast Hybrids"); + + manufacturerCodes.put("SF", "WECO Feuerwerk"); // Previously Sachsen Feuerwerks + manufacturerCodes.put("WECO", "WECO Feuerwerk"); + + } + + /** + * A helper method to load a Motor from a RASP file, read from the + * specified InputStream. The charset used is defined in + * {@link #RASP_CHARSET}. + * + * @param stream the InputStream to read. + * @return the Motor object. + * @throws IOException if an I/O error occurs or if the file format is illegal. + * @see #loadRASP(Reader) + */ + public static List loadRASP(InputStream stream) throws IOException { + return loadRASP(new InputStreamReader(stream, RASP_CHARSET)); + } + + + + /** + * Load a Motor from a RASP file specified by the Reader. + * The Reader is responsible for using the correct charset. + *

+ * The CG is assumed to be located at the center of the motor casing and the mass + * is calculated from the thrust curve by assuming a constant exhaust velocity. + * + * @param reader the source of the file. + * @return a list of the {@link Motor} objects defined in the file. + * @throws IOException if an I/O error occurs or if the file format is illegal. + */ + public static List loadRASP(Reader reader) throws IOException { + List motors = new ArrayList(); + BufferedReader in = new BufferedReader(reader); + + String manufacturer = ""; + String designation = ""; + String comment = ""; + + double length = 0; + double diameter = 0; + ArrayList delays = null; + + List time = new ArrayList(); + List thrust = new ArrayList(); + + double propW = 0; + double totalW = 0; + + try { + String line; + String[] pieces, buf; + + line = in.readLine(); + main: while (line != null) { // Until EOF + + manufacturer = ""; + designation = ""; + comment = ""; + length = 0; + diameter = 0; + delays = new ArrayList(); + propW = 0; + totalW = 0; + time.clear(); + thrust .clear(); + + // Read comment + while (line.length()==0 || line.charAt(0)==';') { + if (line.length() > 0) { + comment += line.substring(1).trim() + "\n"; + } + line = in.readLine(); + if (line == null) + break main; + } + comment = comment.trim(); + + // Parse header line, example: + // F32 24 124 5-10-15-P .0377 .0695 RV + // desig diam len delays prop.w tot.w manufacturer + pieces = split(line); + if (pieces.length != 7) { + throw new IOException("Illegal file format."); + } + + designation = pieces[0]; + diameter = Double.parseDouble(pieces[1]) / 1000.0; + length = Double.parseDouble(pieces[2]) / 1000.0; + + if (pieces[3].equalsIgnoreCase("None")) { + + } else { + buf = split(pieces[3],"[-,]+"); + for (int i=0; i < buf.length; i++) { + if (buf[i].equalsIgnoreCase("P")) { + delays.add(Motor.PLUGGED); + } else { + // Many RASP files have "100" as an only delay + double d = Double.parseDouble(buf[i]); + if (d < 99) + delays.add(d); + } + } + Collections.sort(delays); + } + + propW = Double.parseDouble(pieces[4]); + totalW = Double.parseDouble(pieces[5]); + if (manufacturerCodes.containsKey(pieces[6].toUpperCase())) { + manufacturer = manufacturerCodes.get(pieces[6].toUpperCase()); + } else { + manufacturer = pieces[6].replace('_', ' '); + } + + // Read the data + for (line = in.readLine(); + (line != null) && (line.length()==0 || line.charAt(0) != ';'); + line = in.readLine()) { + + buf = split(line); + if (buf.length == 0) { + continue; + } else if (buf.length == 2) { + + time.add(Double.parseDouble(buf[0])); + thrust .add(Double.parseDouble(buf[1])); + + } else { + throw new IOException("Illegal file format."); + } + } + + // Comment of EOF encountered, marks the start of the next motor + if (time.size() < 2) { + throw new IOException("Illegal file format, too short thrust-curve."); + } + double[] delayArray = new double[delays.size()]; + for (int i=0; i time, List thrust) + throws IOException { + + // Add zero time/thrust if necessary + if (time.get(0) > 0) { + time.add(0, 0.0); + thrust.add(0, 0.0); + } + + List mass = calculateMass(time,thrust,totalW,propW); + + double[] timeArray = new double[time.size()]; + double[] thrustArray = new double[time.size()]; + Coordinate[] cgArray = new Coordinate[time.size()]; + for (int i=0; i < time.size(); i++) { + timeArray[i] = time.get(i); + thrustArray[i] = thrust.get(i); + cgArray[i] = new Coordinate(length/2,0,0,mass.get(i)); + } + + try { + + return new ThrustCurveMotor(manufacturer, designation, comment, Motor.Type.UNKNOWN, + delays, diameter, length, timeArray, thrustArray, cgArray); + + } catch (IllegalArgumentException e) { + + // Bad data read from file. + throw new IOException("Illegal file format.", e); + + } + } + + + + /** + * Calculate the mass of a motor at distinct points in time based on the + * initial total mass, propellant weight and thrust. + *

+ * This calculation assumes that the velocity of the exhaust remains constant + * during the burning. This derives from the mass-flow and thrust relation + *

F = m' * v
+ * + * @param time list of time points + * @param thrust thrust at the discrete times + * @param total total weight of the motor + * @param prop propellant amount consumed during burning + * @return a list of the mass at the specified time points + */ + private static List calculateMass(List time, List thrust, + double total, double prop) { + List mass = new ArrayList(); + List deltam = new ArrayList(); + + double t0, f0; + double totalMassChange = 0; + double scale; + + // First calculate mass change between points + t0 = time.get(0); + f0 = thrust.get(0); + for (int i=1; i < time.size(); i++) { + double t1 = time.get(i); + double f1 = thrust.get(i); + + double dm = 0.5*(f0+f1)*(t1-t0); + deltam.add(dm); + totalMassChange += dm; + } + + // Scale mass change and calculate mass + mass.add(total); + scale = prop / totalMassChange; + for (double dm: deltam) { + total -= dm*scale; + mass.add(total); + } + + return mass; + } + + + /** + * Tokenizes a string using whitespace as the delimiter. + */ + private static String[] split(String str) { + return split(str,"\\s+"); + } + + /** + * Tokenizes a string using the given delimiter. + */ + private static String[] split(String str, String delim) { + String[] pieces = str.split(delim); + if (pieces.length==0 || !pieces[0].equals("")) + return pieces; + return Arrays.copyOfRange(pieces, 1, pieces.length); + } + + + + + + /** + * For testing purposes. + */ + public static void main(String[] args) throws IOException { + List motors; + + if (args.length != 1) { + System.out.println("Run with one argument, the RAPS file."); + System.exit(1); + } + + motors = loadRASP(new FileInputStream(new File(args[0]))); + + for (Motor motor: motors) { + double time = motor.getTotalTime(); + + System.out.println("Motor " + motor); + System.out.println("Manufacturer: "+motor.getManufacturer()); + System.out.println("Designation: "+motor.getDesignation()); + System.out.println("Type: "+motor.getMotorType().getName()); + System.out.printf( "Length: %.1f mm\n",motor.getLength()*1000); + System.out.printf( "Diameter: %.1f mm\n",motor.getDiameter()*1000); + System.out.println("Comment:\n" + motor.getDescription()); + + System.out.printf( "Total burn time: %.2f s\n", time); + System.out.printf( "Avg. burn time: %.2f s\n", motor.getAverageTime()); + System.out.printf( "Avg. thrust: %.2f N\n", motor.getAverageThrust()); + System.out.printf( "Max. thrust: %.2f N\n", motor.getMaxThrust()); + System.out.printf( "Total impulse: %.2f Ns\n", motor.getTotalImpulse()); + System.out.println("Delay times: " + + Arrays.toString(motor.getStandardDelays())); + System.out.println(""); + + final double COUNT = 20; + for (int i=0; i <= COUNT; i++) { + double t = time * i/COUNT; + System.out.printf("t=%.2fs F=%.2fN m=%.4fkg\n", + t, motor.getThrust(t), motor.getMass(t)); + } + System.out.println(""); + } + + } +} diff --git a/src/net/sf/openrocket/file/OpenRocketLoader.java b/src/net/sf/openrocket/file/OpenRocketLoader.java new file mode 100644 index 000000000..e1978a7f5 --- /dev/null +++ b/src/net/sf/openrocket/file/OpenRocketLoader.java @@ -0,0 +1,2103 @@ +package net.sf.openrocket.file; + +import java.awt.Color; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Stack; + +import net.sf.openrocket.aerodynamics.Warning; +import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.database.Databases; +import net.sf.openrocket.document.OpenRocketDocument; +import net.sf.openrocket.document.Simulation; +import net.sf.openrocket.document.StorageOptions; +import net.sf.openrocket.document.Simulation.Status; +import net.sf.openrocket.material.Material; +import net.sf.openrocket.rocketcomponent.BodyComponent; +import net.sf.openrocket.rocketcomponent.BodyTube; +import net.sf.openrocket.rocketcomponent.Bulkhead; +import net.sf.openrocket.rocketcomponent.CenteringRing; +import net.sf.openrocket.rocketcomponent.ClusterConfiguration; +import net.sf.openrocket.rocketcomponent.Clusterable; +import net.sf.openrocket.rocketcomponent.EllipticalFinSet; +import net.sf.openrocket.rocketcomponent.EngineBlock; +import net.sf.openrocket.rocketcomponent.ExternalComponent; +import net.sf.openrocket.rocketcomponent.FinSet; +import net.sf.openrocket.rocketcomponent.FreeformFinSet; +import net.sf.openrocket.rocketcomponent.InnerTube; +import net.sf.openrocket.rocketcomponent.InternalComponent; +import net.sf.openrocket.rocketcomponent.LaunchLug; +import net.sf.openrocket.rocketcomponent.MassComponent; +import net.sf.openrocket.rocketcomponent.MassObject; +import net.sf.openrocket.rocketcomponent.Motor; +import net.sf.openrocket.rocketcomponent.MotorMount; +import net.sf.openrocket.rocketcomponent.NoseCone; +import net.sf.openrocket.rocketcomponent.Parachute; +import net.sf.openrocket.rocketcomponent.RadiusRingComponent; +import net.sf.openrocket.rocketcomponent.RecoveryDevice; +import net.sf.openrocket.rocketcomponent.ReferenceType; +import net.sf.openrocket.rocketcomponent.RingComponent; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.rocketcomponent.ShockCord; +import net.sf.openrocket.rocketcomponent.Stage; +import net.sf.openrocket.rocketcomponent.Streamer; +import net.sf.openrocket.rocketcomponent.StructuralComponent; +import net.sf.openrocket.rocketcomponent.SymmetricComponent; +import net.sf.openrocket.rocketcomponent.ThicknessRingComponent; +import net.sf.openrocket.rocketcomponent.Transition; +import net.sf.openrocket.rocketcomponent.TrapezoidFinSet; +import net.sf.openrocket.rocketcomponent.TubeCoupler; +import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish; +import net.sf.openrocket.rocketcomponent.RocketComponent.Position; +import net.sf.openrocket.simulation.FlightData; +import net.sf.openrocket.simulation.FlightDataBranch; +import net.sf.openrocket.simulation.FlightEvent; +import net.sf.openrocket.simulation.SimulationConditions; +import net.sf.openrocket.simulation.FlightEvent.Type; +import net.sf.openrocket.unit.UnitGroup; +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.LineStyle; +import net.sf.openrocket.util.Reflection; + +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.DefaultHandler; +import org.xml.sax.helpers.XMLReaderFactory; + + +/** + * Class that loads a rocket definition from an OpenRocket rocket file. + *

+ * This class uses SAX to read the XML file format. The + * {@link #loadFromStream(InputStream)} method simply sets the system up and + * starts the parsing, while the actual logic is in the private inner class + * OpenRocketHandler. + * + * @author Sampo Niskanen + */ + +public class OpenRocketLoader extends RocketLoader { + + @Override + public OpenRocketDocument loadFromStream(InputStream source) throws RocketLoadException, + IOException { + InputSource xmlSource = new InputSource(source); + OpenRocketHandler handler = new OpenRocketHandler(); + + DelegatorHandler xmlhandler = new DelegatorHandler(handler, warnings); + + try { + XMLReader parser = XMLReaderFactory.createXMLReader(); + parser.setContentHandler(xmlhandler); + parser.setErrorHandler(xmlhandler); + parser.parse(xmlSource); + } catch (SAXException e) { + throw new RocketLoadException("Malformed XML in input.", e); + } + + + OpenRocketDocument doc = handler.getDocument(); + doc.getDefaultConfiguration().setAllStages(); + + // Deduce suitable time skip + double timeSkip = StorageOptions.SIMULATION_DATA_NONE; + for (Simulation s: doc.getSimulations()) { + if (s.getStatus() == Simulation.Status.EXTERNAL || + s.getStatus() == Simulation.Status.NOT_SIMULATED) + continue; + if (s.getSimulatedData() == null) + continue; + if (s.getSimulatedData().getBranchCount() == 0) + continue; + FlightDataBranch branch = s.getSimulatedData().getBranch(0); + if (branch == null) + continue; + List list = branch.get(FlightDataBranch.TYPE_TIME); + if (list == null) + continue; + + double previousTime = Double.NaN; + for (double time: list) { + if (time - previousTime < timeSkip) + timeSkip = time-previousTime; + previousTime = time; + } + } + // Round value + timeSkip = Math.rint(timeSkip*100)/100; + + doc.getDefaultStorageOptions().setSimulationTimeSkip(timeSkip); + doc.getDefaultStorageOptions().setCompressionEnabled(false); // Set by caller if compressed + doc.getDefaultStorageOptions().setExplicitlySet(false); + return doc; + } + +} + + + +class DocumentConfig { + + /* Remember to update OpenRocketSaver as well! */ + public static final String[] SUPPORTED_VERSIONS = { "0.9", "1.0" }; + + + //////// Component constructors + static final HashMap> constructors = new HashMap>(); + static { + try { + // External components + constructors.put("bodytube", BodyTube.class.getConstructor(new Class[0])); + constructors.put("transition", Transition.class.getConstructor(new Class[0])); + constructors.put("nosecone", NoseCone.class.getConstructor(new Class[0])); + constructors.put("trapezoidfinset", TrapezoidFinSet.class.getConstructor(new Class[0])); + constructors.put("ellipticalfinset", EllipticalFinSet.class.getConstructor(new Class[0])); + constructors.put("freeformfinset", FreeformFinSet.class.getConstructor(new Class[0])); + constructors.put("launchlug", LaunchLug.class.getConstructor(new Class[0])); + + // Internal components + constructors.put("engineblock", EngineBlock.class.getConstructor(new Class[0])); + constructors.put("innertube", InnerTube.class.getConstructor(new Class[0])); + constructors.put("tubecoupler", TubeCoupler.class.getConstructor(new Class[0])); + constructors.put("bulkhead", Bulkhead.class.getConstructor(new Class[0])); + constructors.put("centeringring", CenteringRing.class.getConstructor(new Class[0])); + + constructors.put("masscomponent", MassComponent.class.getConstructor(new Class[0])); + constructors.put("shockcord", ShockCord.class.getConstructor(new Class[0])); + constructors.put("parachute", Parachute.class.getConstructor(new Class[0])); + constructors.put("streamer", Streamer.class.getConstructor(new Class[0])); + + // Other + constructors.put("stage", Stage.class.getConstructor(new Class[0])); + + } catch (NoSuchMethodException e) { + throw new RuntimeException( + "Error in constructing the 'constructors' HashMap."); + } + } + + + //////// Parameter setters + /* + * The keys are of the form Class:param, where Class is the class name and param + * the element name. Setters are searched for in descending class order. + * A setter of null means setting the parameter is not allowed. + */ + static final HashMap setters = new HashMap(); + static { + // RocketComponent + setters.put("RocketComponent:name", new StringSetter( + Reflection.findMethodStatic(RocketComponent.class, "setName", String.class))); + setters.put("RocketComponent:color", new ColorSetter( + Reflection.findMethodStatic(RocketComponent.class, "setColor", Color.class))); + setters.put("RocketComponent:linestyle", new EnumSetter( + Reflection.findMethodStatic(RocketComponent.class, "setLineStyle", LineStyle.class), + LineStyle.class)); + setters.put("RocketComponent:position", new PositionSetter()); + setters.put("RocketComponent:overridemass", new OverrideSetter( + Reflection.findMethodStatic(RocketComponent.class, "setOverrideMass", double.class), + Reflection.findMethodStatic(RocketComponent.class, "setMassOverridden", boolean.class))); + setters.put("RocketComponent:overridecg", new OverrideSetter( + Reflection.findMethodStatic(RocketComponent.class, "setOverrideCGX", double.class), + Reflection.findMethodStatic(RocketComponent.class, "setCGOverridden", boolean.class))); + setters.put("RocketComponent:overridesubcomponents", new BooleanSetter( + Reflection.findMethodStatic(RocketComponent.class, "setOverrideSubcomponents", boolean.class))); + setters.put("RocketComponent:comment", new StringSetter( + Reflection.findMethodStatic(RocketComponent.class, "setComment", String.class))); + + // ExternalComponent + setters.put("ExternalComponent:finish", new EnumSetter( + Reflection.findMethodStatic(ExternalComponent.class, "setFinish", Finish.class), + Finish.class)); + setters.put("ExternalComponent:material", new MaterialSetter( + Reflection.findMethodStatic(ExternalComponent.class, "setMaterial", Material.class), + Material.Type.BULK)); + + // BodyComponent + setters.put("BodyComponent:length", new DoubleSetter( + Reflection.findMethodStatic(BodyComponent.class, "setLength", double.class))); + + // SymmetricComponent + setters.put("SymmetricComponent:thickness", new DoubleSetter( + Reflection.findMethodStatic(SymmetricComponent.class,"setThickness", double.class), + "filled", + Reflection.findMethodStatic(SymmetricComponent.class,"setFilled", boolean.class))); + + // BodyTube + setters.put("BodyTube:radius", new DoubleSetter( + Reflection.findMethodStatic(BodyTube.class, "setRadius", double.class), + "auto", + Reflection.findMethodStatic(BodyTube.class,"setRadiusAutomatic", boolean.class))); + + // Transition + setters.put("Transition:shape", new EnumSetter( + Reflection.findMethodStatic(Transition.class, "setType", Transition.Shape.class), + Transition.Shape.class)); + setters.put("Transition:shapeclipped", new BooleanSetter( + Reflection.findMethodStatic(Transition.class, "setClipped", boolean.class))); + setters.put("Transition:shapeparameter", new DoubleSetter( + Reflection.findMethodStatic(Transition.class, "setShapeParameter", double.class))); + + setters.put("Transition:foreradius", new DoubleSetter( + Reflection.findMethodStatic(Transition.class, "setForeRadius", double.class), + "auto", + Reflection.findMethodStatic(Transition.class, "setForeRadiusAutomatic", boolean.class))); + setters.put("Transition:aftradius", new DoubleSetter( + Reflection.findMethodStatic(Transition.class, "setAftRadius", double.class), + "auto", + Reflection.findMethodStatic(Transition.class, "setAftRadiusAutomatic", boolean.class))); + + setters.put("Transition:foreshoulderradius", new DoubleSetter( + Reflection.findMethodStatic(Transition.class, "setForeShoulderRadius", double.class))); + setters.put("Transition:foreshoulderlength", new DoubleSetter( + Reflection.findMethodStatic(Transition.class, "setForeShoulderLength", double.class))); + setters.put("Transition:foreshoulderthickness", new DoubleSetter( + Reflection.findMethodStatic(Transition.class, "setForeShoulderThickness", double.class))); + setters.put("Transition:foreshouldercapped", new BooleanSetter( + Reflection.findMethodStatic(Transition.class, "setForeShoulderCapped", boolean.class))); + + setters.put("Transition:aftshoulderradius", new DoubleSetter( + Reflection.findMethodStatic(Transition.class, "setAftShoulderRadius", double.class))); + setters.put("Transition:aftshoulderlength", new DoubleSetter( + Reflection.findMethodStatic(Transition.class, "setAftShoulderLength", double.class))); + setters.put("Transition:aftshoulderthickness", new DoubleSetter( + Reflection.findMethodStatic(Transition.class, "setAftShoulderThickness", double.class))); + setters.put("Transition:aftshouldercapped", new BooleanSetter( + Reflection.findMethodStatic(Transition.class, "setAftShoulderCapped", boolean.class))); + + // NoseCone - disable disallowed elements + setters.put("NoseCone:foreradius", null); + setters.put("NoseCone:foreshoulderradius", null); + setters.put("NoseCone:foreshoulderlength", null); + setters.put("NoseCone:foreshoulderthickness", null); + setters.put("NoseCone:foreshouldercapped", null); + + // FinSet + setters.put("FinSet:fincount", new IntSetter( + Reflection.findMethodStatic(FinSet.class, "setFinCount", int.class))); + setters.put("FinSet:rotation", new DoubleSetter( + Reflection.findMethodStatic(FinSet.class, "setBaseRotation", double.class), Math.PI/180.0)); + setters.put("FinSet:thickness", new DoubleSetter( + Reflection.findMethodStatic(FinSet.class, "setThickness", double.class))); + setters.put("FinSet:crosssection", new EnumSetter( + Reflection.findMethodStatic(FinSet.class, "setCrossSection", FinSet.CrossSection.class), + FinSet.CrossSection.class)); + setters.put("FinSet:cant", new DoubleSetter( + Reflection.findMethodStatic(FinSet.class, "setCantAngle", double.class), Math.PI/180.0)); + + // TrapezoidFinSet + setters.put("TrapezoidFinSet:rootchord", new DoubleSetter( + Reflection.findMethodStatic(TrapezoidFinSet.class, "setRootChord", double.class))); + setters.put("TrapezoidFinSet:tipchord", new DoubleSetter( + Reflection.findMethodStatic(TrapezoidFinSet.class, "setTipChord", double.class))); + setters.put("TrapezoidFinSet:sweeplength", new DoubleSetter( + Reflection.findMethodStatic(TrapezoidFinSet.class, "setSweep", double.class))); + setters.put("TrapezoidFinSet:height", new DoubleSetter( + Reflection.findMethodStatic(TrapezoidFinSet.class, "setHeight", double.class))); + + // EllipticalFinSet + setters.put("EllipticalFinSet:rootchord", new DoubleSetter( + Reflection.findMethodStatic(EllipticalFinSet.class, "setLength", double.class))); + setters.put("EllipticalFinSet:height", new DoubleSetter( + Reflection.findMethodStatic(EllipticalFinSet.class, "setHeight", double.class))); + + // FreeformFinSet points handled as a special handler + + // LaunchLug + setters.put("LaunchLug:radius", new DoubleSetter( + Reflection.findMethodStatic(LaunchLug.class, "setRadius", double.class))); + setters.put("LaunchLug:length", new DoubleSetter( + Reflection.findMethodStatic(LaunchLug.class, "setLength", double.class))); + setters.put("LaunchLug:thickness", new DoubleSetter( + Reflection.findMethodStatic(LaunchLug.class, "setThickness", double.class))); + setters.put("LaunchLug:radialDirection", new DoubleSetter( + Reflection.findMethodStatic(LaunchLug.class, "setRadialDirection", double.class), + Math.PI/180.0)); + + // InternalComponent - nothing + + // StructuralComponent + setters.put("StructuralComponent:material", new MaterialSetter( + Reflection.findMethodStatic(StructuralComponent.class, "setMaterial", Material.class), + Material.Type.BULK)); + + // RingComponent + setters.put("RingComponent:length", new DoubleSetter( + Reflection.findMethodStatic(RingComponent.class, "setLength", double.class))); + setters.put("RingComponent:radialposition", new DoubleSetter( + Reflection.findMethodStatic(RingComponent.class, "setRadialPosition", double.class))); + setters.put("RingComponent:radialdirection", new DoubleSetter( + Reflection.findMethodStatic(RingComponent.class, "setRadialDirection", double.class), + Math.PI / 180.0)); + + // ThicknessRingComponent - radius on separate components due to differing automatics + setters.put("ThicknessRingComponent:thickness", new DoubleSetter( + Reflection.findMethodStatic(ThicknessRingComponent.class, "setThickness", double.class))); + + // EngineBlock + setters.put("EngineBlock:outerradius", new DoubleSetter( + Reflection.findMethodStatic(EngineBlock.class, "setOuterRadius", double.class), + "auto", + Reflection.findMethodStatic(EngineBlock.class, "setOuterRadiusAutomatic", boolean.class))); + + // TubeCoupler + setters.put("TubeCoupler:outerradius", new DoubleSetter( + Reflection.findMethodStatic(TubeCoupler.class, "setOuterRadius", double.class), + "auto", + Reflection.findMethodStatic(TubeCoupler.class, "setOuterRadiusAutomatic", boolean.class))); + + // InnerTube + setters.put("InnerTube:outerradius", new DoubleSetter( + Reflection.findMethodStatic(InnerTube.class, "setOuterRadius", double.class))); + setters.put("InnerTube:clusterconfiguration", new ClusterConfigurationSetter()); + setters.put("InnerTube:clusterscale", new DoubleSetter( + Reflection.findMethodStatic(InnerTube.class, "setClusterScale", double.class))); + setters.put("InnerTube:clusterrotation", new DoubleSetter( + Reflection.findMethodStatic(InnerTube.class, "setClusterRotation", double.class), + Math.PI / 180.0)); + + // RadiusRingComponent + + // Bulkhead + setters.put("RadiusRingComponent:innerradius", new DoubleSetter( + Reflection.findMethodStatic(RadiusRingComponent.class, "setInnerRadius", double.class))); + setters.put("Bulkhead:outerradius", new DoubleSetter( + Reflection.findMethodStatic(Bulkhead.class, "setOuterRadius", double.class), + "auto", + Reflection.findMethodStatic(Bulkhead.class, "setOuterRadiusAutomatic", boolean.class))); + + // CenteringRing + setters.put("CenteringRing:innerradius", new DoubleSetter( + Reflection.findMethodStatic(CenteringRing.class, "setInnerRadius", double.class), + "auto", + Reflection.findMethodStatic(CenteringRing.class, "setInnerRadiusAutomatic", boolean.class))); + setters.put("CenteringRing:outerradius", new DoubleSetter( + Reflection.findMethodStatic(CenteringRing.class, "setOuterRadius", double.class), + "auto", + Reflection.findMethodStatic(CenteringRing.class, "setOuterRadiusAutomatic", boolean.class))); + + + // MassObject + setters.put("MassObject:packedlength", new DoubleSetter( + Reflection.findMethodStatic(MassObject.class, "setLength", double.class))); + setters.put("MassObject:packedradius", new DoubleSetter( + Reflection.findMethodStatic(MassObject.class, "setRadius", double.class))); + setters.put("MassObject:radialposition", new DoubleSetter( + Reflection.findMethodStatic(MassObject.class, "setRadialPosition", double.class))); + setters.put("MassObject:radialdirection", new DoubleSetter( + Reflection.findMethodStatic(MassObject.class, "setRadialDirection", double.class), + Math.PI / 180.0)); + + // MassComponent + setters.put("MassComponent:mass", new DoubleSetter( + Reflection.findMethodStatic(MassComponent.class, "setComponentMass", double.class))); + + // ShockCord + setters.put("ShockCord:cordlength", new DoubleSetter( + Reflection.findMethodStatic(ShockCord.class, "setCordLength", double.class))); + setters.put("ShockCord:material", new MaterialSetter( + Reflection.findMethodStatic(ShockCord.class, "setMaterial", Material.class), + Material.Type.LINE)); + + // RecoveryDevice + setters.put("RecoveryDevice:cd", new DoubleSetter( + Reflection.findMethodStatic(RecoveryDevice.class, "setCD", double.class), + "auto", + Reflection.findMethodStatic(RecoveryDevice.class, "setCDAutomatic", boolean.class))); + setters.put("RecoveryDevice:deployevent", new EnumSetter( + Reflection.findMethodStatic(RecoveryDevice.class, "setDeployEvent", RecoveryDevice.DeployEvent.class), + RecoveryDevice.DeployEvent.class)); + setters.put("RecoveryDevice:deployaltitude", new DoubleSetter( + Reflection.findMethodStatic(RecoveryDevice.class, "setDeployAltitude", double.class))); + setters.put("RecoveryDevice:deploydelay", new DoubleSetter( + Reflection.findMethodStatic(RecoveryDevice.class, "setDeployDelay", double.class))); + setters.put("RecoveryDevice:material", new MaterialSetter( + Reflection.findMethodStatic(RecoveryDevice.class, "setMaterial", Material.class), + Material.Type.SURFACE)); + + // Parachute + setters.put("Parachute:diameter", new DoubleSetter( + Reflection.findMethodStatic(Parachute.class, "setDiameter", double.class))); + setters.put("Parachute:linecount", new IntSetter( + Reflection.findMethodStatic(Parachute.class, "setLineCount", int.class))); + setters.put("Parachute:linelength", new DoubleSetter( + Reflection.findMethodStatic(Parachute.class, "setLineLength", double.class))); + setters.put("Parachute:linematerial", new MaterialSetter( + Reflection.findMethodStatic(Parachute.class, "setLineMaterial", Material.class), + Material.Type.LINE)); + + // Streamer + setters.put("Streamer:striplength", new DoubleSetter( + Reflection.findMethodStatic(Streamer.class, "setStripLength", double.class))); + setters.put("Streamer:stripwidth", new DoubleSetter( + Reflection.findMethodStatic(Streamer.class, "setStripWidth", double.class))); + + // Rocket + // handled by separate handler + setters.put("Rocket:referencetype", new EnumSetter( + Reflection.findMethodStatic(Rocket.class, "setReferenceType", ReferenceType.class), + ReferenceType.class)); + setters.put("Rocket:customreference", new DoubleSetter( + Reflection.findMethodStatic(Rocket.class, "setCustomReferenceLength", double.class))); + setters.put("Rocket:designer", new StringSetter( + Reflection.findMethodStatic(Rocket.class, "setDesigner", String.class))); + setters.put("Rocket:revision", new StringSetter( + Reflection.findMethodStatic(Rocket.class, "setRevision", String.class))); + } + + + /** + * Search for a enum value that has the corresponding name as an XML value. The current + * conversion from enum name to XML value is to lowercase the name and strip out all + * underscore characters. This method returns a match to these criteria, or null + * if no such enum exists. + * + * @param then enum type. + * @param name the XML value, null ok. + * @param enumClass the class of the enum. + * @return the found enum value, or null. + */ + public static > Enum findEnum(String name, Class> enumClass) { + + if (name == null) + return null; + name = name.trim(); + for (Enum e: enumClass.getEnumConstants()) { + if (e.name().toLowerCase().replace("_", "").equals(name)) { + return e; + } + } + return null; + } + + + /** + * Convert a string to a double including formatting specifications of the OpenRocket + * file format. This accepts all formatting that is valid for + * Double.parseDouble(s) and a few others as well ("Inf", "-Inf"). + * + * @param s the string to parse. + * @return the numerical value. + * @throws NumberFormatException the the string cannot be parsed. + */ + public static double stringToDouble(String s) throws NumberFormatException { + if (s == null) + throw new NumberFormatException("null string"); + if (s.equalsIgnoreCase("NaN")) + return Double.NaN; + if (s.equalsIgnoreCase("Inf")) + return Double.POSITIVE_INFINITY; + if (s.equalsIgnoreCase("-Inf")) + return Double.NEGATIVE_INFINITY; + return Double.parseDouble(s); + } +} + + + + +/** + * The actual handler class. Contains the necessary methods for parsing the SAX source. + */ +class DelegatorHandler extends DefaultHandler { + private final WarningSet warnings; + + private final Stack handlerStack = new Stack(); + private final Stack elementData = new Stack(); + private final Stack> elementAttributes = new Stack>(); + + + // Ignore all elements as long as ignore > 0 + private int ignore = 0; + + + public DelegatorHandler(ElementHandler initialHandler, WarningSet warnings) { + this.warnings = warnings; + handlerStack.add(initialHandler); + elementData.add(new StringBuilder()); // Just in case + } + + + ///////// SAX handlers + + @Override + public void startElement(String uri, String localName, String name, + Attributes attributes) throws SAXException { + + // Check for ignore + if (ignore > 0) { + ignore++; + return; + } + + // Check for unknown namespace + if (!uri.equals("")) { + warnings.add(Warning.fromString("Unknown namespace element '" + uri + + "' encountered, ignoring.")); + ignore++; + return; + } + + // Add layer to data stacks + elementData.push(new StringBuilder()); + elementAttributes.push(copyAttributes(attributes)); + + // Call the handler + ElementHandler h = handlerStack.peek(); + h = h.openElement(localName, elementAttributes.peek(), warnings); + if (h != null) { + handlerStack.push(h); + } else { + // Start ignoring elements + ignore++; + } + } + + + /** + * Stores encountered characters in the elementData stack. + */ + @Override + public void characters(char[] chars, int start, int length) throws SAXException { + // Check for ignore + if (ignore > 0) + return; + + StringBuilder sb = elementData.peek(); + sb.append(chars, start, length); + } + + + /** + * Removes the last layer from the stack. + */ + @Override + public void endElement(String uri, String localName, String name) throws SAXException { + + // Check for ignore + if (ignore > 0) { + ignore--; + return; + } + + // Remove data from stack + String data = elementData.pop().toString(); // throws on error + HashMap attr = elementAttributes.pop(); + + // Remove last handler and call the next one + ElementHandler h; + + h = handlerStack.pop(); + h.endHandler(localName, attr, data, warnings); + + h = handlerStack.peek(); + h.closeElement(localName, attr, data, warnings); + } + + + private static HashMap copyAttributes(Attributes atts) { + HashMap ret = new HashMap(); + for (int i = 0; i < atts.getLength(); i++) { + ret.put(atts.getLocalName(i), atts.getValue(i)); + } + return ret; + } +} + + + + +abstract class ElementHandler { + + /** + * Called when an opening element is encountered. Returns the handler that will handle + * the elements within that element, or null if the element and all of + * its contents is to be ignored. + * + * @param element the element name. + * @param attributes attributes of the element. + * @param warnings the warning set to store warnings in. + * @return the handler that handles elements encountered within this element, + * or null if the element is to be ignored. + */ + public abstract ElementHandler openElement(String element, + HashMap attributes, WarningSet warnings); + + /** + * Called when an element is closed. The default implementation checks whether there is + * any non-space text within the element and if there exists any attributes, and adds + * a warning of both. This can be used at the and of the method to check for + * spurious data. + * + * @param element the element name. + * @param attributes attributes of the element. + * @param content the textual content of the element. + * @param warnings the warning set to store warnings in. + */ + public void closeElement(String element, HashMap attributes, + String content, WarningSet warnings) { + + if (!content.trim().equals("")) { + warnings.add(Warning.fromString("Unknown text in element " + element + + ", ignoring.")); + } + if (!attributes.isEmpty()) { + warnings.add(Warning.fromString("Unknown attributes in element " + element + + ", ignoring.")); + } + } + + + /** + * Called when the element block that this handler is handling ends. + * The default implementation is a no-op. + * + * @param warnings the warning set to store warnings in. + */ + public void endHandler(String element, HashMap attributes, + String content, WarningSet warnings) { + // No-op + } + +} + + +/** + * The starting point of the handlers. Accepts a single element and hands + * the contents to be read by a OpenRocketContentsHandler. + */ +class OpenRocketHandler extends ElementHandler { + private OpenRocketContentHandler handler = null; + + /** + * Return the OpenRocketDocument read from the file, or null if a document + * has not been read yet. + * + * @return the document read, or null. + */ + public OpenRocketDocument getDocument() { + return handler.getDocument(); + } + + @Override + public ElementHandler openElement(String element, HashMap attributes, + WarningSet warnings) { + + // Check for unknown elements + if (!element.equals("openrocket")) { + warnings.add(Warning.fromString("Unknown element " + element + ", ignoring.")); + return null; + } + + // Check for first call + if (handler != null) { + warnings.add(Warning.fromString("Multiple document elements found, ignoring later " + + "ones.")); + return null; + } + + // Check version number + String version = null; + String docVersion = attributes.remove("version"); + for (String v : DocumentConfig.SUPPORTED_VERSIONS) { + if (v.equals(docVersion)) { + version = v; + break; + } + } + if (version == null) { + if (docVersion != null) + warnings.add(Warning.fromString("Unsupported document version " + + docVersion + ", attempting to read anyway.")); + else + warnings.add(Warning.fromString("Unsupported document version, attempting to" + + " read anyway.")); + } + + handler = new OpenRocketContentHandler(); + return handler; + } +} + + +/** + * Handles the content of the tag. + */ +class OpenRocketContentHandler extends ElementHandler { + private final OpenRocketDocument doc; + private final Rocket rocket; + + private boolean rocketDefined = false; + private boolean simulationsDefined = false; + + public OpenRocketContentHandler() { + this.rocket = new Rocket(); + this.doc = new OpenRocketDocument(rocket); + } + + + public OpenRocketDocument getDocument() { + if (!rocketDefined) + return null; + return doc; + } + + @Override + public ElementHandler openElement(String element, HashMap attributes, + WarningSet warnings) { + + if (element.equals("rocket")) { + if (rocketDefined) { + warnings.add(Warning + .fromString("Multiple rocket designs within one document, " + + "ignoring later ones.")); + return null; + } + rocketDefined = true; + return new ComponentParameterHandler(rocket); + } + + if (element.equals("simulations")) { + if (simulationsDefined) { + warnings.add(Warning + .fromString("Multiple simulation definitions within one document, " + + "ignoring later ones.")); + return null; + } + simulationsDefined = true; + return new SimulationsHandler(doc); + } + + warnings.add(Warning.fromString("Unknown element " + element + ", ignoring.")); + + return null; + } +} + + +/** + * An element handler that does not allow any sub-elements. If any are encountered + * a warning is generated and they are ignored. + */ +class PlainTextHandler extends ElementHandler { + public static final PlainTextHandler INSTANCE = new PlainTextHandler(); + + private PlainTextHandler() { + } + + @Override + public ElementHandler openElement(String element, HashMap attributes, + WarningSet warnings) { + warnings.add(Warning.fromString("Unknown element " + element + ", ignoring.")); + return null; + } + + @Override + public void closeElement(String element, HashMap attributes, + String content, WarningSet warnings) { + // Warning from openElement is sufficient. + } +} + + + +/** + * A handler that creates components from the corresponding elements. The control of the + * contents is passed on to ComponentParameterHandler. + */ +class ComponentHandler extends ElementHandler { + private final RocketComponent parent; + + public ComponentHandler(RocketComponent parent) { + this.parent = parent; + } + + @Override + public ElementHandler openElement(String element, HashMap attributes, + WarningSet warnings) { + + // Attempt to construct new component + Constructor constructor = DocumentConfig.constructors + .get(element); + if (constructor == null) { + warnings.add(Warning.fromString("Unknown element " + element + ", ignoring.")); + return null; + } + + RocketComponent c; + try { + c = constructor.newInstance(); + } catch (InstantiationException e) { + throw new RuntimeException("Error constructing component.", e); + } catch (IllegalAccessException e) { + throw new RuntimeException("Error constructing component.", e); + } catch (InvocationTargetException e) { + throw new RuntimeException("Error constructing component.", e); + } + + parent.addChild(c); + + return new ComponentParameterHandler(c); + } +} + + +/** + * A handler that populates the parameters of a previously constructed rocket component. + * This uses the setters, or delegates the handling to another handler for specific + * elements. + */ +class ComponentParameterHandler extends ElementHandler { + private final RocketComponent component; + + public ComponentParameterHandler(RocketComponent c) { + this.component = c; + } + + @Override + public ElementHandler openElement(String element, HashMap attributes, + WarningSet warnings) { + + // Check for specific elements that contain other elements + if (element.equals("subcomponents")) { + return new ComponentHandler(component); + } + if (element.equals("motormount")) { + if (!(component instanceof MotorMount)) { + warnings.add(Warning.fromString("Illegal component defined as motor mount.")); + return null; + } + return new MotorMountHandler((MotorMount)component); + } + if (element.equals("finpoints")) { + if (!(component instanceof FreeformFinSet)) { + warnings.add(Warning.fromString("Illegal component defined for fin points.")); + return null; + } + return new FinSetPointHandler((FreeformFinSet)component); + } + if (element.equals("motorconfiguration")) { + if (!(component instanceof Rocket)) { + warnings.add(Warning.fromString("Illegal component defined for motor configuration.")); + return null; + } + return new MotorConfigurationHandler((Rocket)component); + } + + + return PlainTextHandler.INSTANCE; + } + + @Override + public void closeElement(String element, HashMap attributes, + String content, WarningSet warnings) { + + if (element.equals("subcomponents") || element.equals("motormount") || + element.equals("finpoints") || element.equals("motorconfiguration")) { + return; + } + + // Search for the correct setter class + + Class c; + for (c = component.getClass(); c != null; c = c.getSuperclass()) { + String setterKey = c.getSimpleName() + ":" + element; + Setter s = DocumentConfig.setters.get(setterKey); + if (s != null) { + // Setter found + System.out.println("Calling with key "+setterKey); + s.set(component, content, attributes, warnings); + break; + } + if (DocumentConfig.setters.containsKey(setterKey)) { + // Key exists but is null -> invalid parameter + c = null; + break; + } + } + if (c == null) { + warnings.add(Warning.fromString("Unknown parameter type " + element + " for " + + component.getComponentName())); + } + } +} + + +/** + * A handler that reads the specifications within the freeformfinset's + * elements. + */ +class FinSetPointHandler extends ElementHandler { + private final FreeformFinSet finset; + private final ArrayList coordinates = new ArrayList(); + + public FinSetPointHandler(FreeformFinSet finset) { + this.finset = finset; + } + + @Override + public ElementHandler openElement(String element, HashMap attributes, + WarningSet warnings) { + return PlainTextHandler.INSTANCE; + } + + + @Override + public void closeElement(String element, HashMap attributes, + String content, WarningSet warnings) { + + String strx = attributes.remove("x"); + String stry = attributes.remove("y"); + if (strx == null || stry == null) { + warnings.add(Warning.fromString("Illegal fin points specification, ignoring.")); + return; + } + try { + double x = Double.parseDouble(strx); + double y = Double.parseDouble(stry); + coordinates.add(new Coordinate(x,y)); + } catch (NumberFormatException e) { + warnings.add(Warning.fromString("Illegal fin points specification, ignoring.")); + return; + } + + super.closeElement(element, attributes, content, warnings); + } + + @Override + public void endHandler(String element, HashMap attributes, + String content, WarningSet warnings) { + try { + finset.setPoints(coordinates.toArray(new Coordinate[0])); + } catch (IllegalArgumentException e) { + warnings.add(Warning.fromString("Freeform fin set point definitions illegal, ignoring.")); + } + } +} + + +class MotorMountHandler extends ElementHandler { + private final MotorMount mount; + private MotorHandler motorHandler; + + public MotorMountHandler(MotorMount mount) { + this.mount = mount; + mount.setMotorMount(true); + } + + @Override + public ElementHandler openElement(String element, HashMap attributes, + WarningSet warnings) { + + if (element.equals("motor")) { + motorHandler = new MotorHandler(); + return motorHandler; + } + + if (element.equals("ignitionevent") || + element.equals("ignitiondelay") || + element.equals("overhang")) { + return PlainTextHandler.INSTANCE; + } + + warnings.add(Warning.fromString("Unknown element '"+element+"' encountered, ignoring.")); + return null; + } + + + + @Override + public void closeElement(String element, HashMap attributes, + String content, WarningSet warnings) { + + if (element.equals("motor")) { + String id = attributes.get("configid"); + if (id == null || id.equals("")) { + warnings.add(Warning.fromString("Illegal motor specification, ignoring.")); + return; + } + + Motor motor = motorHandler.getMotor(warnings); + mount.setMotor(id, motor); + mount.setMotorDelay(id, motorHandler.getDelay(warnings)); + return; + } + + if (element.equals("ignitionevent")) { + MotorMount.IgnitionEvent event = null; + for (MotorMount.IgnitionEvent e : MotorMount.IgnitionEvent.values()) { + if (e.name().toLowerCase().replaceAll("_", "").equals(content)) { + event = e; + break; + } + } + if (event == null) { + warnings.add(Warning.fromString("Unknown ignition event type '"+content+"', ignoring.")); + return; + } + mount.setIgnitionEvent(event); + return; + } + + if (element.equals("ignitiondelay")) { + double d; + try { + d = Double.parseDouble(content); + } catch (NumberFormatException nfe) { + warnings.add(Warning.fromString("Illegal ignition delay specified, ignoring.")); + return; + } + mount.setIgnitionDelay(d); + return; + } + + if (element.equals("overhang")) { + double d; + try { + d = Double.parseDouble(content); + } catch (NumberFormatException nfe) { + warnings.add(Warning.fromString("Illegal overhang specified, ignoring.")); + return; + } + mount.setMotorOverhang(d); + return; + } + + super.closeElement(element, attributes, content, warnings); + } +} + + + + +class MotorConfigurationHandler extends ElementHandler { + private final Rocket rocket; + private String name = null; + private boolean inNameElement = false; + + public MotorConfigurationHandler(Rocket rocket) { + this.rocket = rocket; + } + + @Override + public ElementHandler openElement(String element, HashMap attributes, + WarningSet warnings) { + + if (inNameElement || !element.equals("name")) { + warnings.add(Warning.FILE_INVALID_PARAMETER); + return null; + } + inNameElement = true; + + return PlainTextHandler.INSTANCE; + } + + @Override + public void closeElement(String element, HashMap attributes, + String content, WarningSet warnings) { + name = content; + } + + @Override + public void endHandler(String element, HashMap attributes, + String content, WarningSet warnings) { + + String configid = attributes.remove("configid"); + if (configid == null || configid.equals("")) { + warnings.add(Warning.FILE_INVALID_PARAMETER); + return; + } + + if (!rocket.addMotorConfigurationID(configid)) { + warnings.add("Duplicate motor configuration ID used."); + return; + } + + if (name != null && name.trim().length() > 0) { + rocket.setMotorConfigurationName(configid, name); + } + + if ("true".equals(attributes.remove("default"))) { + rocket.getDefaultConfiguration().setMotorConfigurationID(configid); + } + + super.closeElement(element, attributes, content, warnings); + } +} + + +class MotorHandler extends ElementHandler { + private Motor.Type type = null; + private String manufacturer = null; + private String designation = null; + private double diameter = Double.NaN; + private double length = Double.NaN; + private double delay = Double.NaN; + + @Override + public ElementHandler openElement(String element, HashMap attributes, + WarningSet warnings) { + return PlainTextHandler.INSTANCE; + } + + + /** + * Return the motor to use, or null. + */ + public Motor getMotor(WarningSet warnings) { + if (designation == null) { + warnings.add(Warning.fromString("No motor specified, ignoring.")); + return null; + } + Motor[] motors = Databases.findMotors(type, manufacturer, designation, diameter, length); + if (motors.length == 0) { + String str = "No motor with designation '"+designation+"'"; + if (manufacturer != null) + str += " for manufacturer '" + manufacturer + "'"; + warnings.add(Warning.fromString(str + " found.")); + return null; + } + if (motors.length > 1) { + String str = "Multiple motors with designation '"+designation+"'"; + if (manufacturer != null) + str += " for manufacturer '" + manufacturer + "'"; + warnings.add(Warning.fromString(str + " found, one chosen arbitrarily.")); + } + return motors[0]; + } + + + /** + * Return the delay to use for the motor. + */ + public double getDelay(WarningSet warnings) { + if (Double.isNaN(delay)) { + warnings.add(Warning.fromString("Motor delay not specified, assuming no ejection charge.")); + return Motor.PLUGGED; + } + return delay; + } + + + @Override + public void closeElement(String element, HashMap attributes, + String content, WarningSet warnings) { + + content = content.trim(); + + if (element.equals("type")) { + + // Motor type + type = null; + for (Motor.Type t: Motor.Type.values()) { + if (t.name().toLowerCase().equals(content)) { + type = t; + break; + } + } + if (type == null) { + warnings.add(Warning.fromString("Unknown motor type '"+content+"', ignoring.")); + } + + } else if (element.equals("manufacturer")) { + + // Manufacturer + manufacturer = content; + + } else if (element.equals("designation")) { + + // Designation + designation = content; + + } else if (element.equals("diameter")) { + + // Diameter + diameter = Double.NaN; + try { + diameter = Double.parseDouble(content); + } catch (NumberFormatException e) { + // Ignore + } + if (Double.isNaN(diameter)) { + warnings.add(Warning.fromString("Illegal motor diameter specified, ignoring.")); + } + + } else if (element.equals("length")) { + + // Length + length = Double.NaN; + try { + length = Double.parseDouble(content); + } catch (NumberFormatException ignore) { } + + if (Double.isNaN(length)) { + warnings.add(Warning.fromString("Illegal motor diameter specified, ignoring.")); + } + + } else if (element.equals("delay")) { + + // Delay + delay = Double.NaN; + if (content.equals("none")) { + delay = Motor.PLUGGED; + } else { + try { + delay = Double.parseDouble(content); + } catch (NumberFormatException ignore) { } + + if (Double.isNaN(delay)) { + warnings.add(Warning.fromString("Illegal motor delay specified, ignoring.")); + } + + } + + } else { + super.closeElement(element, attributes, content, warnings); + } + } + +} + + + +class SimulationsHandler extends ElementHandler { + private final OpenRocketDocument doc; + private SingleSimulationHandler handler; + + public SimulationsHandler(OpenRocketDocument doc) { + this.doc = doc; + } + + @Override + public ElementHandler openElement(String element, HashMap attributes, + WarningSet warnings) { + + if (!element.equals("simulation")) { + warnings.add("Unknown element '"+element+"', ignoring."); + return null; + } + + handler = new SingleSimulationHandler(doc); + return handler; + } +} + +class SingleSimulationHandler extends ElementHandler { + + private final OpenRocketDocument doc; + + private String name; + + private SimulationConditionsHandler conditionHandler; + private FlightDataHandler dataHandler; + + private final List listeners = new ArrayList(); + + public SingleSimulationHandler(OpenRocketDocument doc) { + this.doc = doc; + } + + + + @Override + public ElementHandler openElement(String element, HashMap attributes, + WarningSet warnings) { + + if (element.equals("name") || element.equals("simulator") || + element.equals("calculator") || element.equals("listener")) { + return PlainTextHandler.INSTANCE; + } else if (element.equals("conditions")) { + conditionHandler = new SimulationConditionsHandler(doc.getRocket()); + return conditionHandler; + } else if (element.equals("flightdata")) { + dataHandler = new FlightDataHandler(); + return dataHandler; + } else { + warnings.add("Unknown element '"+element+"', ignoring."); + return null; + } + } + + @Override + public void closeElement(String element, HashMap attributes, + String content, WarningSet warnings) { + + if (element.equals("name")) { + name = content; + } else if (element.equals("simulator")) { + if (!content.equals("RK4Simulator")) { + warnings.add("Unknown simulator specified, ignoring."); + } + } else if (element.equals("calculator")) { + if (!content.equals("BarrowmanSimulator")) { + warnings.add("Unknown calculator specified, ignoring."); + } + } else if (element.equals("listener") && content.trim().length() > 0) { + listeners.add(content.trim()); + } + + } + + @Override + public void endHandler(String element, HashMap attributes, + String content, WarningSet warnings) { + + String s = attributes.get("status"); + Simulation.Status status = (Status) DocumentConfig.findEnum(s, Simulation.Status.class); + if (status == null) { + warnings.add("Simulation status unknown, assuming outdated."); + status = Simulation.Status.OUTDATED; + } + + SimulationConditions conditions; + if (conditionHandler != null) { + conditions = conditionHandler.getConditions(); + } else { + warnings.add("Simulation conditions not defined, using defaults."); + conditions = new SimulationConditions(doc.getRocket()); + } + + if (name == null) + name = "Simulation"; + + FlightData data; + if (dataHandler == null) + data = null; + else + data = dataHandler.getFlightData(); + + Simulation simulation = new Simulation(doc.getRocket(), status, name, + conditions, listeners, data); + + doc.addSimulation(simulation); + } +} + + + +class SimulationConditionsHandler extends ElementHandler { + private SimulationConditions conditions; + private AtmosphereHandler atmosphereHandler; + + public SimulationConditionsHandler(Rocket rocket) { + conditions = new SimulationConditions(rocket); + } + + public SimulationConditions getConditions() { + return conditions; + } + + @Override + public ElementHandler openElement(String element, HashMap attributes, + WarningSet warnings) { + if (element.equals("atmosphere")) { + atmosphereHandler = new AtmosphereHandler(attributes.get("model")); + return atmosphereHandler; + } + return PlainTextHandler.INSTANCE; + } + + @Override + public void closeElement(String element, HashMap attributes, + String content, WarningSet warnings) { + + double d = Double.NaN; + try { + d = Double.parseDouble(content); + } catch (NumberFormatException ignore) { } + + + if (element.equals("configid")) { + if (content.equals("")) { + conditions.setMotorConfigurationID(null); + } else { + conditions.setMotorConfigurationID(content); + } + } else if (element.equals("launchrodlength")) { + if (Double.isNaN(d)) { + warnings.add("Illegal launch rod length defined, ignoring."); + } else { + conditions.setLaunchRodLength(d); + } + } else if (element.equals("launchrodangle")) { + if (Double.isNaN(d)) { + warnings.add("Illegal launch rod angle defined, ignoring."); + } else { + conditions.setLaunchRodAngle(d*Math.PI/180); + } + } else if (element.equals("launchroddirection")) { + if (Double.isNaN(d)) { + warnings.add("Illegal launch rod direction defined, ignoring."); + } else { + conditions.setLaunchRodDirection(d*Math.PI/180); + } + } else if (element.equals("windaverage")) { + if (Double.isNaN(d)) { + warnings.add("Illegal average windspeed defined, ignoring."); + } else { + conditions.setWindSpeedAverage(d); + } + } else if (element.equals("windturbulence")) { + if (Double.isNaN(d)) { + warnings.add("Illegal wind turbulence intensity defined, ignoring."); + } else { + conditions.setWindTurbulenceIntensity(d); + } + } else if (element.equals("launchaltitude")) { + if (Double.isNaN(d)) { + warnings.add("Illegal launch altitude defined, ignoring."); + } else { + conditions.setLaunchAltitude(d); + } + } else if (element.equals("launchlatitude")) { + if (Double.isNaN(d)) { + warnings.add("Illegal launch latitude defined, ignoring."); + } else { + conditions.setLaunchLatitude(d); + } + } else if (element.equals("atmosphere")) { + atmosphereHandler.storeSettings(conditions, warnings); + } else if (element.equals("timestep")) { + if (Double.isNaN(d)) { + warnings.add("Illegal time step defined, ignoring."); + } else { + conditions.setTimeStep(d); + } + } + } +} + + +class AtmosphereHandler extends ElementHandler { + private final String model; + private double temperature = Double.NaN; + private double pressure = Double.NaN; + + public AtmosphereHandler(String model) { + this.model = model; + } + + @Override + public ElementHandler openElement(String element, HashMap attributes, + WarningSet warnings) { + return PlainTextHandler.INSTANCE; + } + + @Override + public void closeElement(String element, HashMap attributes, + String content, WarningSet warnings) { + + double d = Double.NaN; + try { + d = Double.parseDouble(content); + } catch (NumberFormatException ignore) { } + + if (element.equals("basetemperature")) { + if (Double.isNaN(d)) { + warnings.add("Illegal base temperature specified, ignoring."); + } + temperature = d; + } else if (element.equals("basepressure")) { + if (Double.isNaN(d)) { + warnings.add("Illegal base pressure specified, ignoring."); + } + pressure = d; + } else { + super.closeElement(element, attributes, content, warnings); + } + } + + + public void storeSettings(SimulationConditions cond, WarningSet warnings) { + if (!Double.isNaN(pressure)) { + cond.setLaunchPressure(pressure); + } + if (!Double.isNaN(temperature)) { + cond.setLaunchTemperature(temperature); + } + + if ("isa".equals(model)) { + cond.setISAAtmosphere(true); + } else if ("extendedisa".equals(model)){ + cond.setISAAtmosphere(false); + } else { + cond.setISAAtmosphere(true); + warnings.add("Unknown atmospheric model, using ISA."); + } + } + +} + + +class FlightDataHandler extends ElementHandler { + + private FlightDataBranchHandler dataHandler; + private WarningSet warningSet = new WarningSet(); + private List branches = new ArrayList(); + + private FlightData data; + + public FlightData getFlightData() { + return data; + } + + @Override + public ElementHandler openElement(String element, HashMap attributes, + WarningSet warnings) { + + if (element.equals("warning")) { + return PlainTextHandler.INSTANCE; + } + if (element.equals("databranch")) { + if (attributes.get("name") == null || attributes.get("types")==null) { + warnings.add("Illegal flight data definition, ignoring."); + return null; + } + dataHandler = new FlightDataBranchHandler(attributes.get("name"), + attributes.get("types")); + return dataHandler; + } + + warnings.add("Unknown element '"+element+"' encountered, ignoring."); + return null; + } + + + @Override + public void closeElement(String element, HashMap attributes, + String content, WarningSet warnings) { + + if (element.equals("databranch")) { + FlightDataBranch branch = dataHandler.getBranch(); + if (branch.getLength() > 0) { + branches.add(branch); + } + } else if (element.equals("warning")) { + warningSet.add(Warning.fromString(content)); + } + } + + + @Override + public void endHandler(String element, HashMap attributes, + String content, WarningSet warnings) { + + if (branches.size() > 0) { + data = new FlightData(branches.toArray(new FlightDataBranch[0])); + } else { + double maxAltitude = Double.NaN; + double maxVelocity = Double.NaN; + double maxAcceleration = Double.NaN; + double maxMach = Double.NaN; + double timeToApogee = Double.NaN; + double flightTime = Double.NaN; + double groundHitVelocity = Double.NaN; + + try { + maxAltitude = DocumentConfig.stringToDouble(attributes.get("maxaltitude")); + } catch (NumberFormatException ignore) { } + try { + maxVelocity = DocumentConfig.stringToDouble(attributes.get("maxvelocity")); + } catch (NumberFormatException ignore) { } + try { + maxAcceleration = DocumentConfig.stringToDouble(attributes.get("maxacceleration")); + } catch (NumberFormatException ignore) { } + try { + maxMach = DocumentConfig.stringToDouble(attributes.get("maxmach")); + } catch (NumberFormatException ignore) { } + try { + timeToApogee = DocumentConfig.stringToDouble(attributes.get("timetoapogee")); + } catch (NumberFormatException ignore) { } + try { + flightTime = DocumentConfig.stringToDouble(attributes.get("flighttime")); + } catch (NumberFormatException ignore) { } + try { + groundHitVelocity = + DocumentConfig.stringToDouble(attributes.get("groundhitvelocity")); + } catch (NumberFormatException ignore) { } + + data = new FlightData(maxAltitude, maxVelocity, maxAcceleration, maxMach, + timeToApogee, flightTime, groundHitVelocity); + } + + data.getWarningSet().addAll(warningSet); + } + + +} + + +class FlightDataBranchHandler extends ElementHandler { + private final FlightDataBranch.Type[] types; + private final FlightDataBranch branch; + + public FlightDataBranchHandler(String name, String typeList) { + String[] split = typeList.split(","); + types = new FlightDataBranch.Type[split.length]; + for (int i=0; i < split.length; i++) { + types[i] = FlightDataBranch.getType(split[i], UnitGroup.UNITS_NONE); + } + + // TODO: LOW: May throw an IllegalArgumentException + branch = new FlightDataBranch(name, types); + } + + public FlightDataBranch getBranch() { + branch.immute(); + return branch; + } + + @Override + public ElementHandler openElement(String element, HashMap attributes, + WarningSet warnings) { + + if (element.equals("datapoint")) + return PlainTextHandler.INSTANCE; + if (element.equals("event")) + return PlainTextHandler.INSTANCE; + + warnings.add("Unknown element '"+element+"' encountered, ignoring."); + return null; + } + + + @Override + public void closeElement(String element, HashMap attributes, + String content, WarningSet warnings) { + + if (element.equals("event")) { + double time; + FlightEvent.Type type; + + try { + time = DocumentConfig.stringToDouble(attributes.get("time")); + } catch (NumberFormatException e) { + warnings.add("Illegal event specification, ignoring."); + return; + } + + type = (Type) DocumentConfig.findEnum(attributes.get("type"), FlightEvent.Type.class); + if (type == null) { + warnings.add("Illegal event specification, ignoring."); + return; + } + + branch.addEvent(time, new FlightEvent(type, time)); + return; + } + + if (!element.equals("datapoint")) { + warnings.add("Unknown element '"+element+"' encountered, ignoring."); + return; + } + + // element == "datapoint" + + + // Check line format + String[] split = content.split(","); + if (split.length != types.length) { + warnings.add("Data point did not contain correct amount of values, ignoring point."); + return; + } + + // Parse the doubles + double[] values = new double[split.length]; + for (int i=0; i < values.length; i++) { + try { + values[i] = DocumentConfig.stringToDouble(split[i]); + } catch (NumberFormatException e) { + warnings.add("Data point format error, ignoring point."); + return; + } + } + + // Add point to branch + branch.addPoint(); + for (int i=0; i < types.length; i++) { + branch.setValue(types[i], values[i]); + } + } +} + + + + + + +///////////////// Setters implementation + + +//// Interface +interface Setter { + /** + * Set the specified value to the given component. + * + * @param component the component to which to set. + * @param value the value within the element. + * @param attributes attributes for the element. + * @param warnings the warning set to use. + */ + public void set(RocketComponent component, String value, + HashMap attributes, WarningSet warnings); +} + + +//// StringSetter - sets the value to the contained String +class StringSetter implements Setter { + private final Reflection.Method setMethod; + + public StringSetter(Reflection.Method set) { + setMethod = set; + } + + public void set(RocketComponent c, String s, HashMap attributes, + WarningSet warnings) { + setMethod.invoke(c, s); + } +} + +//// IntSetter - set an integer value +class IntSetter implements Setter { + private final Reflection.Method setMethod; + + public IntSetter(Reflection.Method set) { + setMethod = set; + } + + public void set(RocketComponent c, String s, HashMap attributes, + WarningSet warnings) { + try { + int n = Integer.parseInt(s); + setMethod.invoke(c, n); + } catch (NumberFormatException e) { + warnings.add(Warning.FILE_INVALID_PARAMETER); + } + } +} + + +//// BooleanSetter - set a boolean value +class BooleanSetter implements Setter { + private final Reflection.Method setMethod; + + public BooleanSetter(Reflection.Method set) { + setMethod = set; + } + + public void set(RocketComponent c, String s, HashMap attributes, + WarningSet warnings) { + + s = s.trim(); + if (s.equalsIgnoreCase("true")) { + setMethod.invoke(c, true); + } else if (s.equalsIgnoreCase("false")) { + setMethod.invoke(c, false); + } else { + warnings.add(Warning.FILE_INVALID_PARAMETER); + } + } +} + + + +//// DoubleSetter - sets a double value or (alternatively) if a specific string is encountered +//// calls a setXXX(boolean) method. +class DoubleSetter implements Setter { + private final Reflection.Method setMethod; + private final String specialString; + private final Reflection.Method specialMethod; + private final double multiplier; + + /** + * Set only the double value. + * @param set set method for the double value. + */ + public DoubleSetter(Reflection.Method set) { + this.setMethod = set; + this.specialString = null; + this.specialMethod = null; + this.multiplier = 1.0; + } + + /** + * Multiply with the given multiplier and set the double value. + * @param set set method for the double value. + * @param mul multiplier. + */ + public DoubleSetter(Reflection.Method set, double mul) { + this.setMethod = set; + this.specialString = null; + this.specialMethod = null; + this.multiplier = mul; + } + + /** + * Set the double value, or if the value equals the special string, use the + * special setter and set it to true. + * + * @param set double setter. + * @param special special string + * @param specialMethod boolean setter. + */ + public DoubleSetter(Reflection.Method set, String special, + Reflection.Method specialMethod) { + this.setMethod = set; + this.specialString = special; + this.specialMethod = specialMethod; + this.multiplier = 1.0; + } + + + public void set(RocketComponent c, String s, HashMap attributes, + WarningSet warnings) { + + s = s.trim(); + + // Check for special case + if (specialMethod != null && s.equalsIgnoreCase(specialString)) { + specialMethod.invoke(c, true); + return; + } + + // Normal case + try { + double d = Double.parseDouble(s); + setMethod.invoke(c, d * multiplier); + } catch (NumberFormatException e) { + warnings.add(Warning.FILE_INVALID_PARAMETER); + } + } +} + + +class OverrideSetter implements Setter { + private final Reflection.Method setMethod; + private final Reflection.Method enabledMethod; + + public OverrideSetter(Reflection.Method set, Reflection.Method enabledMethod) { + this.setMethod = set; + this.enabledMethod = enabledMethod; + } + + public void set(RocketComponent c, String s, HashMap attributes, + WarningSet warnings) { + + try { + double d = Double.parseDouble(s); + setMethod.invoke(c, d); + enabledMethod.invoke(c, true); + } catch (NumberFormatException e) { + warnings.add(Warning.FILE_INVALID_PARAMETER); + } + } +} + +//// EnumSetter - sets a generic enum type +class EnumSetter> implements Setter { + private final Reflection.Method setter; + private final Class enumClass; + + public EnumSetter(Reflection.Method set, Class enumClass) { + this.setter = set; + this.enumClass = enumClass; + } + + @Override + public void set(RocketComponent c, String name, HashMap attributes, + WarningSet warnings) { + + Enum setEnum = DocumentConfig.findEnum(name, enumClass); + if (setEnum == null) { + warnings.add(Warning.FILE_INVALID_PARAMETER); + return; + } + + setter.invoke(c, setEnum); + } +} + + +//// ColorSetter - sets a Color value +class ColorSetter implements Setter { + private final Reflection.Method setMethod; + + public ColorSetter(Reflection.Method set) { + setMethod = set; + } + + public void set(RocketComponent c, String s, HashMap attributes, + WarningSet warnings) { + + String red = attributes.get("red"); + String green = attributes.get("green"); + String blue = attributes.get("blue"); + + if (red == null || green == null || blue == null) { + warnings.add(Warning.FILE_INVALID_PARAMETER); + return; + } + + int r, g, b; + try { + r = Integer.parseInt(red); + g = Integer.parseInt(green); + b = Integer.parseInt(blue); + } catch (NumberFormatException e) { + warnings.add(Warning.FILE_INVALID_PARAMETER); + return; + } + + if (r < 0 || g < 0 || b < 0 || r > 255 || g > 255 || b > 255) { + warnings.add(Warning.FILE_INVALID_PARAMETER); + return; + } + + Color color = new Color(r, g, b); + setMethod.invoke(c, color); + + if (!s.trim().equals("")) { + warnings.add(Warning.FILE_INVALID_PARAMETER); + } + } +} + + + +class MaterialSetter implements Setter { + private final Reflection.Method setMethod; + private final Material.Type type; + + public MaterialSetter(Reflection.Method set, Material.Type type) { + this.setMethod = set; + this.type = type; + } + + public void set(RocketComponent c, String name, HashMap attributes, + WarningSet warnings) { + + Material mat; + + // Check name != "" + name = name.trim(); + if (name.equals("")) { + warnings.add(Warning.fromString("Illegal material specification, ignoring.")); + return; + } + + // Parse density + double density; + String str; + str = attributes.remove("density"); + if (str == null) { + warnings.add(Warning.fromString("Illegal material specification, ignoring.")); + return; + } + try { + density = Double.parseDouble(str); + } catch (NumberFormatException e) { + warnings.add(Warning.fromString("Illegal material specification, ignoring.")); + return; + } + + // Parse thickness +// double thickness = 0; +// str = attributes.remove("thickness"); +// try { +// if (str != null) +// thickness = Double.parseDouble(str); +// } catch (NumberFormatException e){ +// warnings.add(Warning.fromString("Illegal material specification, ignoring.")); +// return; +// } + + // Check type if specified + str = attributes.remove("type"); + if (str != null && !type.name().toLowerCase().equals(str)) { + warnings.add(Warning.fromString("Illegal material type specified, ignoring.")); + return; + } + + mat = Material.newMaterial(type, name, density); + + setMethod.invoke(c, mat); + } +} + + + + +class PositionSetter implements Setter { + + public void set(RocketComponent c, String value, HashMap attributes, + WarningSet warnings) { + + RocketComponent.Position type = (Position) DocumentConfig.findEnum(attributes.get("type"), + RocketComponent.Position.class); + if (type == null) { + warnings.add(Warning.FILE_INVALID_PARAMETER); + return; + } + + double pos; + try { + pos = Double.parseDouble(value); + } catch (NumberFormatException e) { + warnings.add(Warning.FILE_INVALID_PARAMETER); + return; + } + + if (c instanceof FinSet) { + ((FinSet)c).setRelativePosition(type); + c.setPositionValue(pos); + } else if (c instanceof LaunchLug) { + ((LaunchLug)c).setRelativePosition(type); + c.setPositionValue(pos); + } else if (c instanceof InternalComponent) { + ((InternalComponent)c).setRelativePosition(type); + c.setPositionValue(pos); + } else { + warnings.add(Warning.FILE_INVALID_PARAMETER); + } + + } +} + + + +class ClusterConfigurationSetter implements Setter { + + public void set(RocketComponent component, String value, HashMap attributes, + WarningSet warnings) { + + if (!(component instanceof Clusterable)) { + warnings.add("Illegal component defined as cluster."); + return; + } + + ClusterConfiguration config = null; + for (ClusterConfiguration c: ClusterConfiguration.CONFIGURATIONS) { + if (c.getXMLName().equals(value)) { + config = c; + break; + } + } + + if (config == null) { + warnings.add("Illegal cluster configuration specified."); + return; + } + + ((Clusterable)component).setClusterConfiguration(config); + } +} + + diff --git a/src/net/sf/openrocket/file/OpenRocketSaver.java b/src/net/sf/openrocket/file/OpenRocketSaver.java new file mode 100644 index 000000000..970aa2a82 --- /dev/null +++ b/src/net/sf/openrocket/file/OpenRocketSaver.java @@ -0,0 +1,431 @@ +package net.sf.openrocket.file; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.zip.GZIPOutputStream; + +import net.sf.openrocket.aerodynamics.Warning; +import net.sf.openrocket.document.OpenRocketDocument; +import net.sf.openrocket.document.Simulation; +import net.sf.openrocket.document.StorageOptions; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.simulation.FlightData; +import net.sf.openrocket.simulation.FlightDataBranch; +import net.sf.openrocket.simulation.FlightEvent; +import net.sf.openrocket.simulation.SimulationConditions; +import net.sf.openrocket.util.MathUtil; +import net.sf.openrocket.util.Pair; +import net.sf.openrocket.util.Prefs; +import net.sf.openrocket.util.Reflection; + +public class OpenRocketSaver extends RocketSaver { + + /* Remember to update OpenRocketLoader as well! */ + public static final String FILE_VERSION = "1.0"; + + private static final String OPENROCKET_CHARSET = "UTF-8"; + + private static final String METHOD_PACKAGE = "net.sf.openrocket.file.openrocket"; + private static final String METHOD_SUFFIX = "Saver"; + + private int indent; + private Writer dest; + + @Override + public void save(OutputStream output, OpenRocketDocument document, StorageOptions options) + throws IOException { + + if (options.isCompressionEnabled()) { + output = new GZIPOutputStream(output); + } + + dest = new BufferedWriter(new OutputStreamWriter(output, OPENROCKET_CHARSET)); + + + this.indent = 0; + + System.out.println("Writing..."); + + writeln(""); + writeln(""); + indent++; + + // Recursively save the rocket structure + saveComponent(document.getRocket()); + + writeln(""); + + // Save all simulations + writeln(""); + indent++; + boolean first = true; + for (Simulation s: document.getSimulations()) { + if (!first) + writeln(""); + first = false; + saveSimulation(s, options.getSimulationTimeSkip()); + } + indent--; + writeln(""); + + indent--; + writeln(""); + + dest.flush(); + if (output instanceof GZIPOutputStream) + ((GZIPOutputStream)output).finish(); + } + + + + @SuppressWarnings("unchecked") + private void saveComponent(RocketComponent component) throws IOException { + + Reflection.Method m = Reflection.findMethod(METHOD_PACKAGE, component, METHOD_SUFFIX, + "getElements", RocketComponent.class); + if (m==null) { + throw new RuntimeException("Unable to find saving class for component "+ + component.getComponentName()); + } + + // Get the strings to save + List list = (List) m.invokeStatic(component); + int length = list.size(); + + if (length == 0) // Nothing to do + return; + + if (length < 2) { + throw new RuntimeException("BUG, component data length less than two lines."); + } + + // Open element + writeln(list.get(0)); + indent++; + + // Write parameters + for (int i=1; i 0) { + writeln(""); + writeln(""); + indent++; + boolean emptyline = false; + for (RocketComponent subcomponent: component) { + if (emptyline) + writeln(""); + emptyline = true; + saveComponent(subcomponent); + } + indent--; + writeln(""); + } + + // Close element + indent--; + writeln(list.get(length-1)); + } + + + + private void saveSimulation(Simulation simulation, double timeSkip) throws IOException { + SimulationConditions cond = simulation.getConditions(); + + writeln(""); + indent++; + + writeln("" + escapeXML(simulation.getName()) + ""); + // TODO: MEDIUM: Other simulators/calculators + writeln("RK4Simulator"); + writeln("BarrowmanCalculator"); + writeln(""); + indent++; + + writeElement("configid", cond.getMotorConfigurationID()); + writeElement("launchrodlength", cond.getLaunchRodLength()); + writeElement("launchrodangle", cond.getLaunchRodAngle() * 180.0/Math.PI); + writeElement("launchroddirection", cond.getLaunchRodDirection() * 180.0/Math.PI); + writeElement("windaverage", cond.getWindSpeedAverage()); + writeElement("windturbulence", cond.getWindTurbulenceIntensity()); + writeElement("launchaltitude", cond.getLaunchAltitude()); + writeElement("launchlatitude", cond.getLaunchLatitude()); + + if (cond.isISAAtmosphere()) { + writeln(""); + } else { + writeln(""); + indent++; + writeElement("basetemperature", cond.getLaunchTemperature()); + writeElement("basepressure", cond.getLaunchPressure()); + indent--; + writeln(""); + } + + writeElement("timestep", cond.getTimeStep()); + + indent--; + writeln(""); + + + for (String s: simulation.getSimulationListeners()) { + writeElement("listener", escapeXML(s)); + } + + + // Write basic simulation data + + FlightData data = simulation.getSimulatedData(); + if (data != null) { + String str = ""); + } + + indent--; + writeln(""); + + } + + + + private void saveFlightDataBranch(FlightDataBranch branch, double timeSkip) throws IOException { + double previousTime = -100; + + if (branch == null) + return; + + // Retrieve the types from the branch + FlightDataBranch.Type[] types = branch.getTypes(); + + if (types.length == 0) + return; + + // Retrieve the data from the branch + List> data = new ArrayList>(types.length); + for (int i=0; i timeData = branch.get(FlightDataBranch.TYPE_TIME); + if (timeData == null) { + // TODO: MEDIUM: External data may not have time data + throw new IllegalArgumentException("Data did not contain time data"); + } + + // Build the tag + StringBuilder sb = new StringBuilder(); + sb.append(" 0) + sb.append(","); + sb.append(escapeXML(types[i].getName())); + } + sb.append("\">"); + writeln(sb.toString()); + indent++; + + // Write events + for (Pair p: branch.getEvents()) { + writeln(""); + } + + // Write the data + int length = branch.getLength(); + if (length > 0) { + writeDataPointString(data, 0, sb); + previousTime = timeData.get(0); + } + + for (int i=1; i < length-1; i++) { + if (Math.abs(timeData.get(i) - previousTime - timeSkip) < + Math.abs(timeData.get(i+1) - previousTime - timeSkip)) { + writeDataPointString(data, i, sb); + previousTime = timeData.get(i); + } + } + + if (length > 1) { + writeDataPointString(data, length-1, sb); + } + + indent--; + writeln(""); + } + + private void writeDataPointString(List> data, int index, StringBuilder sb) + throws IOException { + sb.setLength(0); + sb.append(""); + for (int j=0; j < data.size(); j++) { + if (j > 0) + sb.append(","); + sb.append(doubleToString(data.get(j).get(index))); + } + sb.append(""); + writeln(sb.toString()); + } + + + + private void writeElement(String element, Object content) throws IOException { + if (content == null) + content = ""; + writeln("<"+element+">"+content+""); + } + + + + private void writeln(String str) throws IOException { + if (str.length() == 0) { + dest.write("\n"); + return; + } + String s=""; + for (int i=0; i= 10.0) { + abs /= 10; + exp++; + } + + String sign = (d < 0) ? "-" : ""; + return sign + String.format((Locale)null, "%.4fe%d", abs, exp); + } + + + + public static void main(String[] arg) { + double d = -0.000000123456789123; + + + for (int i=0; i< 20; i++) { + String str = doubleToString(d); + System.out.println(str + " -> " + Double.parseDouble(str)); + d *= 10; + } + + + System.out.println("Value: "+ Double.parseDouble("1.2345e9")); + + } + + + /** + * Return the XML equivalent of an enum name. + * + * @param e the enum to save. + * @return the corresponding XML name. + */ + public static String enumToXMLName(Enum e) { + return e.name().toLowerCase().replace("_", ""); + } + +} diff --git a/src/net/sf/openrocket/file/RocketLoadException.java b/src/net/sf/openrocket/file/RocketLoadException.java new file mode 100644 index 000000000..2fdd177bd --- /dev/null +++ b/src/net/sf/openrocket/file/RocketLoadException.java @@ -0,0 +1,20 @@ +package net.sf.openrocket.file; + +public class RocketLoadException extends Exception { + + public RocketLoadException() { + } + + public RocketLoadException(String message) { + super(message); + } + + public RocketLoadException(Throwable cause) { + super(cause); + } + + public RocketLoadException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/net/sf/openrocket/file/RocketLoader.java b/src/net/sf/openrocket/file/RocketLoader.java new file mode 100644 index 000000000..13f888ca1 --- /dev/null +++ b/src/net/sf/openrocket/file/RocketLoader.java @@ -0,0 +1,67 @@ +package net.sf.openrocket.file; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; + +import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.document.OpenRocketDocument; + + +public abstract class RocketLoader { + protected final WarningSet warnings = new WarningSet(); + + + /** + * Loads a rocket from the specified File object. + */ + public final OpenRocketDocument load(File source) throws RocketLoadException { + warnings.clear(); + + try { + return load(new BufferedInputStream(new FileInputStream(source))); + } catch (FileNotFoundException e) { + throw new RocketLoadException("File not found: " + source); + } + } + + /** + * Loads a rocket from the specified InputStream. + */ + public final OpenRocketDocument load(InputStream source) throws RocketLoadException { + warnings.clear(); + + try { + return loadFromStream(source); + } catch (RocketLoadException e) { + throw e; + } catch (IOException e) { + throw new RocketLoadException("I/O error: " + e.getMessage()); + } catch (Exception e) { + throw new RocketLoadException("An unknown error occurred. Please report a bug.", e); + } catch (Throwable e) { + throw new RocketLoadException("A serious error occurred and the software may be " + + "unstable. Save your designs and restart OpenRocket.", e); + } + } + + + + /** + * This method is called by the default implementations of {@link #load(File)} + * and {@link #load(InputStream)} to load the rocket. + * + * @throws RocketLoadException if an error occurs during loading. + */ + protected abstract OpenRocketDocument loadFromStream(InputStream source) throws IOException, + RocketLoadException; + + + + public final WarningSet getWarnings() { + return warnings; + } +} diff --git a/src/net/sf/openrocket/file/RocketSaver.java b/src/net/sf/openrocket/file/RocketSaver.java new file mode 100644 index 000000000..b6f8b607c --- /dev/null +++ b/src/net/sf/openrocket/file/RocketSaver.java @@ -0,0 +1,92 @@ +package net.sf.openrocket.file; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import net.sf.openrocket.document.OpenRocketDocument; +import net.sf.openrocket.document.StorageOptions; + + +public abstract class RocketSaver { + + /** + * Save the document to the specified file using the default storage options. + * + * @param dest the destination file. + * @param document the document to save. + * @throws IOException in case of an I/O error. + */ + public final void save(File dest, OpenRocketDocument document) throws IOException { + save(dest, document, document.getDefaultStorageOptions()); + } + + + /** + * Save the document to the specified file using the given storage options. + * + * @param dest the destination file. + * @param document the document to save. + * @param options the storage options. + * @throws IOException in case of an I/O error. + */ + public void save(File dest, OpenRocketDocument document, StorageOptions options) + throws IOException { + OutputStream s = new BufferedOutputStream(new FileOutputStream(dest)); + try { + save(s, document, options); + } finally { + s.close(); + } + } + + + /** + * Save the document to the specified output stream using the default storage options. + * + * @param dest the destination stream. + * @param doc the document to save. + * @throws IOException in case of an I/O error. + */ + public final void save(OutputStream dest, OpenRocketDocument doc) throws IOException { + save(dest, doc, doc.getDefaultStorageOptions()); + } + + + /** + * Save the document to the specified output stream using the given storage options. + * + * @param dest the destination stream. + * @param doc the document to save. + * @param options the storage options. + * @throws IOException in case of an I/O error. + */ + public abstract void save(OutputStream dest, OpenRocketDocument doc, + StorageOptions options) throws IOException; + + + + + + + + public static String escapeXML(String s) { + + s = s.replace("&", "&"); + s = s.replace("<", "<"); + s = s.replace(">", ">"); + s = s.replace("\"","""); + s = s.replace("'", "'"); + + for (int i=0; i < s.length(); i++) { + char n = s.charAt(i); + if (((n < 32) && (n != 9) && (n != 10) && (n != 13)) || (n == 127)) { + s = s.substring(0,i) + "&#" + ((int)n) + ";" + s.substring(i+1); + } + } + + return s; + } +} diff --git a/src/net/sf/openrocket/file/openrocket/BodyComponentSaver.java b/src/net/sf/openrocket/file/openrocket/BodyComponentSaver.java new file mode 100644 index 000000000..97dc5eb59 --- /dev/null +++ b/src/net/sf/openrocket/file/openrocket/BodyComponentSaver.java @@ -0,0 +1,15 @@ +package net.sf.openrocket.file.openrocket; + +import java.util.List; + +public class BodyComponentSaver extends ExternalComponentSaver { + + @Override + protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List elements) { + super.addParams(c, elements); + + // Body components have a natural length, store it now + elements.add(""+((net.sf.openrocket.rocketcomponent.BodyComponent)c).getLength()+""); + } + +} diff --git a/src/net/sf/openrocket/file/openrocket/BodyTubeSaver.java b/src/net/sf/openrocket/file/openrocket/BodyTubeSaver.java new file mode 100644 index 000000000..865c6c809 --- /dev/null +++ b/src/net/sf/openrocket/file/openrocket/BodyTubeSaver.java @@ -0,0 +1,36 @@ +package net.sf.openrocket.file.openrocket; + +import java.util.ArrayList; +import java.util.List; + +public class BodyTubeSaver extends SymmetricComponentSaver { + + private static final BodyTubeSaver instance = new BodyTubeSaver(); + + public static List getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) { + List list = new ArrayList(); + + list.add(""); + instance.addParams(c, list); + list.add(""); + + return list; + } + + @Override + protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List elements) { + super.addParams(c, elements); + net.sf.openrocket.rocketcomponent.BodyTube tube = (net.sf.openrocket.rocketcomponent.BodyTube) c; + + if (tube.isRadiusAutomatic()) + elements.add("auto"); + else + elements.add("" + tube.getRadius() + ""); + + if (tube.isMotorMount()) { + elements.addAll(motorMountParams(tube)); + } + } + + +} diff --git a/src/net/sf/openrocket/file/openrocket/BulkheadSaver.java b/src/net/sf/openrocket/file/openrocket/BulkheadSaver.java new file mode 100644 index 000000000..c4e5b6a09 --- /dev/null +++ b/src/net/sf/openrocket/file/openrocket/BulkheadSaver.java @@ -0,0 +1,20 @@ +package net.sf.openrocket.file.openrocket; + +import java.util.ArrayList; +import java.util.List; + +public class BulkheadSaver extends RadiusRingComponentSaver { + + private static final BulkheadSaver instance = new BulkheadSaver(); + + public static List getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) { + List list = new ArrayList(); + + list.add(""); + instance.addParams(c, list); + list.add(""); + + return list; + } + +} diff --git a/src/net/sf/openrocket/file/openrocket/CenteringRingSaver.java b/src/net/sf/openrocket/file/openrocket/CenteringRingSaver.java new file mode 100644 index 000000000..9014fb0aa --- /dev/null +++ b/src/net/sf/openrocket/file/openrocket/CenteringRingSaver.java @@ -0,0 +1,20 @@ +package net.sf.openrocket.file.openrocket; + +import java.util.ArrayList; +import java.util.List; + +public class CenteringRingSaver extends RadiusRingComponentSaver { + + private static final CenteringRingSaver instance = new CenteringRingSaver(); + + public static List getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) { + List list = new ArrayList(); + + list.add(""); + instance.addParams(c, list); + list.add(""); + + return list; + } + +} diff --git a/src/net/sf/openrocket/file/openrocket/ComponentAssemblySaver.java b/src/net/sf/openrocket/file/openrocket/ComponentAssemblySaver.java new file mode 100644 index 000000000..8a73471f1 --- /dev/null +++ b/src/net/sf/openrocket/file/openrocket/ComponentAssemblySaver.java @@ -0,0 +1,7 @@ +package net.sf.openrocket.file.openrocket; + +public class ComponentAssemblySaver extends RocketComponentSaver { + + // No-op + +} diff --git a/src/net/sf/openrocket/file/openrocket/EllipticalFinSetSaver.java b/src/net/sf/openrocket/file/openrocket/EllipticalFinSetSaver.java new file mode 100644 index 000000000..8874a7aec --- /dev/null +++ b/src/net/sf/openrocket/file/openrocket/EllipticalFinSetSaver.java @@ -0,0 +1,29 @@ +package net.sf.openrocket.file.openrocket; + +import java.util.ArrayList; +import java.util.List; + +public class EllipticalFinSetSaver extends FinSetSaver { + + private static final EllipticalFinSetSaver instance = new EllipticalFinSetSaver(); + + public static ArrayList getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) { + ArrayList list = new ArrayList(); + + list.add(""); + instance.addParams(c,list); + list.add(""); + + return list; + } + + @Override + protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List elements) { + super.addParams(c, elements); + + net.sf.openrocket.rocketcomponent.EllipticalFinSet fins = (net.sf.openrocket.rocketcomponent.EllipticalFinSet)c; + elements.add(""+fins.getLength()+""); + elements.add(""+fins.getHeight()+""); + } + +} diff --git a/src/net/sf/openrocket/file/openrocket/EngineBlockSaver.java b/src/net/sf/openrocket/file/openrocket/EngineBlockSaver.java new file mode 100644 index 000000000..493232255 --- /dev/null +++ b/src/net/sf/openrocket/file/openrocket/EngineBlockSaver.java @@ -0,0 +1,20 @@ +package net.sf.openrocket.file.openrocket; + +import java.util.ArrayList; +import java.util.List; + +public class EngineBlockSaver extends ThicknessRingComponentSaver { + + private static final EngineBlockSaver instance = new EngineBlockSaver(); + + public static List getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) { + List list = new ArrayList(); + + list.add(""); + instance.addParams(c, list); + list.add(""); + + return list; + } + +} diff --git a/src/net/sf/openrocket/file/openrocket/ExternalComponentSaver.java b/src/net/sf/openrocket/file/openrocket/ExternalComponentSaver.java new file mode 100644 index 000000000..7ca8b8f98 --- /dev/null +++ b/src/net/sf/openrocket/file/openrocket/ExternalComponentSaver.java @@ -0,0 +1,23 @@ +package net.sf.openrocket.file.openrocket; + +import java.util.List; + +import net.sf.openrocket.rocketcomponent.ExternalComponent; + + +public class ExternalComponentSaver extends RocketComponentSaver { + + @Override + protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List elements) { + super.addParams(c, elements); + + ExternalComponent ext = (ExternalComponent)c; + + // Finish enum names are currently the same except for case + elements.add("" + ext.getFinish().name().toLowerCase() + ""); + + // Material + elements.add(materialParam(ext.getMaterial())); + } + +} diff --git a/src/net/sf/openrocket/file/openrocket/FinSetSaver.java b/src/net/sf/openrocket/file/openrocket/FinSetSaver.java new file mode 100644 index 000000000..42f4d8967 --- /dev/null +++ b/src/net/sf/openrocket/file/openrocket/FinSetSaver.java @@ -0,0 +1,20 @@ +package net.sf.openrocket.file.openrocket; + +import java.util.List; + +public class FinSetSaver extends ExternalComponentSaver { + + @Override + protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List elements) { + super.addParams(c, elements); + + net.sf.openrocket.rocketcomponent.FinSet fins = (net.sf.openrocket.rocketcomponent.FinSet) c; + elements.add("" + fins.getFinCount() + ""); + elements.add("" + (fins.getBaseRotation() * 180.0 / Math.PI) + ""); + elements.add("" + fins.getThickness() + ""); + elements.add("" + fins.getCrossSection().name().toLowerCase() + + ""); + elements.add("" + (fins.getCantAngle() * 180.0 / Math.PI) + ""); + } + +} diff --git a/src/net/sf/openrocket/file/openrocket/FreeformFinSetSaver.java b/src/net/sf/openrocket/file/openrocket/FreeformFinSetSaver.java new file mode 100644 index 000000000..6aa177820 --- /dev/null +++ b/src/net/sf/openrocket/file/openrocket/FreeformFinSetSaver.java @@ -0,0 +1,36 @@ +package net.sf.openrocket.file.openrocket; + +import java.util.ArrayList; +import java.util.List; + +import net.sf.openrocket.rocketcomponent.FreeformFinSet; +import net.sf.openrocket.util.Coordinate; + + +public class FreeformFinSetSaver extends FinSetSaver { + + private static final FreeformFinSetSaver instance = new FreeformFinSetSaver(); + + public static ArrayList getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) { + ArrayList list = new ArrayList(); + + list.add(""); + instance.addParams(c,list); + list.add(""); + + return list; + } + + @Override + protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List elements) { + super.addParams(c, elements); + + FreeformFinSet fins = (FreeformFinSet)c; + elements.add(""); + for (Coordinate p: fins.getFinPoints()) { + elements.add(" "); + } + elements.add(""); + } + +} diff --git a/src/net/sf/openrocket/file/openrocket/InnerTubeSaver.java b/src/net/sf/openrocket/file/openrocket/InnerTubeSaver.java new file mode 100644 index 000000000..f70691775 --- /dev/null +++ b/src/net/sf/openrocket/file/openrocket/InnerTubeSaver.java @@ -0,0 +1,43 @@ +package net.sf.openrocket.file.openrocket; + +import java.util.ArrayList; +import java.util.List; + +import net.sf.openrocket.rocketcomponent.InnerTube; + + +public class InnerTubeSaver extends ThicknessRingComponentSaver { + + private static final InnerTubeSaver instance = new InnerTubeSaver(); + + public static List getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) { + List list = new ArrayList(); + + list.add(""); + instance.addParams(c, list); + list.add(""); + + return list; + } + + + @Override + protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List elements) { + super.addParams(c, elements); + InnerTube tube = (InnerTube) c; + + elements.add("" + tube.getClusterConfiguration().getXMLName() + + ""); + elements.add("" + tube.getClusterScale() + ""); + elements.add("" + (tube.getClusterRotation() * 180.0 / Math.PI) + + ""); + + if (tube.isMotorMount()) { + elements.addAll(motorMountParams(tube)); + } + + + } + + +} diff --git a/src/net/sf/openrocket/file/openrocket/InternalComponentSaver.java b/src/net/sf/openrocket/file/openrocket/InternalComponentSaver.java new file mode 100644 index 000000000..45c0a5635 --- /dev/null +++ b/src/net/sf/openrocket/file/openrocket/InternalComponentSaver.java @@ -0,0 +1,14 @@ +package net.sf.openrocket.file.openrocket; + +import java.util.List; + +public class InternalComponentSaver extends RocketComponentSaver { + + @Override + protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List elements) { + super.addParams(c, elements); + + // Nothing to save + } + +} diff --git a/src/net/sf/openrocket/file/openrocket/LaunchLugSaver.java b/src/net/sf/openrocket/file/openrocket/LaunchLugSaver.java new file mode 100644 index 000000000..06d3da59a --- /dev/null +++ b/src/net/sf/openrocket/file/openrocket/LaunchLugSaver.java @@ -0,0 +1,35 @@ +package net.sf.openrocket.file.openrocket; + +import java.util.ArrayList; +import java.util.List; + +import net.sf.openrocket.rocketcomponent.LaunchLug; + + +public class LaunchLugSaver extends ExternalComponentSaver { + + private static final LaunchLugSaver instance = new LaunchLugSaver(); + + public static List getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) { + List list = new ArrayList(); + + list.add(""); + instance.addParams(c, list); + list.add(""); + + return list; + } + + @Override + protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List elements) { + super.addParams(c, elements); + LaunchLug lug = (LaunchLug) c; + + elements.add("" + lug.getRadius() + ""); + elements.add("" + lug.getLength() + ""); + elements.add("" + lug.getThickness() + ""); + elements.add("" + (lug.getRadialDirection()*180.0/Math.PI) + ""); + } + + +} diff --git a/src/net/sf/openrocket/file/openrocket/MassComponentSaver.java b/src/net/sf/openrocket/file/openrocket/MassComponentSaver.java new file mode 100644 index 000000000..11a5ce654 --- /dev/null +++ b/src/net/sf/openrocket/file/openrocket/MassComponentSaver.java @@ -0,0 +1,32 @@ +package net.sf.openrocket.file.openrocket; + +import java.util.ArrayList; +import java.util.List; + +import net.sf.openrocket.rocketcomponent.MassComponent; + + +public class MassComponentSaver extends MassObjectSaver { + + private static final MassComponentSaver instance = new MassComponentSaver(); + + public static List getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) { + List list = new ArrayList(); + + list.add(""); + instance.addParams(c, list); + list.add(""); + + return list; + } + + @Override + protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List elements) { + super.addParams(c, elements); + + MassComponent mass = (MassComponent) c; + + elements.add("" + mass.getMass() + ""); + } + +} diff --git a/src/net/sf/openrocket/file/openrocket/MassObjectSaver.java b/src/net/sf/openrocket/file/openrocket/MassObjectSaver.java new file mode 100644 index 000000000..f1ee440c3 --- /dev/null +++ b/src/net/sf/openrocket/file/openrocket/MassObjectSaver.java @@ -0,0 +1,23 @@ +package net.sf.openrocket.file.openrocket; + +import java.util.List; + +import net.sf.openrocket.rocketcomponent.MassObject; + + +public class MassObjectSaver extends InternalComponentSaver { + + @Override + protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List elements) { + super.addParams(c, elements); + + MassObject mass = (MassObject) c; + + elements.add("" + mass.getLength() + ""); + elements.add("" + mass.getRadius() + ""); + elements.add("" + mass.getRadialPosition() + ""); + elements.add("" + (mass.getRadialDirection() * 180.0 / Math.PI) + + ""); + } + +} diff --git a/src/net/sf/openrocket/file/openrocket/NoseConeSaver.java b/src/net/sf/openrocket/file/openrocket/NoseConeSaver.java new file mode 100644 index 000000000..95feafb79 --- /dev/null +++ b/src/net/sf/openrocket/file/openrocket/NoseConeSaver.java @@ -0,0 +1,27 @@ +package net.sf.openrocket.file.openrocket; + +import java.util.ArrayList; +import java.util.List; + +public class NoseConeSaver extends TransitionSaver { + + private static final NoseConeSaver instance = new NoseConeSaver(); + + public static ArrayList getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) { + ArrayList list = new ArrayList(); + + list.add(""); + instance.addParams(c,list); + list.add(""); + + return list; + } + + + @Override + protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List elements) { + super.addParams(c, elements); + + // Transition handles nose cone saving as well + } +} diff --git a/src/net/sf/openrocket/file/openrocket/ParachuteSaver.java b/src/net/sf/openrocket/file/openrocket/ParachuteSaver.java new file mode 100644 index 000000000..64e44ff88 --- /dev/null +++ b/src/net/sf/openrocket/file/openrocket/ParachuteSaver.java @@ -0,0 +1,35 @@ +package net.sf.openrocket.file.openrocket; + +import java.util.ArrayList; +import java.util.List; + +import net.sf.openrocket.rocketcomponent.Parachute; + + +public class ParachuteSaver extends RecoveryDeviceSaver { + + private static final ParachuteSaver instance = new ParachuteSaver(); + + public static List getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) { + List list = new ArrayList(); + + list.add(""); + instance.addParams(c, list); + list.add(""); + + return list; + } + + @Override + protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List elements) { + super.addParams(c, elements); + Parachute para = (Parachute) c; + + elements.add("" + para.getDiameter() + ""); + elements.add("" + para.getLineCount() + ""); + elements.add("" + para.getLineLength() + ""); + elements.add(materialParam("linematerial", para.getLineMaterial())); + } + + +} diff --git a/src/net/sf/openrocket/file/openrocket/RadiusRingComponentSaver.java b/src/net/sf/openrocket/file/openrocket/RadiusRingComponentSaver.java new file mode 100644 index 000000000..02cf01591 --- /dev/null +++ b/src/net/sf/openrocket/file/openrocket/RadiusRingComponentSaver.java @@ -0,0 +1,28 @@ +package net.sf.openrocket.file.openrocket; + +import java.util.List; + +import net.sf.openrocket.rocketcomponent.Bulkhead; +import net.sf.openrocket.rocketcomponent.RadiusRingComponent; + + +public class RadiusRingComponentSaver extends RingComponentSaver { + + @Override + protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List elements) { + super.addParams(c, elements); + + RadiusRingComponent comp = (RadiusRingComponent)c; + if (comp.isOuterRadiusAutomatic()) + elements.add("auto"); + else + elements.add("" + comp.getOuterRadius() + ""); + if (!(comp instanceof Bulkhead)) { + if (comp.isInnerRadiusAutomatic()) + elements.add("auto"); + else + elements.add("" + comp.getInnerRadius() + ""); + } + } + +} diff --git a/src/net/sf/openrocket/file/openrocket/RecoveryDeviceSaver.java b/src/net/sf/openrocket/file/openrocket/RecoveryDeviceSaver.java new file mode 100644 index 000000000..a57747e23 --- /dev/null +++ b/src/net/sf/openrocket/file/openrocket/RecoveryDeviceSaver.java @@ -0,0 +1,27 @@ +package net.sf.openrocket.file.openrocket; + +import java.util.List; + +import net.sf.openrocket.rocketcomponent.RecoveryDevice; + + +public class RecoveryDeviceSaver extends MassObjectSaver { + + @Override + protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List elements) { + super.addParams(c, elements); + + RecoveryDevice dev = (RecoveryDevice) c; + + if (dev.isCDAutomatic()) + elements.add("auto"); + else + elements.add("" + dev.getCD() + ""); + + elements.add("" + dev.getDeployEvent().name().toLowerCase() + ""); + elements.add("" + dev.getDeployAltitude() + ""); + elements.add("" + dev.getDeployDelay() + ""); + elements.add(materialParam(dev.getMaterial())); + } + +} diff --git a/src/net/sf/openrocket/file/openrocket/RingComponentSaver.java b/src/net/sf/openrocket/file/openrocket/RingComponentSaver.java new file mode 100644 index 000000000..53d8177f0 --- /dev/null +++ b/src/net/sf/openrocket/file/openrocket/RingComponentSaver.java @@ -0,0 +1,22 @@ +package net.sf.openrocket.file.openrocket; + +import java.util.List; + +import net.sf.openrocket.rocketcomponent.RingComponent; + + +public class RingComponentSaver extends StructuralComponentSaver { + + @Override + protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List elements) { + super.addParams(c, elements); + + RingComponent ring = (RingComponent) c; + + elements.add("" + ring.getLength() + ""); + elements.add("" + ring.getRadialPosition() + ""); + elements.add("" + (ring.getRadialDirection() * 180.0 / Math.PI) + + ""); + } + +} diff --git a/src/net/sf/openrocket/file/openrocket/RocketComponentSaver.java b/src/net/sf/openrocket/file/openrocket/RocketComponentSaver.java new file mode 100644 index 000000000..1a1a3156b --- /dev/null +++ b/src/net/sf/openrocket/file/openrocket/RocketComponentSaver.java @@ -0,0 +1,151 @@ +package net.sf.openrocket.file.openrocket; + +import java.awt.Color; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import net.sf.openrocket.file.RocketSaver; +import net.sf.openrocket.material.Material; +import net.sf.openrocket.rocketcomponent.ComponentAssembly; +import net.sf.openrocket.rocketcomponent.Motor; +import net.sf.openrocket.rocketcomponent.MotorMount; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.util.LineStyle; + + +public class RocketComponentSaver { + + protected RocketComponentSaver() { + // Prevent instantiation from outside the package + } + + protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List elements) { + elements.add("" + RocketSaver.escapeXML(c.getName()) + ""); + + + // Save color and line style if significant + if (!(c instanceof Rocket || c instanceof ComponentAssembly)) { + Color color = c.getColor(); + if (color != null) { + elements.add(""); + } + + LineStyle style = c.getLineStyle(); + if (style != null) { + // Type names currently equivalent to the enum names except for case. + elements.add("" + style.name().toLowerCase() + ""); + } + } + + + // Save position unless "AFTER" + if (c.getRelativePosition() != RocketComponent.Position.AFTER) { + // The type names are currently equivalent to the enum names except for case. + String type = c.getRelativePosition().name().toLowerCase(); + elements.add("" + c.getPositionValue() + ""); + } + + + // Overrides + boolean overridden = false; + if (c.isMassOverridden()) { + elements.add("" + c.getOverrideMass() + ""); + overridden = true; + } + if (c.isCGOverridden()) { + elements.add("" + c.getOverrideCGX() + ""); + overridden = true; + } + if (overridden) { + elements.add("" + c.getOverrideSubcomponents() + + ""); + } + + + // Comment + if (c.getComment().length() > 0) { + elements.add("" + RocketSaver.escapeXML(c.getComment()) + ""); + } + + } + + + + + protected final String materialParam(Material mat) { + return materialParam("material", mat); + } + + + protected final String materialParam(String tag, Material mat) { + String str = "<" + tag; + + switch (mat.getType()) { + case LINE: + str += " type=\"line\""; + break; + case SURFACE: + str += " type=\"surface\""; + break; + case BULK: + str += " type=\"bulk\""; + break; + default: + throw new RuntimeException("Unknown material type: " + mat.getType()); + } + + return str + " density=\"" + mat.getDensity() + "\">" + RocketSaver.escapeXML(mat.getName()) + ""; + } + + + protected final List motorMountParams(MotorMount mount) { + if (!mount.isMotorMount()) + return Collections.emptyList(); + + String[] motorConfigIDs = ((RocketComponent) mount).getRocket().getMotorConfigurationIDs(); + List elements = new ArrayList(); + + elements.add(""); + + for (String id : motorConfigIDs) { + Motor motor = mount.getMotor(id); + + // Nothing is stored if no motor loaded + if (motor == null) + continue; + + elements.add(" "); + if (motor.getMotorType() != Motor.Type.UNKNOWN) { + elements.add(" " + motor.getMotorType().name().toLowerCase() + ""); + } + elements.add(" " + RocketSaver.escapeXML(motor.getManufacturer()) + ""); + elements.add(" " + RocketSaver.escapeXML(motor.getDesignation()) + ""); + elements.add(" " + motor.getDiameter() + ""); + elements.add(" " + motor.getLength() + ""); + + // Motor delay + if (mount.getMotorDelay(id) == Motor.PLUGGED) { + elements.add(" none"); + } else { + elements.add(" " + mount.getMotorDelay(id) + ""); + } + + elements.add(" "); + } + + elements.add(" " + + mount.getIgnitionEvent().name().toLowerCase().replace("_", "") + + ""); + + elements.add(" " + mount.getIgnitionDelay() + ""); + elements.add(" " + mount.getMotorOverhang() + ""); + + elements.add(""); + + return elements; + } + +} diff --git a/src/net/sf/openrocket/file/openrocket/RocketSaver.java b/src/net/sf/openrocket/file/openrocket/RocketSaver.java new file mode 100644 index 000000000..e27fa76c3 --- /dev/null +++ b/src/net/sf/openrocket/file/openrocket/RocketSaver.java @@ -0,0 +1,73 @@ +package net.sf.openrocket.file.openrocket; + +import java.util.ArrayList; +import java.util.List; + +import net.sf.openrocket.rocketcomponent.ReferenceType; +import net.sf.openrocket.rocketcomponent.Rocket; + + +public class RocketSaver extends RocketComponentSaver { + + private static final RocketSaver instance = new RocketSaver(); + + public static ArrayList getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) { + ArrayList list = new ArrayList(); + + list.add(""); + instance.addParams(c, list); + list.add(""); + + return list; + } + + + + @Override + protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List elements) { + super.addParams(c, elements); + + Rocket rocket = (Rocket) c; + + if (rocket.getDesigner().length() > 0) { + elements.add("" + + net.sf.openrocket.file.RocketSaver.escapeXML(rocket.getDesigner()) + + ""); + } + if (rocket.getRevision().length() > 0) { + elements.add("" + + net.sf.openrocket.file.RocketSaver.escapeXML(rocket.getRevision()) + + ""); + } + + + // Motor configurations + String defId = rocket.getDefaultConfiguration().getMotorConfigurationID(); + for (String id : rocket.getMotorConfigurationIDs()) { + if (id == null) + continue; + + String str = ""; + } + elements.add(str); + } + + // Reference diameter + elements.add("" + rocket.getReferenceType().name().toLowerCase() + + ""); + if (rocket.getReferenceType() == ReferenceType.CUSTOM) { + elements.add("" + rocket.getCustomReferenceLength() + + ""); + } + + } + +} diff --git a/src/net/sf/openrocket/file/openrocket/ShockCordSaver.java b/src/net/sf/openrocket/file/openrocket/ShockCordSaver.java new file mode 100644 index 000000000..0f8746e2e --- /dev/null +++ b/src/net/sf/openrocket/file/openrocket/ShockCordSaver.java @@ -0,0 +1,33 @@ +package net.sf.openrocket.file.openrocket; + +import java.util.ArrayList; +import java.util.List; + +import net.sf.openrocket.rocketcomponent.ShockCord; + + +public class ShockCordSaver extends MassObjectSaver { + + private static final ShockCordSaver instance = new ShockCordSaver(); + + public static List getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) { + List list = new ArrayList(); + + list.add(""); + instance.addParams(c, list); + list.add(""); + + return list; + } + + @Override + protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List elements) { + super.addParams(c, elements); + + ShockCord mass = (ShockCord) c; + + elements.add("" + mass.getCordLength() + ""); + elements.add(materialParam(mass.getMaterial())); + } + +} diff --git a/src/net/sf/openrocket/file/openrocket/StageSaver.java b/src/net/sf/openrocket/file/openrocket/StageSaver.java new file mode 100644 index 000000000..2c8377283 --- /dev/null +++ b/src/net/sf/openrocket/file/openrocket/StageSaver.java @@ -0,0 +1,20 @@ +package net.sf.openrocket.file.openrocket; + +import java.util.ArrayList; + +public class StageSaver extends ComponentAssemblySaver { + + private static final StageSaver instance = new StageSaver(); + + public static ArrayList getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) { + ArrayList list = new ArrayList(); + + list.add(""); + instance.addParams(c,list); + list.add(""); + + return list; + } + + +} diff --git a/src/net/sf/openrocket/file/openrocket/StreamerSaver.java b/src/net/sf/openrocket/file/openrocket/StreamerSaver.java new file mode 100644 index 000000000..d3e936b50 --- /dev/null +++ b/src/net/sf/openrocket/file/openrocket/StreamerSaver.java @@ -0,0 +1,33 @@ +package net.sf.openrocket.file.openrocket; + +import java.util.ArrayList; +import java.util.List; + +import net.sf.openrocket.rocketcomponent.Streamer; + + +public class StreamerSaver extends RecoveryDeviceSaver { + + private static final StreamerSaver instance = new StreamerSaver(); + + public static List getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) { + List list = new ArrayList(); + + list.add(""); + instance.addParams(c, list); + list.add(""); + + return list; + } + + @Override + protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List elements) { + super.addParams(c, elements); + Streamer st = (Streamer) c; + + elements.add("" + st.getStripLength() + ""); + elements.add("" + st.getStripWidth() + ""); + } + + +} diff --git a/src/net/sf/openrocket/file/openrocket/StructuralComponentSaver.java b/src/net/sf/openrocket/file/openrocket/StructuralComponentSaver.java new file mode 100644 index 000000000..4d7f60596 --- /dev/null +++ b/src/net/sf/openrocket/file/openrocket/StructuralComponentSaver.java @@ -0,0 +1,18 @@ +package net.sf.openrocket.file.openrocket; + +import java.util.List; + +import net.sf.openrocket.rocketcomponent.StructuralComponent; + + +public class StructuralComponentSaver extends InternalComponentSaver { + + @Override + protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List elements) { + super.addParams(c, elements); + + StructuralComponent comp = (StructuralComponent)c; + elements.add(materialParam(comp.getMaterial())); + } + +} diff --git a/src/net/sf/openrocket/file/openrocket/SymmetricComponentSaver.java b/src/net/sf/openrocket/file/openrocket/SymmetricComponentSaver.java new file mode 100644 index 000000000..741c5135a --- /dev/null +++ b/src/net/sf/openrocket/file/openrocket/SymmetricComponentSaver.java @@ -0,0 +1,18 @@ +package net.sf.openrocket.file.openrocket; + +import java.util.List; + +public class SymmetricComponentSaver extends BodyComponentSaver { + + @Override + protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List elements) { + super.addParams(c, elements); + + net.sf.openrocket.rocketcomponent.SymmetricComponent comp = (net.sf.openrocket.rocketcomponent.SymmetricComponent)c; + if (comp.isFilled()) + elements.add("filled"); + else + elements.add(""+comp.getThickness()+""); + } + +} diff --git a/src/net/sf/openrocket/file/openrocket/ThicknessRingComponentSaver.java b/src/net/sf/openrocket/file/openrocket/ThicknessRingComponentSaver.java new file mode 100644 index 000000000..d52df559f --- /dev/null +++ b/src/net/sf/openrocket/file/openrocket/ThicknessRingComponentSaver.java @@ -0,0 +1,22 @@ +package net.sf.openrocket.file.openrocket; + +import java.util.List; + +import net.sf.openrocket.rocketcomponent.ThicknessRingComponent; + + +public class ThicknessRingComponentSaver extends RingComponentSaver { + + @Override + protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List elements) { + super.addParams(c, elements); + + ThicknessRingComponent comp = (ThicknessRingComponent)c; + if (comp.isOuterRadiusAutomatic()) + elements.add("auto"); + else + elements.add("" + comp.getOuterRadius() + ""); + elements.add("" + comp.getThickness() + ""); + } + +} diff --git a/src/net/sf/openrocket/file/openrocket/TransitionSaver.java b/src/net/sf/openrocket/file/openrocket/TransitionSaver.java new file mode 100644 index 000000000..d597d129d --- /dev/null +++ b/src/net/sf/openrocket/file/openrocket/TransitionSaver.java @@ -0,0 +1,79 @@ +package net.sf.openrocket.file.openrocket; + +import java.util.ArrayList; +import java.util.List; + +import net.sf.openrocket.rocketcomponent.NoseCone; +import net.sf.openrocket.rocketcomponent.Transition; + + +public class TransitionSaver extends SymmetricComponentSaver { + + private static final TransitionSaver instance = new TransitionSaver(); + + public static ArrayList getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) { + ArrayList list = new ArrayList(); + + list.add(""); + instance.addParams(c, list); + list.add(""); + + return list; + } + + + /* + * Note: This method must be capable of handling nose cones as well. + */ + @Override + protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List elements) { + super.addParams(c, elements); + net.sf.openrocket.rocketcomponent.Transition trans = (net.sf.openrocket.rocketcomponent.Transition) c; + boolean nosecone = (trans instanceof NoseCone); + + + Transition.Shape shape = trans.getType(); + elements.add("" + shape.getName().toLowerCase() + ""); + if (shape.isClippable()) { + elements.add("" + trans.isClipped() + ""); + } + if (shape.usesParameter()) { + elements.add("" + trans.getShapeParameter() + ""); + } + + + if (!nosecone) { + if (trans.isForeRadiusAutomatic()) + elements.add("auto"); + else + elements.add("" + trans.getForeRadius() + ""); + } + + if (trans.isAftRadiusAutomatic()) + elements.add("auto"); + else + elements.add("" + trans.getAftRadius() + ""); + + + if (!nosecone) { + elements.add("" + trans.getForeShoulderRadius() + + ""); + elements.add("" + trans.getForeShoulderLength() + + ""); + elements.add("" + trans.getForeShoulderThickness() + + ""); + elements.add("" + trans.isForeShoulderCapped() + + ""); + } + + elements.add("" + trans.getAftShoulderRadius() + + ""); + elements.add("" + trans.getAftShoulderLength() + + ""); + elements.add("" + trans.getAftShoulderThickness() + + ""); + elements.add("" + trans.isAftShoulderCapped() + + ""); + } + +} diff --git a/src/net/sf/openrocket/file/openrocket/TrapezoidFinSetSaver.java b/src/net/sf/openrocket/file/openrocket/TrapezoidFinSetSaver.java new file mode 100644 index 000000000..fc244cc14 --- /dev/null +++ b/src/net/sf/openrocket/file/openrocket/TrapezoidFinSetSaver.java @@ -0,0 +1,31 @@ +package net.sf.openrocket.file.openrocket; + +import java.util.ArrayList; +import java.util.List; + +public class TrapezoidFinSetSaver extends FinSetSaver { + + private static final TrapezoidFinSetSaver instance = new TrapezoidFinSetSaver(); + + public static ArrayList getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) { + ArrayList list = new ArrayList(); + + list.add(""); + instance.addParams(c,list); + list.add(""); + + return list; + } + + @Override + protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List elements) { + super.addParams(c, elements); + + net.sf.openrocket.rocketcomponent.TrapezoidFinSet fins = (net.sf.openrocket.rocketcomponent.TrapezoidFinSet)c; + elements.add(""+fins.getRootChord()+""); + elements.add(""+fins.getTipChord()+""); + elements.add(""+fins.getSweep()+""); + elements.add(""+fins.getHeight()+""); + } + +} diff --git a/src/net/sf/openrocket/file/openrocket/TubeCouplerSaver.java b/src/net/sf/openrocket/file/openrocket/TubeCouplerSaver.java new file mode 100644 index 000000000..09a79c7fb --- /dev/null +++ b/src/net/sf/openrocket/file/openrocket/TubeCouplerSaver.java @@ -0,0 +1,20 @@ +package net.sf.openrocket.file.openrocket; + +import java.util.ArrayList; +import java.util.List; + +public class TubeCouplerSaver extends ThicknessRingComponentSaver { + + private static final TubeCouplerSaver instance = new TubeCouplerSaver(); + + public static List getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) { + List list = new ArrayList(); + + list.add(""); + instance.addParams(c, list); + list.add(""); + + return list; + } + +} diff --git a/src/net/sf/openrocket/gui/BasicSlider.java b/src/net/sf/openrocket/gui/BasicSlider.java new file mode 100644 index 000000000..d22c3e833 --- /dev/null +++ b/src/net/sf/openrocket/gui/BasicSlider.java @@ -0,0 +1,31 @@ +package net.sf.openrocket.gui; + +import javax.swing.BoundedRangeModel; +import javax.swing.JSlider; +import javax.swing.plaf.basic.BasicSliderUI; + +/** + * A simple slider that does not show the current value. GTK l&f shows the value, and cannot + * be configured otherwise(!). + * + * @author Sampo Niskanen + */ + +public class BasicSlider extends JSlider { + + public BasicSlider(BoundedRangeModel brm) { + this(brm,JSlider.HORIZONTAL,false); + } + + public BasicSlider(BoundedRangeModel brm, int orientation) { + this(brm,orientation,false); + } + + public BasicSlider(BoundedRangeModel brm, int orientation, boolean inverted) { + super(brm); + setOrientation(orientation); + setInverted(inverted); + setUI(new BasicSliderUI(this)); + } + +} diff --git a/src/net/sf/openrocket/gui/ComponentAnalysisDialog.java b/src/net/sf/openrocket/gui/ComponentAnalysisDialog.java new file mode 100644 index 000000000..e44e48bdb --- /dev/null +++ b/src/net/sf/openrocket/gui/ComponentAnalysisDialog.java @@ -0,0 +1,583 @@ +package net.sf.openrocket.gui; + +import static net.sf.openrocket.unit.Unit.NOUNIT2; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Vector; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTabbedPane; +import javax.swing.JTable; +import javax.swing.JToggleButton; +import javax.swing.ListSelectionModel; +import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.table.TableCellRenderer; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.aerodynamics.AerodynamicCalculator; +import net.sf.openrocket.aerodynamics.AerodynamicForces; +import net.sf.openrocket.aerodynamics.FlightConditions; +import net.sf.openrocket.aerodynamics.Warning; +import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.gui.adaptors.Column; +import net.sf.openrocket.gui.adaptors.ColumnTableModel; +import net.sf.openrocket.gui.adaptors.DoubleModel; +import net.sf.openrocket.gui.adaptors.MotorConfigurationModel; +import net.sf.openrocket.gui.scalefigure.RocketPanel; +import net.sf.openrocket.rocketcomponent.Configuration; +import net.sf.openrocket.rocketcomponent.FinSet; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.unit.Unit; +import net.sf.openrocket.unit.UnitGroup; +import net.sf.openrocket.util.GUIUtil; +import net.sf.openrocket.util.MathUtil; +import net.sf.openrocket.util.Prefs; + +public class ComponentAnalysisDialog extends JDialog implements ChangeListener { + + private static ComponentAnalysisDialog singletonDialog = null; + + + private final FlightConditions conditions; + private final Configuration configuration; + private final DoubleModel theta, aoa, mach, roll; + private final JToggleButton worstToggle; + private boolean fakeChange = false; + private AerodynamicCalculator calculator; + + private final ColumnTableModel cpTableModel; + private final ColumnTableModel dragTableModel; + private final ColumnTableModel rollTableModel; + + private final JList warningList; + + + private final List cpData = new ArrayList(); + private final List dragData = new ArrayList(); + private double totalCD = 0; + private final List rollData = new ArrayList(); + + + public ComponentAnalysisDialog(final RocketPanel rocketPanel) { + super(SwingUtilities.getWindowAncestor(rocketPanel), "Component analysis"); + + JTable table; + + JPanel panel = new JPanel(new MigLayout("fill","[][35lp::][fill][fill]")); + add(panel); + + this.configuration = rocketPanel.getConfiguration(); + this.calculator = rocketPanel.getCalculator().newInstance(); + this.calculator.setConfiguration(configuration); + + + conditions = new FlightConditions(configuration); + + rocketPanel.setCPAOA(0); + aoa = new DoubleModel(rocketPanel, "CPAOA", UnitGroup.UNITS_ANGLE, 0, Math.PI); + rocketPanel.setCPMach(Prefs.getDefaultMach()); + mach = new DoubleModel(rocketPanel, "CPMach", UnitGroup.UNITS_COEFFICIENT, 0); + rocketPanel.setCPTheta(rocketPanel.getFigure().getRotation()); + theta = new DoubleModel(rocketPanel, "CPTheta", UnitGroup.UNITS_ANGLE, 0, 2*Math.PI); + rocketPanel.setCPRoll(0); + roll = new DoubleModel(rocketPanel, "CPRoll", UnitGroup.UNITS_ROLL); + + + panel.add(new JLabel("Wind direction:"),"width 100lp!"); + panel.add(new UnitSelector(theta,true),"width 50lp!"); + BasicSlider slider = new BasicSlider(theta.getSliderModel(0, 2*Math.PI)); + panel.add(slider,"growx, split 2"); + worstToggle = new JToggleButton("Worst"); + worstToggle.setSelected(true); + worstToggle.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + stateChanged(null); + } + }); + slider.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + if (!fakeChange) + worstToggle.setSelected(false); + } + }); + panel.add(worstToggle,""); + + + warningList = new JList(); + JScrollPane scrollPane = new JScrollPane(warningList); + scrollPane.setBorder(BorderFactory.createTitledBorder("Warnings:")); + panel.add(scrollPane,"gap paragraph, spany 4, width 300lp!, growy 1, height :100lp:, wrap"); + + + panel.add(new JLabel("Angle of attack:"),"width 100lp!"); + panel.add(new UnitSelector(aoa,true),"width 50lp!"); + panel.add(new BasicSlider(aoa.getSliderModel(0, Math.PI)),"growx, wrap"); + + panel.add(new JLabel("Mach number:"),"width 100lp!"); + panel.add(new UnitSelector(mach,true),"width 50lp!"); + panel.add(new BasicSlider(mach.getSliderModel(0, 3)),"growx, wrap"); + + panel.add(new JLabel("Roll rate:"), "width 100lp!"); + panel.add(new UnitSelector(roll,true),"width 50lp!"); + panel.add(new BasicSlider(roll.getSliderModel(-20*2*Math.PI, 20*2*Math.PI)), + "growx, wrap paragraph"); + + + // Stage and motor selection: + + panel.add(new JLabel("Active stages:"),"spanx, split, gapafter rel"); + panel.add(new StageSelector(configuration),"gapafter paragraph"); + + JLabel label = new JLabel("Motor configuration:"); + label.setHorizontalAlignment(JLabel.RIGHT); + panel.add(label,"growx, right"); + panel.add(new JComboBox(new MotorConfigurationModel(configuration)),"wrap"); + + + + // Tabbed pane + + JTabbedPane tabbedPane = new JTabbedPane(); + panel.add(tabbedPane, "spanx, growx, growy"); + + + // Create the CP data table + cpTableModel = new ColumnTableModel( + + new Column("Component") { + @Override public Object getValueAt(int row) { + RocketComponent c = cpData.get(row).component; + if (c instanceof Rocket) { + return "Total"; + } + return c.toString(); + } + @Override public int getDefaultWidth() { + return 200; + } + }, + new Column("CG / " + UnitGroup.UNITS_LENGTH.getDefaultUnit().getUnit()) { + private Unit unit = UnitGroup.UNITS_LENGTH.getDefaultUnit(); + @Override public Object getValueAt(int row) { + return unit.toString(cpData.get(row).cg.x); + } + }, + new Column("Mass / " + UnitGroup.UNITS_MASS.getDefaultUnit().getUnit()) { + private Unit unit = UnitGroup.UNITS_MASS.getDefaultUnit(); + @Override + public Object getValueAt(int row) { + return unit.toString(cpData.get(row).cg.weight); + } + }, + new Column("CP / " + UnitGroup.UNITS_LENGTH.getDefaultUnit().getUnit()) { + private Unit unit = UnitGroup.UNITS_LENGTH.getDefaultUnit(); + @Override public Object getValueAt(int row) { + return unit.toString(cpData.get(row).cp.x); + } + }, + new Column("CN\u03b1") { + @Override public Object getValueAt(int row) { + return NOUNIT2.toString(cpData.get(row).cp.weight); + } + } + + ) { + @Override public int getRowCount() { + return cpData.size(); + } + }; + + table = new JTable(cpTableModel); + table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + table.setSelectionBackground(Color.LIGHT_GRAY); + table.setSelectionForeground(Color.BLACK); + cpTableModel.setColumnWidths(table.getColumnModel()); + + table.setDefaultRenderer(Object.class, new CustomCellRenderer()); +// table.setShowHorizontalLines(false); +// table.setShowVerticalLines(true); + + JScrollPane scrollpane = new JScrollPane(table); + scrollpane.setPreferredSize(new Dimension(600,200)); + + tabbedPane.addTab("Stability", null, scrollpane, "Stability information"); + + + + // Create the drag data table + dragTableModel = new ColumnTableModel( + new Column("Component") { + @Override public Object getValueAt(int row) { + RocketComponent c = dragData.get(row).component; + if (c instanceof Rocket) { + return "Total"; + } + return c.toString(); + } + @Override public int getDefaultWidth() { + return 200; + } + }, + new Column("Pressure CD") { + @Override public Object getValueAt(int row) { + return dragData.get(row).pressureCD; + } + }, + new Column("Base CD") { + @Override public Object getValueAt(int row) { + return dragData.get(row).baseCD; + } + }, + new Column("Friction CD") { + @Override public Object getValueAt(int row) { + return dragData.get(row).frictionCD; + } + }, + new Column("Total CD") { + @Override public Object getValueAt(int row) { + return dragData.get(row).CD; + } + } + ) { + @Override public int getRowCount() { + return dragData.size(); + } + }; + + + table = new JTable(dragTableModel); + table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + table.setSelectionBackground(Color.LIGHT_GRAY); + table.setSelectionForeground(Color.BLACK); + dragTableModel.setColumnWidths(table.getColumnModel()); + + table.setDefaultRenderer(Object.class, new DragCellRenderer(new Color(0.5f,1.0f,0.5f))); +// table.setShowHorizontalLines(false); +// table.setShowVerticalLines(true); + + scrollpane = new JScrollPane(table); + scrollpane.setPreferredSize(new Dimension(600,200)); + + tabbedPane.addTab("Drag characteristics", null, scrollpane, "Drag characteristics"); + + + + + // Create the roll data table + rollTableModel = new ColumnTableModel( + new Column("Component") { + @Override public Object getValueAt(int row) { + RocketComponent c = rollData.get(row).component; + if (c instanceof Rocket) { + return "Total"; + } + return c.toString(); + } + }, + new Column("Roll forcing coefficient") { + @Override public Object getValueAt(int row) { + return rollData.get(row).CrollForce; + } + }, + new Column("Roll damping coefficient") { + @Override public Object getValueAt(int row) { + return rollData.get(row).CrollDamp; + } + }, + new Column("Total Cl") { + @Override public Object getValueAt(int row) { + return rollData.get(row).Croll; + } + } + ) { + @Override public int getRowCount() { + return rollData.size(); + } + }; + + + table = new JTable(rollTableModel); + table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + table.setSelectionBackground(Color.LIGHT_GRAY); + table.setSelectionForeground(Color.BLACK); + rollTableModel.setColumnWidths(table.getColumnModel()); + + scrollpane = new JScrollPane(table); + scrollpane.setPreferredSize(new Dimension(600,200)); + + tabbedPane.addTab("Roll dynamics", null, scrollpane, "Roll dynamics"); + + + + + + + // Add the data updater to listen to changes in aoa and theta + mach.addChangeListener(this); + theta.addChangeListener(this); + aoa.addChangeListener(this); + roll.addChangeListener(this); + configuration.addChangeListener(this); + this.stateChanged(null); + + + + // Remove listeners when closing window + this.addWindowListener(new WindowAdapter() { + @Override + public void windowClosed(WindowEvent e) { + System.out.println("Closing method called: "+this); + theta.removeChangeListener(ComponentAnalysisDialog.this); + aoa.removeChangeListener(ComponentAnalysisDialog.this); + mach.removeChangeListener(ComponentAnalysisDialog.this); + roll.removeChangeListener(ComponentAnalysisDialog.this); + configuration.removeChangeListener(ComponentAnalysisDialog.this); + System.out.println("SETTING NAN VALUES"); + rocketPanel.setCPAOA(Double.NaN); + rocketPanel.setCPTheta(Double.NaN); + rocketPanel.setCPMach(Double.NaN); + rocketPanel.setCPRoll(Double.NaN); + singletonDialog = null; + } + }); + + + panel.add(new ResizeLabel("Reference length: ", -1), + "span, split, gapleft para, gapright rel"); + DoubleModel dm = new DoubleModel(conditions, "RefLength", UnitGroup.UNITS_LENGTH); + UnitSelector sel = new UnitSelector(dm, true); + sel.resizeFont(-1); + panel.add(sel, "gapright para"); + + panel.add(new ResizeLabel("Reference area: ", -1), "gapright rel"); + dm = new DoubleModel(conditions, "RefArea", UnitGroup.UNITS_AREA); + sel = new UnitSelector(dm, true); + sel.resizeFont(-1); + panel.add(sel, "wrap"); + + + + // Buttons + JButton button; + + // TODO: LOW: printing +// button = new JButton("Print"); +// button.addActionListener(new ActionListener() { +// public void actionPerformed(ActionEvent e) { +// try { +// table.print(); +// } catch (PrinterException e1) { +// JOptionPane.showMessageDialog(ComponentAnalysisDialog.this, +// "An error occurred while printing.", "Print error", +// JOptionPane.ERROR_MESSAGE); +// } +// } +// }); +// panel.add(button,"tag ok"); + + button = new JButton("Close"); + button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + ComponentAnalysisDialog.this.dispose(); + } + }); + panel.add(button,"span, split, tag cancel"); + + + setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + GUIUtil.installEscapeCloseOperation(this); + pack(); + } + + + + /** + * Updates the data in the table and fires a table data change event. + */ + @Override + public void stateChanged(ChangeEvent e) { + AerodynamicForces forces; + WarningSet set = new WarningSet(); + conditions.setAOA(aoa.getValue()); + conditions.setTheta(theta.getValue()); + conditions.setMach(mach.getValue()); + conditions.setRollRate(roll.getValue()); + conditions.setReference(configuration); + + if (worstToggle.isSelected()) { + calculator.getWorstCP(conditions, null); + if (!MathUtil.equals(conditions.getTheta(), theta.getValue())) { + fakeChange = true; + theta.setValue(conditions.getTheta()); // Fires a stateChanged event + fakeChange = false; + return; + } + } + + Map data = calculator.getForceAnalysis(conditions, set); + + cpData.clear(); + dragData.clear(); + rollData.clear(); + for (RocketComponent c: configuration) { + forces = data.get(c); + if (forces == null) + continue; + if (forces.cp != null) { + cpData.add(forces); + } + if (!Double.isNaN(forces.CD)) { + dragData.add(forces); + } + if (c instanceof FinSet) { + rollData.add(forces); + } + } + forces = data.get(configuration.getRocket()); + if (forces != null) { + cpData.add(forces); + dragData.add(forces); + rollData.add(forces); + totalCD = forces.CD; + } else { + totalCD = 0; + } + + // Set warnings + if (set.isEmpty()) { + warningList.setListData(new String[] { + "No warnings." + }); + } else { + warningList.setListData(new Vector(set)); + } + + cpTableModel.fireTableDataChanged(); + dragTableModel.fireTableDataChanged(); + rollTableModel.fireTableDataChanged(); + } + + + private class CustomCellRenderer extends JLabel implements TableCellRenderer { + private final Font normalFont; + private final Font boldFont; + + public CustomCellRenderer() { + super(); + normalFont = getFont(); + boldFont = normalFont.deriveFont(Font.BOLD); + } + @Override + public Component getTableCellRendererComponent(JTable table, Object value, + boolean isSelected, boolean hasFocus, int row, int column) { + + this.setText(value.toString()); + + if ((row < 0) || (row >= cpData.size())) + return this; + + if (cpData.get(row).component instanceof Rocket) { + this.setFont(boldFont); + } else { + this.setFont(normalFont); + } + return this; + } + } + + + + private class DragCellRenderer extends JLabel implements TableCellRenderer { + private final Font normalFont; + private final Font boldFont; + + private final float[] start = { 0.3333f, 0.2f, 1.0f }; + private final float[] end = { 0.0f, 0.8f, 1.0f }; + + + public DragCellRenderer(Color baseColor) { + super(); + normalFont = getFont(); + boldFont = normalFont.deriveFont(Font.BOLD); + } + @Override + public Component getTableCellRendererComponent(JTable table, Object value, + boolean isSelected, boolean hasFocus, int row, int column) { + + if (value instanceof Double) { + + // A drag coefficient + double cd = (Double)value; + this.setText(String.format("%.2f (%.0f%%)", cd, 100*cd/totalCD)); + + float r = (float)(cd/1.5); + + float hue = MathUtil.clamp(0.3333f * (1-2.0f*r), 0, 0.3333f); + float sat = MathUtil.clamp(0.8f*r + 0.1f*(1-r), 0, 1); + float val = 1.0f; + + this.setBackground(Color.getHSBColor(hue, sat, val)); + this.setOpaque(true); + this.setHorizontalAlignment(SwingConstants.CENTER); + + } else { + + // Other + this.setText(value.toString()); + this.setOpaque(false); + this.setHorizontalAlignment(SwingConstants.LEFT); + + } + + if ((row < 0) || (row >= dragData.size())) + return this; + + if ((dragData.get(row).component instanceof Rocket) || (column == 4)){ + this.setFont(boldFont); + } else { + this.setFont(normalFont); + } + return this; + } + } + + + ///////// Singleton implementation + + public static void showDialog(RocketPanel rocketpanel) { + if (singletonDialog != null) + singletonDialog.dispose(); + singletonDialog = new ComponentAnalysisDialog(rocketpanel); + singletonDialog.setVisible(true); + } + + public static void hideDialog() { + if (singletonDialog != null) + singletonDialog.dispose(); + } + +} diff --git a/src/net/sf/openrocket/gui/DescriptionArea.java b/src/net/sf/openrocket/gui/DescriptionArea.java new file mode 100644 index 000000000..ba29be47c --- /dev/null +++ b/src/net/sf/openrocket/gui/DescriptionArea.java @@ -0,0 +1,58 @@ +package net.sf.openrocket.gui; + +import java.awt.Dimension; +import java.awt.Rectangle; + +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.ScrollPaneConstants; + +import net.miginfocom.swing.MigLayout; + +public class DescriptionArea extends JScrollPane { + + private ResizeLabel text; + private MigLayout layout; + private JPanel panel; + + public DescriptionArea(int rows) { + this(rows, -2); + } + + public DescriptionArea(int rows, float size) { + super(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, + ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + + layout = new MigLayout("ins 0 2px, fill"); + panel = new JPanel(layout); + + text = new ResizeLabel(" ",size); + text.validate(); + Dimension dim = text.getPreferredSize(); + dim.height = (dim.height+2)*rows + 2; + this.setPreferredSize(dim); + + panel.add(text, "growx"); + + this.setViewportView(panel); + this.revalidate(); + } + + public void setText(String txt) { + if (!txt.startsWith("")) + txt = "" + txt; + text.setText(txt); + } + + + @Override + public void validate() { + + Rectangle dim = this.getViewportBorderBounds(); + layout.setComponentConstraints(text, "width "+ dim.width + ", growx"); + super.validate(); + text.validate(); + + } + +} diff --git a/src/net/sf/openrocket/gui/DetailDialog.java b/src/net/sf/openrocket/gui/DetailDialog.java new file mode 100644 index 000000000..f0c08308c --- /dev/null +++ b/src/net/sf/openrocket/gui/DetailDialog.java @@ -0,0 +1,18 @@ +package net.sf.openrocket.gui; + +import java.awt.Component; + +import javax.swing.JOptionPane; + +public class DetailDialog { + + public static void showDetailedMessageDialog(Component parentComponent, Object message, + String details, String title, int messageType) { + + // TODO: HIGH: Detailed dialog + JOptionPane.showMessageDialog(parentComponent, message, title, messageType, null); + + } + + +} diff --git a/src/net/sf/openrocket/gui/PreferencesDialog.java b/src/net/sf/openrocket/gui/PreferencesDialog.java new file mode 100644 index 000000000..e42d9f921 --- /dev/null +++ b/src/net/sf/openrocket/gui/PreferencesDialog.java @@ -0,0 +1,389 @@ +package net.sf.openrocket.gui; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.AbstractListModel; +import javax.swing.ComboBoxModel; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTabbedPane; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.unit.Unit; +import net.sf.openrocket.unit.UnitGroup; +import net.sf.openrocket.util.GUIUtil; +import net.sf.openrocket.util.Prefs; + +public class PreferencesDialog extends JDialog { + + private final List unitSelectors = new ArrayList(); + + private PreferencesDialog() { + super((JFrame)null, "Preferences", true); + + JPanel panel = new JPanel(new MigLayout("fill, gap unrel","[grow]","[grow][]")); + + JTabbedPane tabbedPane = new JTabbedPane(); + panel.add(tabbedPane,"grow, wrap"); + + + tabbedPane.addTab("Units", null, unitsPane(), "Default units"); + tabbedPane.addTab("Confirmation", null, confirmationPane(), "Confirmation dialog settings"); + + + + JButton close = new JButton("Close"); + close.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) { + PreferencesDialog.this.setVisible(false); + PreferencesDialog.this.dispose(); + } + }); + panel.add(close,"span, right, tag close"); + + this.setContentPane(panel); + pack(); + setAlwaysOnTop(true); + this.setLocationRelativeTo(null); + + this.addWindowListener(new WindowAdapter() { + @Override + public void windowClosed(WindowEvent e) { + Prefs.storeDefaultUnits(); + } + }); + + GUIUtil.setDefaultButton(close); + GUIUtil.installEscapeCloseOperation(this); + } + + + private JPanel confirmationPane() { + JPanel panel = new JPanel(new MigLayout("fill")); + + panel.add(new JLabel("Position to insert new body components:")); + panel.add(new JComboBox(new PrefChoiseSelector(Prefs.BODY_COMPONENT_INSERT_POSITION_KEY, + "Always ask", "Insert in middle", "Add to end")), "wrap para, sg combos"); + + panel.add(new JLabel("Confirm deletion of simulations:")); + panel.add(new JComboBox(new PrefBooleanSelector(Prefs.CONFIRM_DELETE_SIMULATION, + "Delete", "Confirm", true)), "wrap para, sg combos"); + + return panel; + } + + private JPanel unitsPane() { + JPanel panel = new JPanel(new MigLayout("", "[][]40lp[][]")); + JComboBox combo; + + panel.add(new JLabel("Select your preferred units:"), "span, wrap paragraph"); + +/* + public static final UnitGroup UNITS_LENGTH; + public static final UnitGroup UNITS_MOTOR_DIMENSIONS; + public static final UnitGroup UNITS_DISTANCE; + + public static final UnitGroup UNITS_VELOCITY; + public static final UnitGroup UNITS_ACCELERATION; + public static final UnitGroup UNITS_MASS; + public static final UnitGroup UNITS_FORCE; + public static final UnitGroup UNITS_IMPULSE; + + public static final UnitGroup UNITS_STABILITY; + public static final UnitGroup UNITS_FLIGHT_TIME; + public static final UnitGroup UNITS_ROLL; + + public static final UnitGroup UNITS_AREA; + public static final UnitGroup UNITS_DENSITY_LINE; + public static final UnitGroup UNITS_DENSITY_SURFACE; + public static final UnitGroup UNITS_DENSITY_BULK; + public static final UnitGroup UNITS_ROUGHNESS; + + public static final UnitGroup UNITS_TEMPERATURE; + public static final UnitGroup UNITS_PRESSURE; + public static final UnitGroup UNITS_ANGLE; +*/ + + panel.add(new JLabel("Rocket dimensions:")); + combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_LENGTH)); + panel.add(combo, "sizegroup boxes"); + + panel.add(new JLabel("Line density:")); + combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_DENSITY_LINE)); + panel.add(combo, "sizegroup boxes, wrap"); + + + + panel.add(new JLabel("Motor dimensions:")); + combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_MOTOR_DIMENSIONS)); + panel.add(combo, "sizegroup boxes"); + + panel.add(new JLabel("Surface density:")); + combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_DENSITY_SURFACE)); + panel.add(combo, "sizegroup boxes, wrap"); + + + + panel.add(new JLabel("Distance:")); + combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_DISTANCE)); + panel.add(combo, "sizegroup boxes"); + + panel.add(new JLabel("Bulk density::")); + combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_DENSITY_BULK)); + panel.add(combo, "sizegroup boxes, wrap"); + + + + panel.add(new JLabel("Velocity:")); + combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_VELOCITY)); + panel.add(combo, "sizegroup boxes"); + + panel.add(new JLabel("Surface roughness:")); + combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_ROUGHNESS)); + panel.add(combo, "sizegroup boxes, wrap"); + + + + panel.add(new JLabel("Acceleration:")); + combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_ACCELERATION)); + panel.add(combo, "sizegroup boxes"); + + panel.add(new JLabel("Area:")); + combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_AREA)); + panel.add(combo, "sizegroup boxes, wrap"); + + + + panel.add(new JLabel("Mass:")); + combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_MASS)); + panel.add(combo, "sizegroup boxes"); + + panel.add(new JLabel("Angle:")); + combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_ANGLE)); + panel.add(combo, "sizegroup boxes, wrap"); + + + + panel.add(new JLabel("Force:")); + combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_FORCE)); + panel.add(combo, "sizegroup boxes"); + + panel.add(new JLabel("Roll rate:")); + combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_ROLL)); + panel.add(combo, "sizegroup boxes, wrap"); + + + + panel.add(new JLabel("Total impulse:")); + combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_IMPULSE)); + panel.add(combo, "sizegroup boxes"); + + panel.add(new JLabel("Temperature:")); + combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_TEMPERATURE)); + panel.add(combo, "sizegroup boxes, wrap"); + + + + panel.add(new JLabel("Stability:")); + combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_STABILITY)); + panel.add(combo, "sizegroup boxes"); + + panel.add(new JLabel("Pressure:")); + combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_PRESSURE)); + panel.add(combo, "sizegroup boxes, wrap para"); + + + + JButton button = new JButton("Default metric"); + button.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + UnitGroup.setDefaultMetricUnits(); + for (DefaultUnitSelector s: unitSelectors) + s.fireChange(); + } + }); + panel.add(button, "spanx, split 2, grow"); + + button = new JButton("Default imperial"); + button.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + UnitGroup.setDefaultImperialUnits(); + for (DefaultUnitSelector s: unitSelectors) + s.fireChange(); + } + }); + panel.add(button, "grow, wrap para"); + + + panel.add(new ResizeLabel("The effects will take place the next time you open a window.",-2), + "spanx, wrap"); + + + return panel; + } + + + + + private class DefaultUnitSelector extends AbstractListModel implements ComboBoxModel { + + private final UnitGroup group; + public DefaultUnitSelector(UnitGroup group) { + this.group = group; + unitSelectors.add(this); + } + + @Override + public Object getSelectedItem() { + return group.getDefaultUnit(); + } + @Override + public void setSelectedItem(Object item) { + if (!(item instanceof Unit)) { + throw new IllegalArgumentException("Illegal argument "+item); + } + group.setDefaultUnit(group.getUnitIndex((Unit)item)); + } + @Override + public Object getElementAt(int index) { + return group.getUnit(index); + } + @Override + public int getSize() { + return group.getUnitCount(); + } + + + public void fireChange() { + this.fireContentsChanged(this, 0, this.getSize()); + } + } + + + + private class PrefChoiseSelector extends AbstractListModel implements ComboBoxModel { + private final String preference; + private final String[] descriptions; + + public PrefChoiseSelector(String preference, String ... descriptions) { + this.preference = preference; + this.descriptions = descriptions; + } + + @Override + public Object getSelectedItem() { + return descriptions[Prefs.getChoise(preference, descriptions.length, 0)]; + } + + @Override + public void setSelectedItem(Object item) { + if (!(item instanceof String)) { + throw new IllegalArgumentException("Illegal argument "+item); + } + int index; + for (index = 0; index < descriptions.length; index++) { + if (((String)item).equalsIgnoreCase(descriptions[index])) + break; + } + if (index >= descriptions.length) { + throw new IllegalArgumentException("Illegal argument "+item); + } + + Prefs.putChoise(preference, index); + } + + @Override + public Object getElementAt(int index) { + return descriptions[index]; + } + @Override + public int getSize() { + return descriptions.length; + } + } + + + private class PrefBooleanSelector extends AbstractListModel implements ComboBoxModel { + private final String preference; + private final String trueDesc, falseDesc; + private final boolean def; + + public PrefBooleanSelector(String preference, String falseDescription, + String trueDescription, boolean defaultState) { + this.preference = preference; + this.trueDesc = trueDescription; + this.falseDesc = falseDescription; + this.def = defaultState; + } + + @Override + public Object getSelectedItem() { + if (Prefs.NODE.getBoolean(preference, def)) { + return trueDesc; + } else { + return falseDesc; + } + } + + @Override + public void setSelectedItem(Object item) { + if (!(item instanceof String)) { + throw new IllegalArgumentException("Illegal argument "+item); + } + + if (trueDesc.equals(item)) { + Prefs.NODE.putBoolean(preference, true); + } else if (falseDesc.equals(item)) { + Prefs.NODE.putBoolean(preference, false); + } else { + throw new IllegalArgumentException("Illegal argument "+item); + } + } + + @Override + public Object getElementAt(int index) { + switch (index) { + case 0: + return def ? trueDesc : falseDesc; + + case 1: + return def ? falseDesc: trueDesc; + + default: + throw new IndexOutOfBoundsException("Boolean asked for index="+index); + } + } + @Override + public int getSize() { + return 2; + } + } + + + + //////// Singleton implementation //////// + + private static PreferencesDialog dialog = null; + + public static void showPreferences() { + if (dialog != null) { + dialog.dispose(); + } + dialog = new PreferencesDialog(); + dialog.setVisible(true); + } + + +} diff --git a/src/net/sf/openrocket/gui/Resettable.java b/src/net/sf/openrocket/gui/Resettable.java new file mode 100644 index 000000000..1f30d2b7f --- /dev/null +++ b/src/net/sf/openrocket/gui/Resettable.java @@ -0,0 +1,15 @@ +package net.sf.openrocket.gui; + +/** + * An interface for GUI elements with a resettable model. The resetModel() method in + * this interface resets the model to some default model, releasing the old model + * listening connections. + * + * Some components that don't have a settable model simply release the current model. + * These components cannot therefore be reused after calling resetModel(). + * + * @author Sampo Niskanen + */ +public interface Resettable { + public void resetModel(); +} diff --git a/src/net/sf/openrocket/gui/ResizeLabel.java b/src/net/sf/openrocket/gui/ResizeLabel.java new file mode 100644 index 000000000..6951d6282 --- /dev/null +++ b/src/net/sf/openrocket/gui/ResizeLabel.java @@ -0,0 +1,47 @@ +package net.sf.openrocket.gui; + +import java.awt.Font; +import javax.swing.JLabel; + +/** + * A resizeable JLabel. The method resizeFont(float) changes the current font size by the + * given (positive or negative) amount. The change is relative to the current font size. + *

+ * A nice small text is achievable by new ResizeLabel("My text", -2); + * + * @author Sampo Niskanen + */ + +public class ResizeLabel extends JLabel { + + public ResizeLabel() { + super(); + } + + public ResizeLabel(String text) { + super(text); + } + + public ResizeLabel(float size) { + super(); + resizeFont(size); + } + + public ResizeLabel(String text, float size) { + super(text); + resizeFont(size); + } + + public ResizeLabel(String text, int horizontalAlignment, float size) { + super(text, horizontalAlignment); + resizeFont(size); + } + + + public void resizeFont(float size) { + Font font = this.getFont(); + font = font.deriveFont(font.getSize2D()+size); + this.setFont(font); + } + +} diff --git a/src/net/sf/openrocket/gui/SpinnerEditor.java b/src/net/sf/openrocket/gui/SpinnerEditor.java new file mode 100644 index 000000000..843eed6a7 --- /dev/null +++ b/src/net/sf/openrocket/gui/SpinnerEditor.java @@ -0,0 +1,21 @@ +package net.sf.openrocket.gui; + +import javax.swing.JSpinner; + +/** + * Editable editor for a JSpinner. Simply uses JSpinner.DefaultEditor, which has been made + * editable. Why the f*** isn't this possible in the normal API? + * + * @author Sampo Niskanen + */ + +public class SpinnerEditor extends JSpinner.NumberEditor { +//public class SpinnerEditor extends JSpinner.DefaultEditor { + + public SpinnerEditor(JSpinner spinner) { + //super(spinner); + super(spinner,"0.0##"); + //getTextField().setEditable(true); + } + +} diff --git a/src/net/sf/openrocket/gui/StageSelector.java b/src/net/sf/openrocket/gui/StageSelector.java new file mode 100644 index 000000000..a0ad43762 --- /dev/null +++ b/src/net/sf/openrocket/gui/StageSelector.java @@ -0,0 +1,108 @@ +package net.sf.openrocket.gui; + +import java.awt.event.ActionEvent; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.AbstractAction; +import javax.swing.JPanel; +import javax.swing.JToggleButton; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.rocketcomponent.Configuration; + + +public class StageSelector extends JPanel implements ChangeListener { + + private final Configuration configuration; + + private List buttons = new ArrayList(); + + public StageSelector(Configuration configuration) { + super(new MigLayout("gap 0!")); + this.configuration = configuration; + + JToggleButton button = new JToggleButton(new StageAction(0)); + this.add(button); + buttons.add(button); + + updateButtons(); + configuration.addChangeListener(this); + } + + private void updateButtons() { + int stages = configuration.getStageCount(); + if (buttons.size() == stages) + return; + + while (buttons.size() > stages) { + JToggleButton button = buttons.remove(buttons.size()-1); + this.remove(button); + } + + while (buttons.size() < stages) { + JToggleButton button = new JToggleButton(new StageAction(buttons.size())); + this.add(button); + buttons.add(button); + } + + this.revalidate(); + } + + + + + @Override + public void stateChanged(ChangeEvent e) { + updateButtons(); + } + + + private class StageAction extends AbstractAction implements ChangeListener { + private final int stage; + + public StageAction(final int stage) { + this.stage = stage; + configuration.addChangeListener(this); + stateChanged(null); + } + + @Override + public Object getValue(String key) { + if (key.equals(NAME)) { + return "Stage "+(stage+1); + } + return super.getValue(key); + } + + @Override + public void actionPerformed(ActionEvent e) { + configuration.setToStage(stage); + +// boolean state = (Boolean)getValue(SELECTED_KEY); +// if (state == true) { +// // Was disabled, now enabled +// configuration.setToStage(stage); +// } else { +// // Was enabled, check what to do +// if (configuration.isStageActive(stage + 1)) { +// configuration.setToStage(stage); +// } else { +// if (stage == 0) +// configuration.setAllStages(); +// else +// configuration.setToStage(stage-1); +// } +// } +// stateChanged(null); + } + + + @Override + public void stateChanged(ChangeEvent e) { + this.putValue(SELECTED_KEY, configuration.isStageActive(stage)); + } + } +} diff --git a/src/net/sf/openrocket/gui/StorageOptionChooser.java b/src/net/sf/openrocket/gui/StorageOptionChooser.java new file mode 100644 index 000000000..9b8f1aa46 --- /dev/null +++ b/src/net/sf/openrocket/gui/StorageOptionChooser.java @@ -0,0 +1,204 @@ +package net.sf.openrocket.gui; + +import javax.swing.BorderFactory; +import javax.swing.ButtonGroup; +import javax.swing.JCheckBox; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JSpinner; +import javax.swing.SpinnerNumberModel; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.document.OpenRocketDocument; +import net.sf.openrocket.document.Simulation; +import net.sf.openrocket.document.StorageOptions; +import net.sf.openrocket.simulation.FlightData; +import net.sf.openrocket.simulation.FlightDataBranch; + +public class StorageOptionChooser extends JPanel { + + public static final double DEFAULT_SAVE_TIME_SKIP = 0.20; + + private JRadioButton allButton; + private JRadioButton someButton; + private JRadioButton noneButton; + + private JSpinner timeSpinner; + + private JCheckBox compressButton; + + + private boolean artificialEvent = false; + + public StorageOptionChooser(StorageOptions opts) { + super(new MigLayout()); + + ButtonGroup buttonGroup = new ButtonGroup(); + String tip; + + this.add(new JLabel("Simulated data to store:"), "spanx, wrap unrel"); + + allButton = new JRadioButton("All simulated data"); + allButton.setToolTipText("Store all simulated data.
" + + "This can result in very large files!"); + buttonGroup.add(allButton); + this.add(allButton, "spanx, wrap rel"); + + + someButton = new JRadioButton("Every"); + tip = "Store plottable values approximately this far apart.
" + + "Larger values result in smaller files."; + someButton.setToolTipText(tip); + buttonGroup.add(someButton); + this.add(someButton, ""); + + timeSpinner = new JSpinner(new SpinnerNumberModel(0.0, 0.0, 5.0, 0.1)); + timeSpinner.setToolTipText(tip); + timeSpinner.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + if (artificialEvent) + return; + someButton.setSelected(true); + } + }); + this.add(timeSpinner, "wmin 55lp"); + + JLabel label = new JLabel("seconds"); + label.setToolTipText(tip); + this.add(label, "wrap rel"); + + + noneButton = new JRadioButton("Only primary figures"); + noneButton.setToolTipText("Store only the values shown in the summary table.
" + + "This results in the smallest files."); + buttonGroup.add(noneButton); + + this.add(noneButton, "spanx, wrap 20lp"); + + + compressButton = new JCheckBox("Compress file"); + compressButton.setToolTipText("Using compression reduces the file size significantly."); + this.add(compressButton, "spanx"); + + + this.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createEmptyBorder(0, 10, 0, 0), + BorderFactory.createTitledBorder("Save options"))); + + loadOptions(opts); + } + + + public void loadOptions(StorageOptions opts) { + double t; + + // Data storage radio button + t = opts.getSimulationTimeSkip(); + if (t == StorageOptions.SIMULATION_DATA_ALL) { + allButton.setSelected(true); + t = DEFAULT_SAVE_TIME_SKIP; + } else if (t == StorageOptions.SIMULATION_DATA_NONE) { + noneButton.setSelected(true); + t = DEFAULT_SAVE_TIME_SKIP; + } else { + someButton.setSelected(true); + } + + // Time skip spinner + artificialEvent = true; + timeSpinner.setValue(t); + artificialEvent = false; + + // Compression checkbox + compressButton.setSelected(opts.isCompressionEnabled()); + } + + + public void storeOptions(StorageOptions opts) { + double t; + + if (allButton.isSelected()) { + t = StorageOptions.SIMULATION_DATA_ALL; + } else if (noneButton.isSelected()) { + t = StorageOptions.SIMULATION_DATA_NONE; + } else { + t = (Double)timeSpinner.getValue(); + } + + opts.setSimulationTimeSkip(t); + + opts.setCompressionEnabled(compressButton.isSelected()); + + opts.setExplicitlySet(true); + } + + + + /** + * Asks the user the storage options using a modal dialog window if the document + * contains simulated data and the user has not explicitly set how to store the data. + * + * @param document the document to check. + * @param parent the parent frame for the dialog. + * @return true to continue, false if the user cancelled. + */ + public static boolean verifyStorageOptions(OpenRocketDocument document, JFrame parent) { + StorageOptions options = document.getDefaultStorageOptions(); + + if (options.isExplicitlySet()) { + // User has explicitly set the values, save as is + return true; + } + + + boolean hasData = false; + + simulationLoop: + for (Simulation s: document.getSimulations()) { + if (s.getStatus() == Simulation.Status.NOT_SIMULATED || + s.getStatus() == Simulation.Status.EXTERNAL) + continue; + + FlightData data = s.getSimulatedData(); + if (data == null) + continue; + + for (int i=0; i < data.getBranchCount(); i++) { + FlightDataBranch branch = data.getBranch(i); + if (branch == null) + continue; + if (branch.getLength() > 0) { + hasData = true; + break simulationLoop; + } + } + } + + + if (!hasData) { + // No data to store, do not ask only about compression + return true; + } + + + StorageOptionChooser chooser = new StorageOptionChooser(options); + + if (JOptionPane.showConfirmDialog(parent, chooser, "Save options", + JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE) != + JOptionPane.OK_OPTION) { + // User cancelled + return false; + } + + chooser.storeOptions(options); + return true; + } + + +} diff --git a/src/net/sf/openrocket/gui/TextFieldListener.java b/src/net/sf/openrocket/gui/TextFieldListener.java new file mode 100644 index 000000000..d07730046 --- /dev/null +++ b/src/net/sf/openrocket/gui/TextFieldListener.java @@ -0,0 +1,35 @@ +package net.sf.openrocket.gui; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; + +import javax.swing.JTextField; + +public abstract class TextFieldListener implements ActionListener, FocusListener { + private JTextField field; + + public void listenTo(JTextField newField) { + if (field != null) { + field.removeActionListener(this); + field.removeFocusListener(this); + } + field = newField; + if (field != null) { + field.addActionListener(this); + field.addFocusListener(this); + } + } + + public abstract void setText(String text); + + public void actionPerformed(ActionEvent e) { + setText(field.getText()); + } + public void focusGained(FocusEvent e) { } + public void focusLost(FocusEvent e) { + setText(field.getText()); + } + +} \ No newline at end of file diff --git a/src/net/sf/openrocket/gui/UnitSelector.java b/src/net/sf/openrocket/gui/UnitSelector.java new file mode 100644 index 000000000..37453ef46 --- /dev/null +++ b/src/net/sf/openrocket/gui/UnitSelector.java @@ -0,0 +1,314 @@ +package net.sf.openrocket.gui; + + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.ItemSelectable; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.Action; +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; +import javax.swing.border.Border; +import javax.swing.border.CompoundBorder; +import javax.swing.border.EmptyBorder; +import javax.swing.border.LineBorder; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import net.sf.openrocket.gui.adaptors.DoubleModel; +import net.sf.openrocket.unit.Unit; +import net.sf.openrocket.unit.UnitGroup; + + +/** + * A Swing component that allows one to choose a unit from a UnitGroup within + * a DoubleModel model. The current unit of the model is shown as a JLabel, and + * the unit can be changed by clicking on the label. + * + * @author Sampo Niskanen + */ + +public class UnitSelector extends ResizeLabel implements ChangeListener, MouseListener, + ItemSelectable { + + private final DoubleModel model; + private final Action[] extraActions; + + private UnitGroup unitGroup; + private Unit currentUnit; + + private final boolean showValue; + + private final Border normalBorder; + private final Border withinBorder; + + + private final List itemListeners = new ArrayList(); + + + /** + * Common private constructor that sets the values and sets up the borders. + * Either model or group must be null. + * + * @param model + * @param showValue + * @param group + * @param actions + */ + private UnitSelector(DoubleModel model, boolean showValue, UnitGroup group, + Action[] actions) { + super(); + + this.model = model; + this.showValue = showValue; + + if (model != null) { + this.unitGroup = model.getUnitGroup(); + this.currentUnit = model.getCurrentUnit(); + } else { + this.unitGroup = group; + this.currentUnit = group.getDefaultUnit(); + } + + this.extraActions = actions; + + addMouseListener(this); + + // Define borders to use: + + normalBorder = new CompoundBorder( + new LineBorder(new Color(0f, 0f, 0f, 0.08f), 1), new EmptyBorder(1, 1, 1, + 1)); + withinBorder = new CompoundBorder(new LineBorder(new Color(0f, 0f, 0f, 0.6f)), + new EmptyBorder(1, 1, 1, 1)); + + setBorder(normalBorder); + updateText(); + } + + + + public UnitSelector(DoubleModel model, Action... actions) { + this(model, false, actions); + } + + public UnitSelector(DoubleModel model, boolean showValue, Action... actions) { + this(model, showValue, null, actions); + + // Add model listener + this.model.addChangeListener(this); + } + + + public UnitSelector(UnitGroup group, Action... actions) { + this(null, false, group, actions); + } + + + + + /** + * Return the DoubleModel that is backing this selector up, or null. + * Either this method or {@link #getUnitGroup()} always returns null. + * + * @return the DoubleModel being used, or null. + */ + public DoubleModel getModel() { + return model; + } + + + /** + * Return the unit group that is being shown, or null. Either this method + * or {@link #getModel()} always returns null. + * + * @return the UnitGroup being used, or null. + */ + public UnitGroup getUnitGroup() { + return unitGroup; + } + + + public void setUnitGroup(UnitGroup group) { + if (model != null) { + throw new IllegalStateException( + "UnitGroup cannot be set when backed up with model."); + } + + if (this.unitGroup == group) + return; + + this.unitGroup = group; + this.currentUnit = group.getDefaultUnit(); + updateText(); + } + + + /** + * Return the currently selected unit. Works both when backup up with a DoubleModel + * and UnitGroup. + * + * @return the currently selected unit. + */ + public Unit getSelectedUnit() { + return currentUnit; + } + + + /** + * Set the currently selected unit. Sets it to the DoubleModel if it is backed up + * by it. + * + * @param unit the unit to select. + */ + public void setSelectedUnit(Unit unit) { + if (!unitGroup.contains(unit)) { + throw new IllegalArgumentException("unit " + unit + + " not contained in group " + unitGroup); + } + + this.currentUnit = unit; + if (model != null) { + model.setCurrentUnit(unit); + } + updateText(); + fireItemEvent(); + } + + + + /** + * Updates the text of the label + */ + private void updateText() { + if (model != null) { + + Unit unit = model.getCurrentUnit(); + if (showValue) { + setText(unit.toStringUnit(model.getValue())); + } else { + setText(unit.getUnit()); + } + + } else if (unitGroup != null) { + + setText(currentUnit.getUnit()); + + } else { + throw new IllegalStateException("Both model and unitGroup are null."); + } + } + + + /** + * Update the component when the DoubleModel changes. + */ + public void stateChanged(ChangeEvent e) { + updateText(); + } + + + + //////// ItemListener handling //////// + + public void addItemListener(ItemListener listener) { + itemListeners.add(listener); + } + + public void removeItemListener(ItemListener listener) { + itemListeners.remove(listener); + } + + protected void fireItemEvent() { + ItemEvent event = null; + ItemListener[] listeners = itemListeners.toArray(new ItemListener[0]); + for (ItemListener l: listeners) { + if (event == null) { + event = new ItemEvent(this, ItemEvent.ITEM_STATE_CHANGED, getSelectedUnit(), + ItemEvent.SELECTED); + } + l.itemStateChanged(event); + } + } + + + + //////// Popup //////// + + private void popup() { + JPopupMenu popup = new JPopupMenu(); + + for (int i = 0; i < unitGroup.getUnitCount(); i++) { + Unit unit = unitGroup.getUnit(i); + JMenuItem item = new JMenuItem(unit.getUnit()); + item.addActionListener(new UnitSelectorItem(unit)); + popup.add(item); + } + + for (int i = 0; i < extraActions.length; i++) { + if (extraActions[i] == null && i < extraActions.length - 1) { + popup.addSeparator(); + } else { + popup.add(new JMenuItem(extraActions[i])); + } + } + + Dimension d = getSize(); + popup.show(this, 0, d.height); + } + + + /** + * ActionListener class that sets the currently selected unit. + */ + private class UnitSelectorItem implements ActionListener { + private final Unit unit; + + public UnitSelectorItem(Unit u) { + unit = u; + } + + public void actionPerformed(ActionEvent e) { + setSelectedUnit(unit); + } + } + + + @Override + public Object[] getSelectedObjects() { + return new Object[]{ getSelectedUnit() }; + } + + + + //////// Mouse handling //////// + + public void mouseClicked(MouseEvent e) { + if (unitGroup.getUnitCount() > 1) + popup(); + } + + public void mouseEntered(MouseEvent e) { + if (unitGroup.getUnitCount() > 1) + setBorder(withinBorder); + } + + public void mouseExited(MouseEvent e) { + setBorder(normalBorder); + } + + public void mousePressed(MouseEvent e) { + } // Ignore + + public void mouseReleased(MouseEvent e) { + } // Ignore + +} diff --git a/src/net/sf/openrocket/gui/adaptors/BooleanModel.java b/src/net/sf/openrocket/gui/adaptors/BooleanModel.java new file mode 100644 index 000000000..ab68c336a --- /dev/null +++ b/src/net/sf/openrocket/gui/adaptors/BooleanModel.java @@ -0,0 +1,241 @@ +package net.sf.openrocket.gui.adaptors; + +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.AbstractAction; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import net.sf.openrocket.util.ChangeSource; + + +/** + * A class that adapts an isXXX/setXXX boolean variable. It functions as an Action suitable + * for usage in JCheckBox or JToggleButton. You can create a suitable button with + * + * check = new JCheckBox(new BooleanModel(component,"Value")) + * check.setText("Label"); + * + * This will produce a button that uses isValue() and setValue(boolean) of the corresponding + * component. + *

+ * Additionally a number of component enabled states may be controlled by this class using + * the method {@link #addEnableComponent(Component, boolean)}. + * + * @author Sampo Niskanen + */ + +public class BooleanModel extends AbstractAction implements ChangeListener { + + private final ChangeSource source; + private final String valueName; + + private final Method getMethod; + private final Method setMethod; + private final Method getEnabled; + + private final List components = new ArrayList(); + private final List componentEnableState = new ArrayList(); + + private int firing = 0; + + private boolean oldValue; + private boolean oldEnabled; + + public BooleanModel(ChangeSource source, String valueName) { + this.source = source; + this.valueName = valueName; + + Method getter=null, setter=null; + + + // Try get/is and set + try { + getter = source.getClass().getMethod("is" + valueName); + } catch (NoSuchMethodException ignore) { } + if (getter == null) { + try { + getter = source.getClass().getMethod("get" + valueName); + } catch (NoSuchMethodException ignore) { } + } + try { + setter = source.getClass().getMethod("set" + valueName,boolean.class); + } catch (NoSuchMethodException ignore) { } + + if (getter==null || setter==null) { + throw new IllegalArgumentException("get/is methods for boolean '"+valueName+ + "' not present in class "+source.getClass().getCanonicalName()); + } + + getMethod = getter; + setMethod = setter; + + Method e = null; + try { + e = source.getClass().getMethod("is" + valueName + "Enabled"); + } catch (NoSuchMethodException ignore) { } + getEnabled = e; + + oldValue = getValue(); + oldEnabled = getIsEnabled(); + + this.setEnabled(oldEnabled); + this.putValue(SELECTED_KEY, oldValue); + + source.addChangeListener(this); + } + + public boolean getValue() { + try { + return (Boolean)getMethod.invoke(source); + } catch (IllegalAccessException e) { + throw new RuntimeException("getMethod execution error for source "+source,e); + } catch (InvocationTargetException e) { + throw new RuntimeException("getMethod execution error for source "+source,e); + } + } + + public void setValue(boolean b) { + try { + setMethod.invoke(source, new Object[] { (Boolean)b }); + } catch (IllegalAccessException e) { + throw new RuntimeException("setMethod execution error for source "+source,e); + } catch (InvocationTargetException e) { + throw new RuntimeException("setMethod execution error for source "+source,e); + } + } + + + /** + * Add a component the enabled status of which will be controlled by the value + * of this boolean. The component will be enabled exactly when + * the state of this model is equal to that of enableState. + * + * @param component the component to control. + * @param enableState the state in which the component should be enabled. + */ + public void addEnableComponent(Component component, boolean enableState) { + components.add(component); + componentEnableState.add(enableState); + updateEnableStatus(); + } + + /** + * Add a component which will be enabled when this boolean is true. + * This is equivalent to booleanModel.addEnableComponent(component, true). + * + * @param component the component to control. + * @see #addEnableComponent(Component, boolean) + */ + public void addEnableComponent(Component component) { + addEnableComponent(component, true); + } + + private void updateEnableStatus() { + boolean state = getValue(); + + for (int i=0; i < components.size(); i++) { + Component c = components.get(i); + boolean b = componentEnableState.get(i); + c.setEnabled(state == b); + } + } + + +// @Override +// public boolean isEnabled() { +// if (getEnabled == null) +// return true; +// try { +// return (Boolean)getEnabled.invoke(source); +// } catch (IllegalAccessException e) { +// throw new RuntimeException("getEnabled execution error for source "+source,e); +// } catch (InvocationTargetException e) { +// throw new RuntimeException("getEnabled execution error for source "+source,e); +// } +// } + + + private boolean getIsEnabled() { + if (getEnabled == null) + return true; + try { + return (Boolean)getEnabled.invoke(source); + } catch (IllegalAccessException e) { + throw new RuntimeException("getEnabled execution error for source "+source,e); + } catch (InvocationTargetException e) { + throw new RuntimeException("getEnabled execution error for source "+source,e); + } + } + +// @Override +// public Object getValue(String key) { +// if (key.equals(SELECTED_KEY)) { +// return getValue(); +// } +// return super.getValue(key); +// } +// +// @Override +// public void putValue(String key, Object value) { +// if (firing > 0) // Ignore if currently firing event +// return; +// if (key.equals(SELECTED_KEY) && (value instanceof Boolean)) { +// setValue((Boolean)value); +// } else { +// super.putValue(key, value); +// } +// updateEnableStatus(); +// } + + + @Override + public void stateChanged(ChangeEvent event) { + if (firing > 0) + return; + + boolean v = getValue(); + boolean e = getIsEnabled(); + if (oldValue != v) { + oldValue = v; + firing++; + this.putValue(SELECTED_KEY, getValue()); +// this.firePropertyChange(SELECTED_KEY, !v, v); + updateEnableStatus(); + firing--; + } + if (oldEnabled != e) { + oldEnabled = e; + setEnabled(e); + } + } + + + @Override + public void actionPerformed(ActionEvent e) { + if (firing > 0) + return; + + boolean v = (Boolean)this.getValue(SELECTED_KEY); + if (v != oldValue) { + firing++; + setValue(v); + oldValue = getValue(); + // Update all states + this.putValue(SELECTED_KEY, oldValue); + this.setEnabled(getIsEnabled()); + updateEnableStatus(); + firing--; + } + } + + @Override + public String toString() { + return "BooleanModel["+source.getClass().getCanonicalName()+":"+valueName+"]"; + } +} diff --git a/src/net/sf/openrocket/gui/adaptors/Column.java b/src/net/sf/openrocket/gui/adaptors/Column.java new file mode 100644 index 000000000..3b22d11b2 --- /dev/null +++ b/src/net/sf/openrocket/gui/adaptors/Column.java @@ -0,0 +1,60 @@ +package net.sf.openrocket.gui.adaptors; + +import javax.swing.table.TableColumnModel; + +public abstract class Column { + private final String name; + + /** + * Create a new column with specified name. Additionally, the {@link #getValueAt(int)} + * method must be implemented. + * + * @param name the caption of the column. + */ + public Column(String name) { + this.name = name; + } + + /** + * Return the caption of the column. + */ + @Override + public String toString() { + return name; + } + + /** + * Return the default width of the column. This is used by the method + * {@link #ColumnTableModel.setColumnWidth(TableColumnModel)}. The default width is + * 100, the method may be overridden to return other values relative to this value. + * + * @return the relative width of the column (default 100). + */ + public int getDefaultWidth() { + return 100; + } + + + /** + * Returns the exact width of this column. If the return value is positive, + * both the minimum and maximum widths of this column are set to this value + * + * @return the absolute exact width of the column (default 0). + */ + public int getExactWidth() { + return 0; + } + + + public Class getColumnClass() { + return Object.class; + } + + /** + * Return the value in this column at the specified row. + * + * @param row the row of the data. + * @return the value at the specified position. + */ + public abstract Object getValueAt(int row); +} diff --git a/src/net/sf/openrocket/gui/adaptors/ColumnTableModel.java b/src/net/sf/openrocket/gui/adaptors/ColumnTableModel.java new file mode 100644 index 000000000..651738c18 --- /dev/null +++ b/src/net/sf/openrocket/gui/adaptors/ColumnTableModel.java @@ -0,0 +1,54 @@ +package net.sf.openrocket.gui.adaptors; + +import javax.swing.table.AbstractTableModel; +import javax.swing.table.TableColumn; +import javax.swing.table.TableColumnModel; + +public abstract class ColumnTableModel extends AbstractTableModel { + private final Column[] columns; + + public ColumnTableModel(Column... columns) { + this.columns = columns; + } + + public void setColumnWidths(TableColumnModel model) { + for (int i=0; i < columns.length; i++) { + if (columns[i].getExactWidth() > 0) { + TableColumn col = model.getColumn(i); + int w = columns[i].getExactWidth(); + col.setResizable(false); + col.setMinWidth(w); + col.setMaxWidth(w); + col.setPreferredWidth(w); + } else { + model.getColumn(i).setPreferredWidth(columns[i].getDefaultWidth()); + } + } + } + + @Override + public int getColumnCount() { + return columns.length; + } + + @Override + public String getColumnName(int col) { + return columns[col].toString(); + } + + @Override + public Class getColumnClass(int col) { + return columns[col].getColumnClass(); + } + + @Override + public Object getValueAt(int row, int col) { + if ((row < 0) || (row >= getRowCount()) || + (col < 0) || (col >= columns.length)) { + System.err.println("Error: Requested illegal column/row = "+col+"/"+row+"."); + assert(false); + return null; + } + return columns[col].getValueAt(row); + } +} diff --git a/src/net/sf/openrocket/gui/adaptors/DoubleModel.java b/src/net/sf/openrocket/gui/adaptors/DoubleModel.java new file mode 100644 index 000000000..0004dbc46 --- /dev/null +++ b/src/net/sf/openrocket/gui/adaptors/DoubleModel.java @@ -0,0 +1,778 @@ +package net.sf.openrocket.gui.adaptors; + +import java.awt.event.ActionEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.BoundedRangeModel; +import javax.swing.SpinnerModel; +import javax.swing.SpinnerNumberModel; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import net.sf.openrocket.unit.Unit; +import net.sf.openrocket.unit.UnitGroup; +import net.sf.openrocket.util.ChangeSource; +import net.sf.openrocket.util.MathUtil; + + +/** + * A model connector that can read and modify any value of any ChangeSource that + * has the appropriate get/set methods defined. + * + * The variable is defined in the constructor by providing the variable name as a string + * (e.g. "Radius" -> getRadius()/setRadius()). Additional scaling may be applied, e.g. a + * DoubleModel for the diameter can be defined by the variable "Radius" and a multiplier of 2. + * + * Sub-models suitable for JSpinners and other components are available from the appropriate + * methods. + * + * @author Sampo Niskanen + */ + +public class DoubleModel implements ChangeListener, ChangeSource { + private static final boolean DEBUG_LISTENERS = false; + + //////////// JSpinner Model //////////// + + /** + * Model suitable for JSpinner using JSpinner.NumberEditor. It extends SpinnerNumberModel + * to be compatible with the NumberEditor, but only has the necessary methods defined. + */ + private class ValueSpinnerModel extends SpinnerNumberModel { + + @Override + public Object getValue() { + return currentUnit.toUnit(DoubleModel.this.getValue()); +// return makeString(currentUnit.toUnit(DoubleModel.this.getValue())); + } + + @Override + public void setValue(Object value) { + + System.out.println("setValue("+value+") called, valueName="+valueName+ + " firing="+firing); + + if (firing > 0) // Ignore, if called when model is sending events + return; + Number num = (Number)value; + double newValue = num.doubleValue(); + DoubleModel.this.setValue(currentUnit.fromUnit(newValue)); + + +// try { +// double newValue = Double.parseDouble((String)value); +// DoubleModel.this.setValue(currentUnit.fromUnit(newValue)); +// } catch (NumberFormatException e) { +// DoubleModel.this.fireStateChanged(); +// }; + } + + @Override + public Object getNextValue() { + double d = currentUnit.toUnit(DoubleModel.this.getValue()); + double max = currentUnit.toUnit(maxValue); + if (MathUtil.equals(d,max)) + return null; + d = currentUnit.getNextValue(d); + if (d > max) + d = max; + return d; +// return makeString(d); + } + + @Override + public Object getPreviousValue() { + double d = currentUnit.toUnit(DoubleModel.this.getValue()); + double min = currentUnit.toUnit(minValue); + if (MathUtil.equals(d,min)) + return null; + d = currentUnit.getPreviousValue(d); + if (d < min) + d = min; + return d; +// return makeString(d); + } + + + @Override + public Comparable getMinimum() { + return currentUnit.toUnit(minValue); + } + + @Override + public Comparable getMaximum() { + return currentUnit.toUnit(maxValue); + } + + + @Override + public void addChangeListener(ChangeListener l) { + DoubleModel.this.addChangeListener(l); + } + + @Override + public void removeChangeListener(ChangeListener l) { + DoubleModel.this.removeChangeListener(l); + } + } + + /** + * Returns a new SpinnerModel with the same base as the DoubleModel. + * The values given to the JSpinner are in the currently selected units. + * + * @return A compatibility layer for a SpinnerModel. + */ + public SpinnerModel getSpinnerModel() { + return new ValueSpinnerModel(); + } + + + + + + //////////// JSlider model //////////// + + private class ValueSliderModel implements BoundedRangeModel, ChangeListener { + private static final int MAX = 1000; + + /* + * Use linear scale value = linear1 * x + linear0 when x < linearPosition + * Use quadratic scale value = quad2 * x^2 + quad1 * x + quad0 otherwise + */ + + // Linear in range x <= linearPosition + private final double linearPosition; + + // May be changing DoubleModels when using linear model + private final DoubleModel min, mid, max; + + // Linear multiplier and constant + //private final double linear1; + //private final double linear0; + + // Non-linear multiplier, exponent and constant + private final double quad2,quad1,quad0; + + + + public ValueSliderModel(DoubleModel min, DoubleModel max) { + linearPosition = 1.0; + + this.min = min; + this.mid = max; // Never use exponential scale + this.max = max; + + min.addChangeListener(this); + max.addChangeListener(this); + + quad2 = quad1 = quad0 = 0; // Not used + } + + + + /** + * Generate a linear model from min to max. + */ + public ValueSliderModel(double min, double max) { + linearPosition = 1.0; + + this.min = new DoubleModel(min); + this.mid = new DoubleModel(max); // Never use exponential scale + this.max = new DoubleModel(max); + + quad2 = quad1 = quad0 = 0; // Not used + } + + public ValueSliderModel(double min, double mid, double max) { + this(min,0.5,mid,max); + } + + /* + * v(x) = mul * x^exp + add + * + * v(pos) = mul * pos^exp + add = mid + * v(1) = mul + add = max + * v'(pos) = mul*exp * pos^(exp-1) = linearMul + */ + public ValueSliderModel(double min, double pos, double mid, double max) { + this.min = new DoubleModel(min); + this.mid = new DoubleModel(mid); + this.max = new DoubleModel(max); + + + linearPosition = pos; + //linear0 = min; + //linear1 = (mid-min)/pos; + + if (!(min < mid && mid <= max && 0 < pos && pos < 1)) { + throw new IllegalArgumentException("Bad arguments for ValueSliderModel "+ + "min="+min+" mid="+mid+" max="+max+" pos="+pos); + } + + /* + * quad2..0 are calculated such that + * f(pos) = mid - continuity + * f(1) = max - end point + * f'(pos) = linear1 - continuity of derivative + */ + + double delta = (mid-min)/pos; + quad2 = (max - mid - delta + delta*pos) / pow2(pos-1); + quad1 = (delta + 2*(mid-max)*pos - delta*pos*pos) / pow2(pos-1); + quad0 = (mid - (2*mid+delta)*pos + (max+delta)*pos*pos) / pow2(pos-1); + + } + + private double pow2(double x) { + return x*x; + } + + public int getValue() { + double value = DoubleModel.this.getValue(); + if (value <= min.getValue()) + return 0; + if (value >= max.getValue()) + return MAX; + + double x; + if (value <= mid.getValue()) { + // Use linear scale + //linear0 = min; + //linear1 = (mid-min)/pos; + + x = (value - min.getValue())*linearPosition/(mid.getValue()-min.getValue()); + } else { + // Use quadratic scale + // Further solution of the quadratic equation + // a*x^2 + b*x + c-value == 0 + x = (Math.sqrt(quad1*quad1 - 4*quad2*(quad0-value)) - quad1) / (2*quad2); + } + return (int)(x*MAX); + } + + + public void setValue(int newValue) { + if (firing > 0) // Ignore loops + return; + + double x = (double)newValue/MAX; + double value; + + if (x <= linearPosition) { + // Use linear scale + //linear0 = min; + //linear1 = (mid-min)/pos; + + value = (mid.getValue()-min.getValue())/linearPosition*x + min.getValue(); + } else { + // Use quadratic scale + value = quad2*x*x + quad1*x + quad0; + } + + DoubleModel.this.setValue(currentUnit.fromUnit( + currentUnit.round(currentUnit.toUnit(value)))); + } + + + // Static get-methods + private boolean isAdjusting; + public int getExtent() { return 0; } + public int getMaximum() { return MAX; } + public int getMinimum() { return 0; } + public boolean getValueIsAdjusting() { return isAdjusting; } + + // Ignore set-values + public void setExtent(int newExtent) { } + public void setMaximum(int newMaximum) { } + public void setMinimum(int newMinimum) { } + public void setValueIsAdjusting(boolean b) { isAdjusting = b; } + + public void setRangeProperties(int value, int extent, int min, int max, boolean adjusting) { + setValueIsAdjusting(adjusting); + setValue(value); + } + + // Pass change listeners to the underlying model + public void addChangeListener(ChangeListener l) { + DoubleModel.this.addChangeListener(l); + } + + public void removeChangeListener(ChangeListener l) { + DoubleModel.this.removeChangeListener(l); + } + + + + public void stateChanged(ChangeEvent e) { + // Min or max range has changed. + // Fire if not already firing + if (firing == 0) + fireStateChanged(); + } + } + + + public BoundedRangeModel getSliderModel(DoubleModel min, DoubleModel max) { + return new ValueSliderModel(min,max); + } + + public BoundedRangeModel getSliderModel(double min, double max) { + return new ValueSliderModel(min,max); + } + + public BoundedRangeModel getSliderModel(double min, double mid, double max) { + return new ValueSliderModel(min,mid,max); + } + + public BoundedRangeModel getSliderModel(double min, double pos, double mid, double max) { + return new ValueSliderModel(min,pos,mid,max); + } + + + + + + //////////// Action model //////////// + + private class AutomaticActionModel extends AbstractAction implements ChangeListener { + private boolean oldValue = false; + + public AutomaticActionModel() { + oldValue = isAutomatic(); + addChangeListener(this); + } + + + @Override + public boolean isEnabled() { + // TODO: LOW: does not reflect if component is currently able to support automatic setting + return isAutomaticAvailable(); + } + + @Override + public Object getValue(String key) { + if (key.equals(Action.SELECTED_KEY)) { + oldValue = isAutomatic(); + return oldValue; + } + return super.getValue(key); + } + + @Override + public void putValue(String key, Object value) { + if (firing > 0) + return; + if (key.equals(Action.SELECTED_KEY) && (value instanceof Boolean)) { + oldValue = (Boolean)value; + setAutomatic((Boolean)value); + } else { + super.putValue(key, value); + } + } + + // Implement a wrapper to the ChangeListeners + ArrayList listeners = new ArrayList(); + @Override + public void addPropertyChangeListener(PropertyChangeListener listener) { + listeners.add(listener); + DoubleModel.this.addChangeListener(this); + } + @Override + public void removePropertyChangeListener(PropertyChangeListener listener) { + listeners.remove(listener); + if (listeners.isEmpty()) + DoubleModel.this.removeChangeListener(this); + } + // If the value has changed, generate an event to the listeners + public void stateChanged(ChangeEvent e) { + boolean newValue = isAutomatic(); + if (oldValue == newValue) + return; + PropertyChangeEvent event = new PropertyChangeEvent(this,Action.SELECTED_KEY, + oldValue,newValue); + oldValue = newValue; + Object[] l = listeners.toArray(); + for (int i=0; i listeners = new ArrayList(); + + private final UnitGroup units; + private Unit currentUnit; + + private final double minValue; + private final double maxValue; + + + private int firing = 0; // >0 when model itself is sending events + + + // Used to differentiate changes in valueName and other changes in the component: + private double lastValue = 0; + private boolean lastAutomatic = false; + + + public DoubleModel(double value) { + this(value, UnitGroup.UNITS_NONE,Double.NEGATIVE_INFINITY,Double.POSITIVE_INFINITY); + } + + public DoubleModel(double value, UnitGroup unit) { + this(value,unit,Double.NEGATIVE_INFINITY,Double.POSITIVE_INFINITY); + } + + public DoubleModel(double value, UnitGroup unit, double min) { + this(value,unit,min,Double.POSITIVE_INFINITY); + } + + public DoubleModel(double value, UnitGroup unit, double min, double max) { + this.lastValue = value; + this.minValue = min; + this.maxValue = max; + + source = null; + valueName = "Constant value"; + multiplier = 1; + + getMethod = setMethod = null; + getAutoMethod = setAutoMethod = null; + units = unit; + currentUnit = units.getDefaultUnit(); + } + + + /** + * Generates a new DoubleModel that changes the values of the specified component. + * The double value is read and written using the methods "get"/"set" + valueName. + * + * @param source Component whose parameter to use. + * @param valueName Name of metods used to get/set the parameter. + * @param multiplier Value shown by the model is the value from component.getXXX * multiplier + * @param min Minimum value allowed (in SI units) + * @param max Maximum value allowed (in SI units) + */ + public DoubleModel(ChangeSource source, String valueName, double multiplier, UnitGroup unit, + double min, double max) { + this.source = source; + this.valueName = valueName; + this.multiplier = multiplier; + + this.units = unit; + currentUnit = units.getDefaultUnit(); + + this.minValue = min; + this.maxValue = max; + + try { + getMethod = source.getClass().getMethod("get" + valueName); + } catch (NoSuchMethodException e) { + throw new IllegalArgumentException("get method for value '"+valueName+ + "' not present in class "+source.getClass().getCanonicalName()); + } + + Method s=null; + try { + s = source.getClass().getMethod("set" + valueName,double.class); + } catch (NoSuchMethodException e1) { } // Ignore + setMethod = s; + + // Automatic selection methods + + Method set=null,get=null; + + try { + get = source.getClass().getMethod("is" + valueName + "Automatic"); + set = source.getClass().getMethod("set" + valueName + "Automatic",boolean.class); + } catch (NoSuchMethodException e) { } // ignore + + if (set!=null && get!=null) { + getAutoMethod = get; + setAutoMethod = set; + } else { + getAutoMethod = null; + setAutoMethod = null; + } + + } + + public DoubleModel(ChangeSource source, String valueName, double multiplier, UnitGroup unit, + double min) { + this(source,valueName,multiplier,unit,min,Double.POSITIVE_INFINITY); + } + + public DoubleModel(ChangeSource source, String valueName, double multiplier, UnitGroup unit) { + this(source,valueName,multiplier,unit,Double.NEGATIVE_INFINITY,Double.POSITIVE_INFINITY); + } + + public DoubleModel(ChangeSource source, String valueName, UnitGroup unit, + double min, double max) { + this(source,valueName,1.0,unit,min,max); + } + + public DoubleModel(ChangeSource source, String valueName, UnitGroup unit, double min) { + this(source,valueName,1.0,unit,min,Double.POSITIVE_INFINITY); + } + + public DoubleModel(ChangeSource source, String valueName, UnitGroup unit) { + this(source,valueName,1.0,unit,Double.NEGATIVE_INFINITY,Double.POSITIVE_INFINITY); + } + + public DoubleModel(ChangeSource source, String valueName) { + this(source,valueName,1.0,UnitGroup.UNITS_NONE, + Double.NEGATIVE_INFINITY,Double.POSITIVE_INFINITY); + } + + public DoubleModel(ChangeSource source, String valueName, double min) { + this(source,valueName,1.0,UnitGroup.UNITS_NONE,min,Double.POSITIVE_INFINITY); + } + + public DoubleModel(ChangeSource source, String valueName, double min, double max) { + this(source,valueName,1.0,UnitGroup.UNITS_NONE,min,max); + } + + + + /** + * Returns the value of the variable (in SI units). + */ + public double getValue() { + if (getMethod==null) // Constant value + return lastValue; + + try { + return (Double)getMethod.invoke(source)*multiplier; + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + return lastValue; // Should not occur + } + + /** + * Sets the value of the variable. + * @param v New value for parameter in SI units. + */ + public void setValue(double v) { + if (setMethod==null) { + if (getMethod != null) { + throw new RuntimeException("setMethod not available for variable '"+valueName+ + "' in class "+source.getClass().getCanonicalName()); + } + lastValue = v; + fireStateChanged(); + return; + } + + try { + setMethod.invoke(source, v/multiplier); + return; + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + fireStateChanged(); // Should not occur + } + + + /** + * Returns whether setting the value automatically is available. + */ + public boolean isAutomaticAvailable() { + return (getAutoMethod != null) && (setAutoMethod != null); + } + + /** + * Returns whether the value is currently being set automatically. + * Returns false if automatic setting is not available at all. + */ + public boolean isAutomatic() { + if (getAutoMethod == null) + return false; + + try { + return (Boolean)getAutoMethod.invoke(source); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + return false; // Should not occur + } + + /** + * Sets whether the value should be set automatically. Simply fires a + * state change event if automatic setting is not available. + */ + public void setAutomatic(boolean auto) { + if (setAutoMethod == null) { + fireStateChanged(); // in case something is out-of-sync + return; + } + + try { + lastAutomatic = auto; + setAutoMethod.invoke(source, auto); + return; + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + fireStateChanged(); // Should not occur + } + + + /** + * Returns the current Unit. At the beginning it is the default unit of the UnitGroup. + * @return The most recently set unit. + */ + public Unit getCurrentUnit() { + return currentUnit; + } + + /** + * Sets the current Unit. The unit must be one of those included in the UnitGroup. + * @param u The unit to set active. + */ + public void setCurrentUnit(Unit u) { + if (currentUnit == u) + return; + currentUnit = u; + fireStateChanged(); + } + + + /** + * Returns the UnitGroup associated with the parameter value. + * + * @return The UnitGroup given to the constructor. + */ + public UnitGroup getUnitGroup() { + return units; + } + + + + /** + * Add a listener to the model. Adds the model as a listener to the Component if this + * is the first listener. + * @param l Listener to add. + */ + public void addChangeListener(ChangeListener l) { + if (listeners.isEmpty()) { + if (source != null) { + source.addChangeListener(this); + lastValue = getValue(); + lastAutomatic = isAutomatic(); + } + } + + listeners.add(l); + if (DEBUG_LISTENERS) + System.out.println(this+" adding listener (total "+listeners.size()+"): "+l); + } + + /** + * Remove a listener from the model. Removes the model from being a listener to the Component + * if this was the last listener of the model. + * @param l Listener to remove. + */ + public void removeChangeListener(ChangeListener l) { + listeners.remove(l); + if (listeners.isEmpty() && source != null) { + source.removeChangeListener(this); + } + if (DEBUG_LISTENERS) + System.out.println(this+" removing listener (total "+listeners.size()+"): "+l); + } + + /** + * Fire a ChangeEvent to all listeners. + */ + protected void fireStateChanged() { + Object[] l = listeners.toArray(); + ChangeEvent event = new ChangeEvent(this); + firing++; + for (int i=0; i> extends AbstractListModel + implements ComboBoxModel, ChangeListener { + + private final ChangeSource source; + private final String valueName; + private final String nullText; + + private final Enum[] values; + private Enum currentValue = null; + + private final Reflection.Method getMethod; + private final Reflection.Method setMethod; + + + + public EnumModel(ChangeSource source, String valueName) { + this(source,valueName,null,null); + } + + public EnumModel(ChangeSource source, String valueName, Enum[] values) { + this(source, valueName, values, null); + } + + @SuppressWarnings("unchecked") + public EnumModel(ChangeSource source, String valueName, Enum[] values, String nullText) { + Class> enumClass; + this.source = source; + this.valueName = valueName; + + try { + java.lang.reflect.Method getM = source.getClass().getMethod("get" + valueName); + enumClass = (Class>) getM.getReturnType(); + if (!enumClass.isEnum()) { + throw new IllegalArgumentException("Return type of get" + valueName + + " not an enum type"); + } + + getMethod = new Reflection.Method(getM); + setMethod = new Reflection.Method(source.getClass().getMethod("set" + valueName, + enumClass)); + } catch (NoSuchMethodException e) { + throw new IllegalArgumentException("get/is methods for enum '"+valueName+ + "' not present in class "+source.getClass().getCanonicalName()); + } + + if (values != null) + this.values = values; + else + this.values = enumClass.getEnumConstants(); + + this.nullText = nullText; + + stateChanged(null); // Update current value + source.addChangeListener(this); + } + + + + @Override + public Object getSelectedItem() { + if (currentValue==null) + return nullText; + return currentValue; + } + + @Override + public void setSelectedItem(Object item) { + if (item instanceof String) { + if (currentValue != null) + setMethod.invoke(source, (Object)null); + return; + } + + if (!(item instanceof Enum)) { + throw new IllegalArgumentException("Not String or Enum"); + } + + // Comparison with == ok, since both are enums + if (currentValue == item) + return; + setMethod.invoke(source, item); + } + + @Override + public Object getElementAt(int index) { + if (values[index] == null) + return nullText; + return values[index]; + } + + @Override + public int getSize() { + return values.length; + } + + + + + + @SuppressWarnings("unchecked") + @Override + public void stateChanged(ChangeEvent e) { + Enum value = (Enum) getMethod.invoke(source); + if (value != currentValue) { + currentValue = value; + this.fireContentsChanged(this, 0, values.length); + } + } + + + + @Override + public String toString() { + return "EnumModel["+source.getClass().getCanonicalName()+":"+valueName+"]"; + } + +} diff --git a/src/net/sf/openrocket/gui/adaptors/IntegerModel.java b/src/net/sf/openrocket/gui/adaptors/IntegerModel.java new file mode 100644 index 000000000..6800e0de6 --- /dev/null +++ b/src/net/sf/openrocket/gui/adaptors/IntegerModel.java @@ -0,0 +1,234 @@ +package net.sf.openrocket.gui.adaptors; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; + +import javax.swing.SpinnerModel; +import javax.swing.SpinnerNumberModel; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import net.sf.openrocket.util.ChangeSource; + + +public class IntegerModel implements ChangeListener { + + + //////////// JSpinner Model //////////// + + private class IntegerSpinnerModel extends SpinnerNumberModel { + @Override + public Object getValue() { + return IntegerModel.this.getValue(); + } + + @Override + public void setValue(Object value) { + if (firing > 0) // Ignore, if called when model is sending events + return; + Number num = (Number)value; + int newValue = num.intValue(); + IntegerModel.this.setValue(newValue); + +// try { +// int newValue = Integer.parseInt((String)value); +// IntegerModel.this.setValue(newValue); +// } catch (NumberFormatException e) { +// IntegerModel.this.fireStateChanged(); +// }; + } + + @Override + public Object getNextValue() { + int d = IntegerModel.this.getValue(); + if (d >= maxValue) + return null; + return (d+1); + } + + @Override + public Object getPreviousValue() { + int d = IntegerModel.this.getValue(); + if (d <= minValue) + return null; + return (d-1); + } + + @Override + public void addChangeListener(ChangeListener l) { + IntegerModel.this.addChangeListener(l); + } + + @Override + public void removeChangeListener(ChangeListener l) { + IntegerModel.this.removeChangeListener(l); + } + } + + /** + * Returns a new SpinnerModel with the same base as the DoubleModel. + * The values given to the JSpinner are in the currently selected units. + * + * @return A compatibility layer for a SpinnerModel. + */ + public SpinnerModel getSpinnerModel() { + return new IntegerSpinnerModel(); + } + + + + + //////////// Main model ///////////// + + /* + * The main model handles all values in SI units, i.e. no conversion is made within the model. + */ + + private final ChangeSource source; + private final String valueName; + + private final Method getMethod; + private final Method setMethod; + + private final ArrayList listeners = new ArrayList(); + + private final int minValue; + private final int maxValue; + + + private int firing = 0; // >0 when model itself is sending events + + + // Used to differentiate changes in valueName and other changes in the source: + private int lastValue = 0; + + + + /** + * Generates a new DoubleModel that changes the values of the specified source. + * The double value is read and written using the methods "get"/"set" + valueName. + * + * @param source Component whose parameter to use. + * @param valueName Name of metods used to get/set the parameter. + * @param multiplier Value shown by the model is the value from source.getXXX * multiplier + * @param min Minimum value allowed (in SI units) + * @param max Maximum value allowed (in SI units) + */ + public IntegerModel(ChangeSource source, String valueName, int min, int max) { + this.source = source; + this.valueName = valueName; + + this.minValue = min; + this.maxValue = max; + + try { + getMethod = source.getClass().getMethod("get" + valueName); + setMethod = source.getClass().getMethod("set" + valueName,int.class); + } catch (NoSuchMethodException e) { + throw new IllegalArgumentException("get/set methods for value '"+valueName+ + "' not present in class "+source.getClass().getCanonicalName()); + } + } + + public IntegerModel(ChangeSource source, String valueName, int min) { + this(source,valueName,min,Integer.MAX_VALUE); + } + + public IntegerModel(ChangeSource source, String valueName) { + this(source,valueName,Integer.MIN_VALUE,Integer.MAX_VALUE); + } + + + + + /** + * Returns the value of the variable. + */ + public int getValue() { + try { + return (Integer)getMethod.invoke(source); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + return lastValue; // Should not occur + } + + /** + * Sets the value of the variable. + */ + public void setValue(int v) { + try { + setMethod.invoke(source, v); + return; + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + fireStateChanged(); // Should not occur + } + + + /** + * Add a listener to the model. Adds the model as a listener to the Component if this + * is the first listener. + * @param l Listener to add. + */ + public void addChangeListener(ChangeListener l) { + if (listeners.isEmpty()) { + source.addChangeListener(this); + lastValue = getValue(); + } + + listeners.add(l); + } + + /** + * Remove a listener from the model. Removes the model from being a listener to the Component + * if this was the last listener of the model. + * @param l Listener to remove. + */ + public void removeChangeListener(ChangeListener l) { + listeners.remove(l); + if (listeners.isEmpty()) { + source.removeChangeListener(this); + } + } + + public void fireStateChanged() { + Object[] l = listeners.toArray(); + ChangeEvent event = new ChangeEvent(this); + firing++; + for (int i=0; i database; + + private final Reflection.Method getMethod; + private final Reflection.Method setMethod; + + + public MaterialModel(RocketComponent component, Material.Type type) { + this(component, type, "Material"); + } + + public MaterialModel(RocketComponent component, Material.Type type, String name) { + this.component = component; + this.type = type; + + switch (type) { + case LINE: + this.database = Databases.LINE_MATERIAL; + break; + + case BULK: + this.database = Databases.BULK_MATERIAL; + break; + + case SURFACE: + this.database = Databases.SURFACE_MATERIAL; + break; + + default: + throw new IllegalArgumentException("Unknown material type:"+type); + } + + try { + getMethod = new Reflection.Method(component.getClass().getMethod("get"+name)); + setMethod = new Reflection.Method(component.getClass().getMethod("set"+name, + Material.class)); + } catch (NoSuchMethodException e) { + throw new IllegalArgumentException("get/is methods for material " + + "not present in class "+component.getClass().getCanonicalName()); + } + + component.addChangeListener(this); + database.addChangeListener(this); + } + + @Override + public Object getSelectedItem() { + return getMethod.invoke(component); + } + + @Override + public void setSelectedItem(Object item) { + if (item == CUSTOM) { + + // Open custom material dialog in the future, after combo box has closed + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + AddMaterialDialog dialog = new AddMaterialDialog(); + dialog.setVisible(true); + + if (!dialog.okClicked) + return; + + Material material = Material.newMaterial(type, + dialog.nameField.getText().trim(), + dialog.density.getValue()); + setMethod.invoke(component, material); + + // TODO: HIGH: Allow saving added material to database +// if (dialog.addBox.isSelected()) { +// database.add(material); +// } + } + }); + + } else if (item instanceof Material) { + + setMethod.invoke(component, item); + + } else { + assert(false): "Should not occur"; + } + } + + @Override + public Object getElementAt(int index) { + if (index == database.size()) { + return CUSTOM; + } else if (index >= database.size()+1) { + return null; + } + return database.get(index); + } + + @Override + public int getSize() { + return database.size() + 1; + } + + + @Override + public void stateChanged(ChangeEvent e) { + if (e instanceof ComponentChangeEvent) { + if (((ComponentChangeEvent)e).isMassChange()) { + this.fireContentsChanged(this, 0, 0); + } + } else { + this.fireContentsChanged(this, 0, database.size()); + } + } + + + + + private class AddMaterialDialog extends JDialog { + + private boolean okClicked = false; + private JTextField nameField; + private DoubleModel density; +// private JCheckBox addBox; + + public AddMaterialDialog() { + super((JFrame)null, "Custom material", true); + + Material material = (Material) getSelectedItem(); + + JPanel panel = new JPanel(new MigLayout("gap rel unrel","[][65lp::][30lp::]")); + + panel.add(new JLabel("Material name:")); + nameField = new JTextField(15); + nameField.setText(material.getName()); + panel.add(nameField,"span 2, growx, wrap"); + + panel.add(new JLabel("Material density:")); + density = new DoubleModel(material.getDensity(),UnitGroup.UNITS_DENSITY_BULK,0); + JSpinner spinner = new JSpinner(density.getSpinnerModel()); + panel.add(spinner, "growx"); + panel.add(new UnitSelector(density),"wrap"); + +// addBox = new JCheckBox("Add material to database"); +// panel.add(addBox,"span, wrap"); + + JButton button = new JButton("OK"); + button.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + okClicked = true; + AddMaterialDialog.this.setVisible(false); + } + }); + panel.add(button,"span, split, tag ok"); + + button = new JButton("Cancel"); + button.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + AddMaterialDialog.this.setVisible(false); + } + }); + panel.add(button,"tag cancel"); + + this.setContentPane(panel); + this.pack(); + this.setAlwaysOnTop(true); + this.setLocationRelativeTo(null); + } + + } +} diff --git a/src/net/sf/openrocket/gui/adaptors/MotorConfigurationModel.java b/src/net/sf/openrocket/gui/adaptors/MotorConfigurationModel.java new file mode 100644 index 000000000..fbf367bf4 --- /dev/null +++ b/src/net/sf/openrocket/gui/adaptors/MotorConfigurationModel.java @@ -0,0 +1,382 @@ +package net.sf.openrocket.gui.adaptors; + + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import javax.swing.ComboBoxModel; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.JTextField; +import javax.swing.ListSelectionModel; +import javax.swing.SwingUtilities; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.EventListenerList; +import javax.swing.event.ListDataEvent; +import javax.swing.event.ListDataListener; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.gui.ResizeLabel; +import net.sf.openrocket.gui.TextFieldListener; +import net.sf.openrocket.rocketcomponent.ComponentChangeEvent; +import net.sf.openrocket.rocketcomponent.Configuration; +import net.sf.openrocket.rocketcomponent.Motor; +import net.sf.openrocket.rocketcomponent.MotorMount; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.util.GUIUtil; + +public class MotorConfigurationModel implements ComboBoxModel, ChangeListener { + + private static final String EDIT = "Edit configurations"; + + + private EventListenerList listenerList = new EventListenerList(); + + private final Configuration config; + private final Rocket rocket; + + private Map map = new HashMap(); + + + public MotorConfigurationModel(Configuration config) { + this.config = config; + this.rocket = config.getRocket(); + config.addChangeListener(this); + } + + + + @Override + public Object getElementAt(int index) { + String[] ids = rocket.getMotorConfigurationIDs(); + if (index < 0 || index > ids.length) + return null; + + if (index == ids.length) + return EDIT; + + return get(ids[index]); + } + + @Override + public int getSize() { + return rocket.getMotorConfigurationIDs().length + 1; + } + + @Override + public Object getSelectedItem() { + return get(config.getMotorConfigurationID()); + } + + @Override + public void setSelectedItem(Object item) { + if (item == EDIT) { + + // Open edit dialog in the future, after combo box has closed + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + EditConfigurationDialog dialog = new EditConfigurationDialog(); + dialog.setVisible(true); + + if (dialog.isRowSelected()) { + rocket.getDefaultConfiguration().setMotorConfigurationID( + dialog.getSelectedID()); + } + } + }); + + return; + } + if (!(item instanceof ID)) + return; + + ID idObject = (ID) item; + config.setMotorConfigurationID(idObject.getID()); + } + + + + //////////////// Event/listener handling //////////////// + + + @Override + public void addListDataListener(ListDataListener l) { + listenerList.add(ListDataListener.class, l); + } + + @Override + public void removeListDataListener(ListDataListener l) { + listenerList.remove(ListDataListener.class, l); + } + + protected void fireListDataEvent() { + Object[] listeners = listenerList.getListenerList(); + ListDataEvent e = null; + + for (int i = listeners.length-2; i>=0; i-=2) { + if (listeners[i] == ListDataListener.class) { + if (e == null) + e = new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, 0, getSize()); + ((ListDataListener) listeners[i+1]).contentsChanged(e); + } + } + } + + + @Override + public void stateChanged(ChangeEvent e) { + if (e instanceof ComponentChangeEvent) { + // Ignore unnecessary changes + if (!((ComponentChangeEvent)e).isMotorChange()) + return; + } + fireListDataEvent(); + } + + + + /* + * The ID class is an adapter, that contains the actual configuration ID, + * but gives the configuration description as its String representation. + * The get(id) method retrieves ID objects and caches them for reuse. + */ + + private ID get(String id) { + ID idObject = map.get(id); + if (idObject != null) + return idObject; + + idObject = new ID(id); + map.put(id, idObject); + return idObject; + } + + + private class ID { + private final String id; + + public ID(String id) { + this.id = id; + } + + public String getID() { + return id; + } + + @Override + public String toString() { + return rocket.getMotorConfigurationDescription(id); + } + } + + + private class EditConfigurationDialog extends JDialog { + private final ColumnTableModel tableModel; + private String[] ids; + int selection = -1; + + private final JButton addButton; + private final JButton removeButton; + private final JTextField nameField; + + private final JTable table; + + + public boolean isRowSelected() { + return selection >= 0; + } + + public String getSelectedID() { + if (selection >= 0) + return ids[selection]; + return null; + } + + + public EditConfigurationDialog() { + super((JFrame)null, "Edit configurations", true); + + ids = rocket.getMotorConfigurationIDs(); + + // Create columns + ArrayList columnList = new ArrayList(); + columnList.add(new Column("Name") { + @Override + public Object getValueAt(int row) { + return rocket.getMotorConfigurationDescription(ids[row]); + } + }); + + // Create columns from the motor mounts + Iterator iterator = rocket.deepIterator(); + while (iterator.hasNext()) { + RocketComponent c = iterator.next(); + if (!(c instanceof MotorMount)) + continue; + + final MotorMount mount = (MotorMount)c; + if (!mount.isMotorMount()) + continue; + + Column col = new Column(c.getName()) { + @Override + public Object getValueAt(int row) { + Motor motor = mount.getMotor(ids[row]); + if (motor == null) + return ""; + return motor.getDesignation(mount.getMotorDelay(ids[row])); + } + }; + columnList.add(col); + } + tableModel = new ColumnTableModel(columnList.toArray(new Column[0])) { + @Override + public int getRowCount() { + return ids.length; + } + }; + + + + // Create the panel + JPanel panel = new JPanel(new MigLayout("fill","[shrink][grow]")); + + + panel.add(new JLabel("Configuration name:"), "gapright para"); + nameField = new JTextField(); + new TextFieldListener() { + @Override + public void setText(String text) { + if (selection < 0 || ids[selection] == null) + return; + rocket.setMotorConfigurationName(ids[selection], text); + fireChange(); + } + }.listenTo(nameField); + panel.add(nameField, "growx, wrap"); + + panel.add(new ResizeLabel("Leave empty for default description", -2), + "skip, growx, wrap para"); + + + table = new JTable(tableModel); + table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + table.getSelectionModel().addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent e) { + updateSelection(); + } + }); + + // Mouse listener to act on double-clicks + table.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) { + EditConfigurationDialog.this.dispose(); + } + } + }); + + + + JScrollPane scrollpane = new JScrollPane(table); + panel.add(scrollpane, "spanx, height 150lp, width 400lp, grow, wrap"); + + + addButton = new JButton("New"); + addButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + String id = rocket.newMotorConfigurationID(); + ids = rocket.getMotorConfigurationIDs(); + tableModel.fireTableDataChanged(); + int sel; + for (sel=0; sel < ids.length; sel++) { + if (id.equals(ids[sel])) + break; + } + table.getSelectionModel().addSelectionInterval(sel, sel); + } + }); + panel.add(addButton, "growx, spanx, split 2"); + + removeButton = new JButton("Remove"); + removeButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + int sel = table.getSelectedRow(); + if (sel < 0 || sel >= ids.length || ids[sel] == null) + return; + rocket.removeMotorConfigurationID(ids[sel]); + ids = rocket.getMotorConfigurationIDs(); + tableModel.fireTableDataChanged(); + if (sel >= ids.length) + sel--; + table.getSelectionModel().addSelectionInterval(sel, sel); + } + }); + panel.add(removeButton, "growx, wrap para"); + + + JButton close = new JButton("Close"); + close.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + EditConfigurationDialog.this.dispose(); + } + }); + panel.add(close, "spanx, alignx 100%"); + + this.getRootPane().setDefaultButton(close); + + + this.setDefaultCloseOperation(DISPOSE_ON_CLOSE); + GUIUtil.installEscapeCloseOperation(this); + this.setLocationByPlatform(true); + + updateSelection(); + + this.add(panel); + this.validate(); + this.pack(); + } + + private void fireChange() { + int sel = table.getSelectedRow(); + tableModel.fireTableDataChanged(); + table.getSelectionModel().addSelectionInterval(sel, sel); + } + + private void updateSelection() { + selection = table.getSelectedRow(); + if (selection < 0 || ids[selection] == null) { + removeButton.setEnabled(false); + nameField.setEnabled(false); + nameField.setText(""); + } else { + removeButton.setEnabled(true); + nameField.setEnabled(true); + nameField.setText(rocket.getMotorConfigurationName(ids[selection])); + } + } + } + +} + diff --git a/src/net/sf/openrocket/gui/configdialog/BodyTubeConfig.java b/src/net/sf/openrocket/gui/configdialog/BodyTubeConfig.java new file mode 100644 index 000000000..7c5776ade --- /dev/null +++ b/src/net/sf/openrocket/gui/configdialog/BodyTubeConfig.java @@ -0,0 +1,111 @@ +package net.sf.openrocket.gui.configdialog; + + +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSpinner; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.gui.BasicSlider; +import net.sf.openrocket.gui.SpinnerEditor; +import net.sf.openrocket.gui.UnitSelector; +import net.sf.openrocket.gui.adaptors.BooleanModel; +import net.sf.openrocket.gui.adaptors.DoubleModel; +import net.sf.openrocket.material.Material; +import net.sf.openrocket.rocketcomponent.BodyTube; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.unit.UnitGroup; + +public class BodyTubeConfig extends RocketComponentConfig { + + private MotorConfig motorConfigPane = null; + + public BodyTubeConfig(RocketComponent c) { + super(c); + + JPanel panel = new JPanel(new MigLayout("gap rel unrel","[][65lp::][30lp::][]","")); + + //// Body tube length + panel.add(new JLabel("Body tube length:")); + + DoubleModel m = new DoubleModel(component,"Length",UnitGroup.UNITS_LENGTH,0); + + JSpinner spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(0, 0.5, 2.0)),"w 100lp, wrap"); + + + //// Body tube diameter + panel.add(new JLabel("Outer diameter:")); + + DoubleModel od = new DoubleModel(component,"Radius",2,UnitGroup.UNITS_LENGTH,0); + // Diameter = 2*Radius + + spin = new JSpinner(od.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(od),"growx"); + panel.add(new BasicSlider(od.getSliderModel(0, 0.04, 0.2)),"w 100lp, wrap 0px"); + + JCheckBox check = new JCheckBox(od.getAutomaticAction()); + check.setText("Automatic"); + panel.add(check,"skip, span 2, wrap"); + + + //// Inner diameter + panel.add(new JLabel("Inner diameter:")); + + // Diameter = 2*Radius + m = new DoubleModel(component,"InnerRadius",2,UnitGroup.UNITS_LENGTH,0); + + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(new DoubleModel(0), od)),"w 100lp, wrap"); + + + //// Wall thickness + panel.add(new JLabel("Wall thickness:")); + + m = new DoubleModel(component,"Thickness",UnitGroup.UNITS_LENGTH,0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(0,0.01)),"w 100lp, wrap 0px"); + + + check = new JCheckBox(new BooleanModel(component,"Filled")); + check.setText("Filled"); + panel.add(check,"skip, span 2, wrap"); + + + //// Material + panel.add(materialPanel(new JPanel(new MigLayout()), Material.Type.BULK), + "cell 4 0, gapleft paragraph, aligny 0%, spany"); + + + tabbedPane.insertTab("General", null, panel, "General properties", 0); + motorConfigPane = new MotorConfig((BodyTube)c); + tabbedPane.insertTab("Motor", null, motorConfigPane, "Motor mount configuration", 1); + tabbedPane.setSelectedIndex(0); + } + + @Override + public void updateFields() { + super.updateFields(); + if (motorConfigPane != null) + motorConfigPane.updateFields(); + } + +} diff --git a/src/net/sf/openrocket/gui/configdialog/BulkheadConfig.java b/src/net/sf/openrocket/gui/configdialog/BulkheadConfig.java new file mode 100644 index 000000000..c8130421e --- /dev/null +++ b/src/net/sf/openrocket/gui/configdialog/BulkheadConfig.java @@ -0,0 +1,22 @@ +package net.sf.openrocket.gui.configdialog; + + +import javax.swing.JPanel; + +import net.sf.openrocket.rocketcomponent.RocketComponent; + + + +public class BulkheadConfig extends RingComponentConfig { + + public BulkheadConfig(RocketComponent c) { + super(c); + + JPanel tab; + + tab = generalTab("Radius:", null, null, "Thickness:"); + tabbedPane.insertTab("General", null, tab, "General properties", 0); + tabbedPane.setSelectedIndex(0); + } + +} \ No newline at end of file diff --git a/src/net/sf/openrocket/gui/configdialog/CenteringRingConfig.java b/src/net/sf/openrocket/gui/configdialog/CenteringRingConfig.java new file mode 100644 index 000000000..7abedfae8 --- /dev/null +++ b/src/net/sf/openrocket/gui/configdialog/CenteringRingConfig.java @@ -0,0 +1,22 @@ +package net.sf.openrocket.gui.configdialog; + + +import javax.swing.JPanel; + +import net.sf.openrocket.rocketcomponent.RocketComponent; + + + +public class CenteringRingConfig extends RingComponentConfig { + + public CenteringRingConfig(RocketComponent c) { + super(c); + + JPanel tab; + + tab = generalTab("Outer diameter:", "Inner diameter:", null, "Thickness:"); + tabbedPane.insertTab("General", null, tab, "General properties", 0); + tabbedPane.setSelectedIndex(0); + } + +} \ No newline at end of file diff --git a/src/net/sf/openrocket/gui/configdialog/ComponentConfigDialog.java b/src/net/sf/openrocket/gui/configdialog/ComponentConfigDialog.java new file mode 100644 index 000000000..e785e45e7 --- /dev/null +++ b/src/net/sf/openrocket/gui/configdialog/ComponentConfigDialog.java @@ -0,0 +1,281 @@ +package net.sf.openrocket.gui.configdialog; + + +import java.awt.Component; +import java.awt.Container; +import java.awt.Point; +import java.awt.Window; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +import javax.swing.DefaultBoundedRangeModel; +import javax.swing.JDialog; +import javax.swing.JSlider; +import javax.swing.JSpinner; +import javax.swing.SpinnerNumberModel; + +import net.sf.openrocket.document.OpenRocketDocument; +import net.sf.openrocket.gui.Resettable; +import net.sf.openrocket.rocketcomponent.ComponentChangeEvent; +import net.sf.openrocket.rocketcomponent.ComponentChangeListener; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.util.GUIUtil; +import net.sf.openrocket.util.Prefs; + +/** + * A JFrame dialog that contains the configuration elements of one component. + * The contents of the dialog are instantiated from CONFIGDIALOGPACKAGE according + * to the current component. + * + * @author Sampo Niskanen + */ + +public class ComponentConfigDialog extends JDialog implements ComponentChangeListener { + private static final long serialVersionUID = 1L; + private static final String CONFIGDIALOGPACKAGE = "net.sf.openrocket.gui.configdialog"; + private static final String CONFIGDIALOGPOSTFIX = "Config"; + + + private static ComponentConfigDialog dialog = null; + + + private OpenRocketDocument document = null; + private RocketComponent component = null; + private RocketComponentConfig configurator = null; + + private final Window parent; + + private ComponentConfigDialog(Window parent, OpenRocketDocument document, + RocketComponent component) { + super(parent); + this.parent = parent; + + setComponent(document, component); + + // Set window position according to preferences, and set prefs when moving + Point position = Prefs.getWindowPosition(this.getClass()); + if (position == null) + this.setLocationByPlatform(true); + else + this.setLocation(position); + + this.addComponentListener(new ComponentAdapter() { + @Override + public void componentMoved(ComponentEvent e) { + Prefs.setWindowPosition(ComponentConfigDialog.this.getClass(), + ComponentConfigDialog.this.getLocation()); + } + }); + + + // Install ESC listener + GUIUtil.installEscapeCloseOperation(this); + } + + + /** + * Set the component being configured. The listening connections of the old configurator + * will be removed and the new ones created. + * + * @param component Component to configure. + */ + private void setComponent(OpenRocketDocument document, RocketComponent component) { + if (this.document != null) { + this.document.getRocket().removeComponentChangeListener(this); + } + + if (configurator != null) { + // Remove listeners by setting all applicable models to null + setNullModels(configurator); // null-safe + +// mainPanel.remove(configurator); + } + + this.document = document; + this.component = component; + this.document.getRocket().addComponentChangeListener(this); + + configurator = getDialogContents(); + this.setContentPane(configurator); + configurator.updateFields(); +// mainPanel.add(configurator,"cell 0 0, growx, growy"); + + setTitle(component.getComponentName()+" configuration"); + +// Dimension pref = getPreferredSize(); +// Dimension real = getSize(); +// if (pref.width > real.width || pref.height > real.height) + pack(); + } + + /** + * Traverses recursively the component tree, and sets all applicable component + * models to null, so as to remove the listener connections. + * + * NOTE: All components in the configuration dialogs that use custom models must be added + * to this method. + */ + private void setNullModels(Component c) { + if (c==null) + return; + + // Remove models for known components + // Why the FSCK must this be so hard?!?!? + + if (c instanceof JSpinner) { + ((JSpinner)c).setModel(new SpinnerNumberModel()); + } else if (c instanceof JSlider) { + ((JSlider)c).setModel(new DefaultBoundedRangeModel()); + } else if (c instanceof Resettable) { + ((Resettable)c).resetModel(); + } + + + if (c instanceof Container) { + Component[] cs = ((Container)c).getComponents(); + for (Component sub: cs) + setNullModels(sub); + } + + } + + + /** + * Return the configurator panel of the current component. + */ + private RocketComponentConfig getDialogContents() { + Constructor c = + findDialogContentsConstructor(component); + if (c != null) { + try { + return (RocketComponentConfig) c.newInstance(component); + } catch (InstantiationException e) { + throw new RuntimeException("BUG in constructor reflection",e); + } catch (IllegalAccessException e) { + throw new RuntimeException("BUG in constructor reflection",e); + } catch (InvocationTargetException e) { + throw new RuntimeException("BUG in constructor reflection",e); + } + } + + // Should never be reached, since RocketComponentConfig should catch all + // components without their own configurator. + throw new RuntimeException("Unable to find any configurator for "+component); + } + + /** + * Finds the Constructor of the given component's config dialog panel in + * CONFIGDIALOGPACKAGE. + */ + @SuppressWarnings("unchecked") + private static Constructor + findDialogContentsConstructor(RocketComponent component) { + Class currentclass; + String currentclassname; + String configclassname; + + Class configclass; + Constructor c; + + currentclass = component.getClass(); + while ((currentclass != null) && (currentclass != Object.class)) { + currentclassname = currentclass.getCanonicalName(); + int index = currentclassname.lastIndexOf('.'); + if (index >= 0) + currentclassname = currentclassname.substring(index + 1); + configclassname = CONFIGDIALOGPACKAGE + "." + currentclassname + + CONFIGDIALOGPOSTFIX; + + try { + configclass = Class.forName(configclassname); + c = (Constructor) + configclass.getConstructor(RocketComponent.class); + return c; + } catch (Exception ignore) { } + + currentclass = currentclass.getSuperclass(); + } + return null; + } + + + + + ////////// Static dialog ///////// + + /** + * A singleton configuration dialog. Will create and show a new dialog if one has not + * previously been used, or update the dialog and show it if a previous one exists. + * + * @param document the document to configure. + * @param component the component to configure. + */ + public static void showDialog(Window parent, OpenRocketDocument document, + RocketComponent component) { + if (dialog != null) + dialog.dispose(); + + dialog = new ComponentConfigDialog(parent, document, component); + dialog.setVisible(true); + + document.addUndoPosition("Modify "+component.getComponentName()); + } + + + /* package */ + static void showDialog(RocketComponent component) { + showDialog(dialog.parent, dialog.document, component); + } + + /** + * Hides the configuration dialog. May be used even if not currently visible. + */ + public static void hideDialog() { + if (dialog != null) + dialog.setVisible(false); + } + + + /** + * Add an undo position for the current document. This is intended for use only + * by the currently open dialog. + * + * @param description Description of the undoable action + */ + /*package*/ static void addUndoPosition(String description) { + if (dialog == null) { + throw new IllegalStateException("Dialog not open, report bug!"); + } + dialog.document.addUndoPosition(description); + } + + /*package*/ + static String getUndoDescription() { + if (dialog == null) { + throw new IllegalStateException("Dialog not open, report bug!"); + } + return dialog.document.getUndoDescription(); + } + + /** + * Returns whether the singleton configuration dialog is currently visible or not. + */ + public static boolean isDialogVisible() { + return (dialog!=null) && (dialog.isVisible()); + } + + + public void componentChanged(ComponentChangeEvent e) { + if (e.isTreeChange() || e.isUndoChange()) { + + // Hide dialog in case of tree or undo change + dialog.setVisible(false); + + } else { + configurator.updateFields(); + } + } + +} diff --git a/src/net/sf/openrocket/gui/configdialog/EllipticalFinSetConfig.java b/src/net/sf/openrocket/gui/configdialog/EllipticalFinSetConfig.java new file mode 100644 index 000000000..8092ae4b6 --- /dev/null +++ b/src/net/sf/openrocket/gui/configdialog/EllipticalFinSetConfig.java @@ -0,0 +1,195 @@ +package net.sf.openrocket.gui.configdialog; + + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSeparator; +import javax.swing.JSpinner; +import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.gui.BasicSlider; +import net.sf.openrocket.gui.SpinnerEditor; +import net.sf.openrocket.gui.UnitSelector; +import net.sf.openrocket.gui.adaptors.DoubleModel; +import net.sf.openrocket.gui.adaptors.EnumModel; +import net.sf.openrocket.gui.adaptors.IntegerModel; +import net.sf.openrocket.material.Material; +import net.sf.openrocket.rocketcomponent.FinSet; +import net.sf.openrocket.rocketcomponent.FreeformFinSet; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.unit.UnitGroup; + +public class EllipticalFinSetConfig extends FinSetConfig { + + public EllipticalFinSetConfig(final RocketComponent component) { + super(component); + + DoubleModel m; + JSpinner spin; + JComboBox combo; + + JPanel mainPanel = new JPanel(new MigLayout()); + + + + JPanel panel = new JPanel(new MigLayout("gap rel unrel","[][65lp::][30lp::]","")); + + //// Number of fins + panel.add(new JLabel("Number of fins:")); + + IntegerModel im = new IntegerModel(component,"FinCount",1,8); + + spin = new JSpinner(im.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx, wrap"); + + + //// Base rotation + panel.add(new JLabel("Rotation:")); + + m = new DoubleModel(component, "BaseRotation", UnitGroup.UNITS_ANGLE,-Math.PI,Math.PI); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(-Math.PI,Math.PI)),"w 100lp, wrap"); + + + //// Root chord + panel.add(new JLabel("Root chord:")); + + m = new DoubleModel(component,"Length",UnitGroup.UNITS_LENGTH,0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(0,0.05,0.2)),"w 100lp, wrap"); + + + //// Height + panel.add(new JLabel("Height:")); + + m = new DoubleModel(component,"Height",UnitGroup.UNITS_LENGTH,0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(0,0.05,0.2)),"w 100lp, wrap"); + + + //// Position + + panel.add(new JLabel("Position relative to:")); + + combo = new JComboBox( + new EnumModel(component, "RelativePosition", + new RocketComponent.Position[] { + RocketComponent.Position.TOP, + RocketComponent.Position.MIDDLE, + RocketComponent.Position.BOTTOM, + RocketComponent.Position.ABSOLUTE + })); + panel.add(combo,"spanx, growx, wrap"); + + panel.add(new JLabel("plus"),"right"); + + m = new DoubleModel(component,"PositionValue",UnitGroup.UNITS_LENGTH); + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel( + new DoubleModel(component.getParent(), "Length", -1.0, UnitGroup.UNITS_NONE), + new DoubleModel(component.getParent(), "Length"))), + "w 100lp, wrap"); + + + + //// Right portion + mainPanel.add(panel,"aligny 20%"); + + mainPanel.add(new JSeparator(SwingConstants.VERTICAL),"growy"); + + + + panel = new JPanel(new MigLayout("gap rel unrel","[][65lp::][30lp::]","")); + + + //// Cross section + panel.add(new JLabel("Fin cross section:"),"span, split"); + combo = new JComboBox( + new EnumModel(component,"CrossSection")); + panel.add(combo,"growx, wrap unrel"); + + + //// Thickness + panel.add(new JLabel("Thickness:")); + + m = new DoubleModel(component,"Thickness",UnitGroup.UNITS_LENGTH,0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(0,0.01)),"w 100lp, wrap 30lp"); + + + + //// Material + materialPanel(panel, Material.Type.BULK); + + + + //// Convert button + + JButton button = new JButton("Convert to freeform fin set"); + button.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + // Do change in future for overall safety + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + FreeformFinSet freeform = new FreeformFinSet((FinSet)component); + RocketComponent parent = component.getParent(); + int index = parent.getChildPosition(component); + + ComponentConfigDialog.addUndoPosition("Convert fin set"); + parent.removeChild(index); + parent.addChild(freeform, index); + ComponentConfigDialog.showDialog(freeform); + } + }); + + ComponentConfigDialog.hideDialog(); + } + }); + panel.add(button,"span, growx, gaptop paragraph"); + + + + mainPanel.add(panel,"aligny 20%"); + + addFinSetButtons(); + + + tabbedPane.insertTab("General", null, mainPanel, "General properties", 0); + tabbedPane.setSelectedIndex(0); + } + +} diff --git a/src/net/sf/openrocket/gui/configdialog/FinSetConfig.java b/src/net/sf/openrocket/gui/configdialog/FinSetConfig.java new file mode 100644 index 000000000..fb8e76cce --- /dev/null +++ b/src/net/sf/openrocket/gui/configdialog/FinSetConfig.java @@ -0,0 +1,108 @@ +package net.sf.openrocket.gui.configdialog; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.JButton; +import javax.swing.SwingUtilities; + +import net.sf.openrocket.rocketcomponent.FinSet; +import net.sf.openrocket.rocketcomponent.FreeformFinSet; +import net.sf.openrocket.rocketcomponent.RocketComponent; + + +public abstract class FinSetConfig extends RocketComponentConfig { + + private JButton split = null; + + public FinSetConfig(RocketComponent component) { + super(component); + } + + + + protected void addFinSetButtons() { + JButton convert=null; + + //// Convert buttons + if (!(component instanceof FreeformFinSet)) { + convert = new JButton("Convert to freeform"); + convert.setToolTipText("Convert this fin set into a freeform fin set"); + convert.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + // Do change in future for overall safety + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + FreeformFinSet freeform = new FreeformFinSet((FinSet)component); + String name = component.getComponentName(); + + if (freeform.getName().startsWith(name)) { + freeform.setName(freeform.getComponentName() + + freeform.getName().substring(name.length())); + } + + RocketComponent parent = component.getParent(); + int index = parent.getChildPosition(component); + + ComponentConfigDialog.addUndoPosition("Convert fin set"); + parent.removeChild(index); + parent.addChild(freeform, index); + ComponentConfigDialog.showDialog(freeform); + } + }); + + ComponentConfigDialog.hideDialog(); + } + }); + } + + split = new JButton("Split fins"); + split.setToolTipText("Split the fin set into separate fins"); + split.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + // Do change in future for overall safety + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + RocketComponent parent = component.getParent(); + int index = parent.getChildPosition(component); + int count = ((FinSet)component).getFinCount(); + double base = ((FinSet)component).getBaseRotation(); + if (count <= 1) + return; + + ComponentConfigDialog.addUndoPosition("Split fin set"); + parent.removeChild(index); + for (int i=0; i 1); + + if (convert==null) + addButtons(split); + else + addButtons(split,convert); + + } + + + @Override + public void updateFields() { + super.updateFields(); + if (split != null) + split.setEnabled(((FinSet)component).getFinCount() > 1); + } +} diff --git a/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java b/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java new file mode 100644 index 000000000..de59d7a10 --- /dev/null +++ b/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java @@ -0,0 +1,470 @@ +package net.sf.openrocket.gui.configdialog; + + +import java.awt.Point; +import java.awt.event.MouseEvent; +import java.awt.geom.Point2D; + +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSeparator; +import javax.swing.JSpinner; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.SwingConstants; +import javax.swing.table.AbstractTableModel; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.gui.BasicSlider; +import net.sf.openrocket.gui.ResizeLabel; +import net.sf.openrocket.gui.SpinnerEditor; +import net.sf.openrocket.gui.UnitSelector; +import net.sf.openrocket.gui.adaptors.DoubleModel; +import net.sf.openrocket.gui.adaptors.EnumModel; +import net.sf.openrocket.gui.adaptors.IntegerModel; +import net.sf.openrocket.gui.scalefigure.FinPointFigure; +import net.sf.openrocket.gui.scalefigure.ScaleScrollPane; +import net.sf.openrocket.gui.scalefigure.ScaleSelector; +import net.sf.openrocket.material.Material; +import net.sf.openrocket.rocketcomponent.FinSet; +import net.sf.openrocket.rocketcomponent.FreeformFinSet; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.unit.UnitGroup; +import net.sf.openrocket.util.Coordinate; + +public class FreeformFinSetConfig extends FinSetConfig { + + private final FreeformFinSet finset; + private JTable table = null; + private FinPointTableModel tableModel = null; + + private FinPointFigure figure = null; + + + public FreeformFinSetConfig(RocketComponent component) { + super(component); + this.finset = (FreeformFinSet)component; + + + tabbedPane.insertTab("General", null, generalPane(), "General properties", 0); + tabbedPane.insertTab("Shape", null, shapePane(), "Fin shape", 1); + tabbedPane.setSelectedIndex(0); + + addFinSetButtons(); + } + + + + private JPanel generalPane() { + + DoubleModel m; + JSpinner spin; + JComboBox combo; + + JPanel mainPanel = new JPanel(new MigLayout("fill")); + + JPanel panel = new JPanel(new MigLayout("fill, gap rel unrel","[][65lp::][30lp::]","")); + + + + //// Number of fins + panel.add(new JLabel("Number of fins:")); + + IntegerModel im = new IntegerModel(component,"FinCount",1,8); + + spin = new JSpinner(im.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx, wrap"); + + + //// Base rotation + panel.add(new JLabel("Fin rotation:")); + + m = new DoubleModel(component, "BaseRotation", UnitGroup.UNITS_ANGLE,-Math.PI,Math.PI); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(-Math.PI,Math.PI)),"w 100lp, wrap"); + + + + //// Fin cant + JLabel label = new JLabel("Fin cant:"); + label.setToolTipText("The angle that the fins are canted with respect to the rocket " + + "body."); + panel.add(label); + + m = new DoubleModel(component, "CantAngle", UnitGroup.UNITS_ANGLE, + -FinSet.MAX_CANT,FinSet.MAX_CANT); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(-FinSet.MAX_CANT,FinSet.MAX_CANT)), + "w 100lp, wrap 40lp"); + + + + //// Position + panel.add(new JLabel("Position relative to:")); + + combo = new JComboBox( + new EnumModel(component, "RelativePosition", + new RocketComponent.Position[] { + RocketComponent.Position.TOP, + RocketComponent.Position.MIDDLE, + RocketComponent.Position.BOTTOM, + RocketComponent.Position.ABSOLUTE + })); + panel.add(combo,"spanx 3, growx, wrap"); + + panel.add(new JLabel("plus"),"right"); + + m = new DoubleModel(component,"PositionValue",UnitGroup.UNITS_LENGTH); + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel( + new DoubleModel(component.getParent(), "Length", -1.0, UnitGroup.UNITS_NONE), + new DoubleModel(component.getParent(), "Length"))), + "w 100lp, wrap"); + + + + + + mainPanel.add(panel, "aligny 20%"); + mainPanel.add(new JSeparator(SwingConstants.VERTICAL), "growy, height 150lp"); + + + panel = new JPanel(new MigLayout("gap rel unrel","[][65lp::][30lp::]","")); + + + + + //// Cross section + panel.add(new JLabel("Fin cross section:"),"span, split"); + combo = new JComboBox( + new EnumModel(component,"CrossSection")); + panel.add(combo,"growx, wrap unrel"); + + + //// Thickness + panel.add(new JLabel("Thickness:")); + + m = new DoubleModel(component,"Thickness",UnitGroup.UNITS_LENGTH,0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(0,0.01)),"w 100lp, wrap 30lp"); + + + //// Material + materialPanel(panel, Material.Type.BULK); + + + + mainPanel.add(panel, "aligny 20%"); + + return mainPanel; + } + + + + private JPanel shapePane() { + JPanel panel = new JPanel(new MigLayout("fill")); + + + // Create the figure + figure = new FinPointFigure(finset); + ScaleScrollPane figurePane = new FinPointScrollPane(); + figurePane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); + figurePane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); + + // Create the table + tableModel = new FinPointTableModel(); + table = new JTable(tableModel); + table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + for (int i=0; i < Columns.values().length; i++) { + table.getColumnModel().getColumn(i). + setPreferredWidth(Columns.values()[i].getWidth()); + } + JScrollPane tablePane = new JScrollPane(table); + + +// panel.add(new JLabel("Coordinates:"), "aligny bottom, alignx 50%"); +// panel.add(new JLabel(" View:"), "wrap, aligny bottom"); + + + panel.add(tablePane,"growy, width :100lp:, height 100lp:250lp:"); + panel.add(figurePane,"gap unrel, spanx, growx, growy 1000, height 100lp:250lp:, wrap"); + + panel.add(new ResizeLabel("Double-click", -2), "alignx 50%"); + + panel.add(new ScaleSelector(figurePane),"spany 2"); + panel.add(new ResizeLabel("Click+drag: Add and move points " + + "Ctrl+click: Remove point", -2), "spany 2, right, wrap"); + + + panel.add(new ResizeLabel("to edit", -2), "alignx 50%"); + + return panel; + } + + + + + + @Override + public void updateFields() { + super.updateFields(); + + if (tableModel != null) { + tableModel.fireTableDataChanged(); + } + if (figure != null) { + figure.updateFigure(); + } + } + + + + + private class FinPointScrollPane extends ScaleScrollPane { + private static final int ANY_MASK = + (MouseEvent.ALT_DOWN_MASK | MouseEvent.ALT_GRAPH_DOWN_MASK | + MouseEvent.META_DOWN_MASK | MouseEvent.CTRL_DOWN_MASK | + MouseEvent.SHIFT_DOWN_MASK); + + private int dragIndex = -1; + + public FinPointScrollPane() { + super(figure, false); // Disallow fitting as it's buggy + } + + @Override + public void mousePressed(MouseEvent event) { + int mods = event.getModifiersEx(); + + if (event.getButton() != MouseEvent.BUTTON1 || + (mods & ANY_MASK) != 0) { + super.mousePressed(event); + return; + } + + int index = getPoint(event); + if (index >= 0) { + dragIndex = index; + return; + } + index = getSegment(event); + if (index >= 0) { + Point2D.Double point = getCoordinates(event); + finset.addPoint(index); + try { + finset.setPoint(index, point.x, point.y); + } catch (IllegalArgumentException ignore) { } + dragIndex = index; + + return; + } + + super.mousePressed(event); + return; + } + + + @Override + public void mouseDragged(MouseEvent event) { + int mods = event.getModifiersEx(); + if (dragIndex < 0 || + (mods & (ANY_MASK | MouseEvent.BUTTON1_DOWN_MASK)) != + MouseEvent.BUTTON1_DOWN_MASK) { + super.mouseDragged(event); + return; + } + Point2D.Double point = getCoordinates(event); + + try { + finset.setPoint(dragIndex, point.x, point.y); + } catch (IllegalArgumentException ignore) { + System.out.println("IAE:"+ignore); + } + } + + + @Override + public void mouseReleased(MouseEvent event) { + dragIndex = -1; + super.mouseReleased(event); + } + + @Override + public void mouseClicked(MouseEvent event) { + int mods = event.getModifiersEx(); + if (event.getButton() != MouseEvent.BUTTON1 || + (mods & ANY_MASK) != MouseEvent.CTRL_DOWN_MASK) { + super.mouseClicked(event); + return; + } + + int index = getPoint(event); + if (index < 0) { + super.mouseClicked(event); + return; + } + + try { + finset.removePoint(index); + } catch (IllegalArgumentException ignore) { + } + } + + + private int getPoint(MouseEvent event) { + Point p0 = event.getPoint(); + Point p1 = this.getViewport().getViewPosition(); + int x = p0.x + p1.x; + int y = p0.y + p1.y; + + return figure.getIndexByPoint(x, y); + } + + private int getSegment(MouseEvent event) { + Point p0 = event.getPoint(); + Point p1 = this.getViewport().getViewPosition(); + int x = p0.x + p1.x; + int y = p0.y + p1.y; + + return figure.getSegmentByPoint(x, y); + } + + private Point2D.Double getCoordinates(MouseEvent event) { + Point p0 = event.getPoint(); + Point p1 = this.getViewport().getViewPosition(); + int x = p0.x + p1.x; + int y = p0.y + p1.y; + + return figure.convertPoint(x, y); + } + + + } + + + + + + private enum Columns { +// NUMBER { +// @Override +// public String toString() { +// return "#"; +// } +// @Override +// public String getValue(FreeformFinSet finset, int row) { +// return "" + (row+1) + "."; +// } +// @Override +// public int getWidth() { +// return 10; +// } +// }, + X { + @Override + public String toString() { + return "X / " + UnitGroup.UNITS_LENGTH.getDefaultUnit().toString(); + } + @Override + public String getValue(FreeformFinSet finset, int row) { + return UnitGroup.UNITS_LENGTH.getDefaultUnit() + .toString(finset.getFinPoints()[row].x); + } + }, + Y { + @Override + public String toString() { + return "Y / " + UnitGroup.UNITS_LENGTH.getDefaultUnit().toString(); + } + @Override + public String getValue(FreeformFinSet finset, int row) { + return UnitGroup.UNITS_LENGTH.getDefaultUnit() + .toString(finset.getFinPoints()[row].y); + } + }; + + public abstract String getValue(FreeformFinSet finset, int row); + @Override + public abstract String toString(); + public int getWidth() { + return 20; + } + } + + private class FinPointTableModel extends AbstractTableModel { + + @Override + public int getColumnCount() { + return Columns.values().length; + } + + @Override + public int getRowCount() { + return finset.getPointCount(); + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + return Columns.values()[columnIndex].getValue(finset, rowIndex); + } + + @Override + public String getColumnName(int columnIndex) { + return Columns.values()[columnIndex].toString(); + } + + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) { + if (rowIndex == 0 || rowIndex == getRowCount()-1) { + return (columnIndex == Columns.X.ordinal()); + } + + return (columnIndex == Columns.X.ordinal() || columnIndex == Columns.Y.ordinal()); + } + + @Override + public void setValueAt(Object o, int rowIndex, int columnIndex) { + if (!(o instanceof String)) + return; + + String str = (String)o; + try { + + double value = UnitGroup.UNITS_LENGTH.fromString(str); + Coordinate c = finset.getFinPoints()[rowIndex]; + if (columnIndex == Columns.X.ordinal()) + c = c.setX(value); + else + c = c.setY(value); + + finset.setPoint(rowIndex, c.x, c.y); + + } catch (NumberFormatException ignore) { + } + } + + + } +} diff --git a/src/net/sf/openrocket/gui/configdialog/InnerTubeConfig.java b/src/net/sf/openrocket/gui/configdialog/InnerTubeConfig.java new file mode 100644 index 000000000..b8d9534b4 --- /dev/null +++ b/src/net/sf/openrocket/gui/configdialog/InnerTubeConfig.java @@ -0,0 +1,237 @@ +package net.sf.openrocket.gui.configdialog; + + + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.geom.Ellipse2D; +import java.util.List; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSpinner; +import javax.swing.border.BevelBorder; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.gui.BasicSlider; +import net.sf.openrocket.gui.Resettable; +import net.sf.openrocket.gui.SpinnerEditor; +import net.sf.openrocket.gui.UnitSelector; +import net.sf.openrocket.gui.adaptors.DoubleModel; +import net.sf.openrocket.rocketcomponent.ClusterConfiguration; +import net.sf.openrocket.rocketcomponent.Clusterable; +import net.sf.openrocket.rocketcomponent.InnerTube; +import net.sf.openrocket.rocketcomponent.MotorMount; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.unit.UnitGroup; + + +public class InnerTubeConfig extends ThicknessRingComponentConfig { + + + public InnerTubeConfig(RocketComponent c) { + super(c); + + JPanel tab; + + tab = positionTab(); + tabbedPane.insertTab("Radial position", null, tab, "Radial position", 1); + + tab = clusterTab(); + tabbedPane.insertTab("Cluster", null, tab, "Cluster configuration", 2); + + tab = new MotorConfig((MotorMount)c); + tabbedPane.insertTab("Motor", null, tab, "Motor mount configuration", 3); + + tabbedPane.setSelectedIndex(0); + } + + + private JPanel clusterTab() { + JPanel panel = new JPanel(new MigLayout()); + + JPanel subPanel = new JPanel(new MigLayout()); + + // Cluster type selection + subPanel.add(new JLabel("Select cluster configuration:"),"spanx, wrap"); + subPanel.add(new ClusterSelectionPanel((InnerTube)component),"spanx, wrap"); +// JPanel clusterSelection = new ClusterSelectionPanel((InnerTube)component); +// clusterSelection.setBackground(Color.blue); +// subPanel.add(clusterSelection); + + panel.add(subPanel); + + + subPanel = new JPanel(new MigLayout("gap rel unrel","[][65lp::][30lp::]")); + + // Tube separation scale + JLabel l = new JLabel("Tube separation:"); + l.setToolTipText("The separation of the tubes, 1.0 = touching each other"); + subPanel.add(l); + DoubleModel dm = new DoubleModel(component,"ClusterScale",1,UnitGroup.UNITS_NONE,0); + + JSpinner spin = new JSpinner(dm.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + spin.setToolTipText("The separation of the tubes, 1.0 = touching each other"); + subPanel.add(spin,"growx"); + + BasicSlider bs = new BasicSlider(dm.getSliderModel(0, 1, 4)); + bs.setToolTipText("The separation of the tubes, 1.0 = touching each other"); + subPanel.add(bs,"skip,w 100lp, wrap"); + + // Rotation + l = new JLabel("Rotation:"); + l.setToolTipText("Rotation angle of the cluster configuration"); + subPanel.add(l); + dm = new DoubleModel(component,"ClusterRotation",1,UnitGroup.UNITS_ANGLE,0); + + spin = new JSpinner(dm.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + spin.setToolTipText("Rotation angle of the cluster configuration"); + subPanel.add(spin,"growx"); + + subPanel.add(new UnitSelector(dm),"growx"); + bs = new BasicSlider(dm.getSliderModel(-Math.PI, 0, Math.PI)); + bs.setToolTipText("Rotation angle of the cluster configuration"); + subPanel.add(bs,"w 100lp, wrap"); + + // Reset button + JButton reset = new JButton("Reset"); + reset.setToolTipText("Reset the separation and rotation to the default values"); + reset.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) { + ((InnerTube)component).setClusterScale(1.0); + ((InnerTube)component).setClusterRotation(0.0); + } + }); + subPanel.add(reset,"spanx,right"); + + panel.add(subPanel,"grow"); + + + return panel; + } +} + + +class ClusterSelectionPanel extends JPanel { + private static final int BUTTON_SIZE = 50; + private static final int MOTOR_DIAMETER = 10; + + private static final Color SELECTED_COLOR = Color.RED; + private static final Color UNSELECTED_COLOR = Color.WHITE; + private static final Color MOTOR_FILL_COLOR = Color.GREEN; + private static final Color MOTOR_BORDER_COLOR = Color.BLACK; + + public ClusterSelectionPanel(Clusterable component) { + super(new MigLayout("gap 0 0", + "["+BUTTON_SIZE+"!]["+BUTTON_SIZE+"!]["+BUTTON_SIZE+"!]["+BUTTON_SIZE+"!]", + "["+BUTTON_SIZE+"!]["+BUTTON_SIZE+"!]["+BUTTON_SIZE+"!]")); + + for (int i=0; i points = config.getPoints(); + Ellipse2D.Float circle = new Ellipse2D.Float(); + for (int i=0; i < points.size()/2; i++) { + double x = points.get(i*2); + double y = points.get(i*2+1); + + double px = BUTTON_SIZE/2 + x*MOTOR_DIAMETER; + double py = BUTTON_SIZE/2 - y*MOTOR_DIAMETER; + circle.setFrameFromCenter(px,py,px+MOTOR_DIAMETER/2,py+MOTOR_DIAMETER/2); + + g2.setColor(MOTOR_FILL_COLOR); + g2.fill(circle); + g2.setColor(MOTOR_BORDER_COLOR); + g2.draw(circle); + } + } + + + public void stateChanged(ChangeEvent e) { + repaint(); + } + + + public void mouseClicked(MouseEvent e) { + if (e.getButton() == MouseEvent.BUTTON1) { + component.setClusterConfiguration(config); + } + } + + public void mouseEntered(MouseEvent e) { } + public void mouseExited(MouseEvent e) { } + public void mousePressed(MouseEvent e) { } + public void mouseReleased(MouseEvent e) { } + + + public void resetModel() { + component.removeChangeListener(this); + removeMouseListener(this); + } + } + +} diff --git a/src/net/sf/openrocket/gui/configdialog/LaunchLugConfig.java b/src/net/sf/openrocket/gui/configdialog/LaunchLugConfig.java new file mode 100644 index 000000000..9ef720345 --- /dev/null +++ b/src/net/sf/openrocket/gui/configdialog/LaunchLugConfig.java @@ -0,0 +1,153 @@ +package net.sf.openrocket.gui.configdialog; + + +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSpinner; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.gui.BasicSlider; +import net.sf.openrocket.gui.SpinnerEditor; +import net.sf.openrocket.gui.UnitSelector; +import net.sf.openrocket.gui.adaptors.DoubleModel; +import net.sf.openrocket.gui.adaptors.EnumModel; +import net.sf.openrocket.material.Material; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.unit.UnitGroup; + +public class LaunchLugConfig extends RocketComponentConfig { + + private MotorConfig motorConfigPane = null; + + public LaunchLugConfig(RocketComponent c) { + super(c); + + JPanel primary = new JPanel(new MigLayout("fill")); + + + JPanel panel = new JPanel(new MigLayout("gap rel unrel","[][65lp::][30lp::][]","")); + + //// Body tube length + panel.add(new JLabel("Length:")); + + DoubleModel m = new DoubleModel(component,"Length",UnitGroup.UNITS_LENGTH,0); + + JSpinner spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(0, 0.02, 0.1)),"w 100lp, wrap para"); + + + //// Body tube diameter + panel.add(new JLabel("Outer diameter:")); + + DoubleModel od = new DoubleModel(component,"Radius",2,UnitGroup.UNITS_LENGTH,0); + // Diameter = 2*Radius + + spin = new JSpinner(od.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(od),"growx"); + panel.add(new BasicSlider(od.getSliderModel(0, 0.04, 0.2)),"w 100lp, wrap rel"); + + + //// Inner diameter + panel.add(new JLabel("Inner diameter:")); + + // Diameter = 2*Radius + m = new DoubleModel(component,"InnerRadius",2,UnitGroup.UNITS_LENGTH,0); + + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(new DoubleModel(0), od)),"w 100lp, wrap rel"); + + + //// Wall thickness + panel.add(new JLabel("Thickness:")); + + m = new DoubleModel(component,"Thickness",UnitGroup.UNITS_LENGTH,0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(0,0.01)),"w 100lp, wrap 20lp"); + + + //// Radial direction + panel.add(new JLabel("Radial position:")); + + m = new DoubleModel(component,"RadialDirection",UnitGroup.UNITS_ANGLE, + -Math.PI, Math.PI); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(-Math.PI, Math.PI)),"w 100lp, wrap"); + + + + + primary.add(panel, "grow, gapright 20lp"); + panel = new JPanel(new MigLayout("gap rel unrel","[][65lp::][30lp::][]","")); + + + + + panel.add(new JLabel("Position relative to:")); + + JComboBox combo = new JComboBox( + new EnumModel(component, "RelativePosition", + new RocketComponent.Position[] { + RocketComponent.Position.TOP, + RocketComponent.Position.MIDDLE, + RocketComponent.Position.BOTTOM, + RocketComponent.Position.ABSOLUTE + })); + panel.add(combo,"spanx, growx, wrap"); + + panel.add(new JLabel("plus"),"right"); + + m = new DoubleModel(component,"PositionValue",UnitGroup.UNITS_LENGTH); + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel( + new DoubleModel(component.getParent(), "Length", -1.0, UnitGroup.UNITS_NONE), + new DoubleModel(component.getParent(), "Length"))), + "w 100lp, wrap para"); + + + + //// Material + materialPanel(panel, Material.Type.BULK); + + + primary.add(panel,"grow"); + + + tabbedPane.insertTab("General", null, primary, "General properties", 0); + tabbedPane.setSelectedIndex(0); + } + + @Override + public void updateFields() { + super.updateFields(); + if (motorConfigPane != null) + motorConfigPane.updateFields(); + } + +} diff --git a/src/net/sf/openrocket/gui/configdialog/MassComponentConfig.java b/src/net/sf/openrocket/gui/configdialog/MassComponentConfig.java new file mode 100644 index 000000000..93a3d472a --- /dev/null +++ b/src/net/sf/openrocket/gui/configdialog/MassComponentConfig.java @@ -0,0 +1,152 @@ +package net.sf.openrocket.gui.configdialog; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + + +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSpinner; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.gui.BasicSlider; +import net.sf.openrocket.gui.SpinnerEditor; +import net.sf.openrocket.gui.UnitSelector; +import net.sf.openrocket.gui.adaptors.DoubleModel; +import net.sf.openrocket.gui.adaptors.EnumModel; +import net.sf.openrocket.rocketcomponent.MassComponent; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.unit.UnitGroup; + + +public class MassComponentConfig extends RocketComponentConfig { + + public MassComponentConfig(RocketComponent component) { + super(component); + + + JPanel panel = new JPanel(new MigLayout("gap rel unrel","[][65lp::][30lp::]","")); + + + + //// Mass + panel.add(new JLabel("Mass")); + + DoubleModel m = new DoubleModel(component,"ComponentMass",UnitGroup.UNITS_MASS,0); + + JSpinner spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(0, 0.05, 0.5)),"w 100lp, wrap"); + + + + //// Mass length + panel.add(new JLabel("Length")); + + m = new DoubleModel(component,"Length",UnitGroup.UNITS_LENGTH,0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(0, 0.1, 0.5)),"w 100lp, wrap"); + + + //// Tube diameter + panel.add(new JLabel("Diameter:")); + + DoubleModel od = new DoubleModel(component,"Radius",2,UnitGroup.UNITS_LENGTH,0); + // Diameter = 2*Radius + + spin = new JSpinner(od.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(od),"growx"); + panel.add(new BasicSlider(od.getSliderModel(0, 0.04, 0.2)),"w 100lp, wrap"); + + + //// Position + + panel.add(new JLabel("Position relative to:")); + + JComboBox combo = new JComboBox( + new EnumModel(component, "RelativePosition", + new RocketComponent.Position[] { + RocketComponent.Position.TOP, + RocketComponent.Position.MIDDLE, + RocketComponent.Position.BOTTOM, + RocketComponent.Position.ABSOLUTE + })); + panel.add(combo,"spanx, growx, wrap"); + + panel.add(new JLabel("plus"),"right"); + + m = new DoubleModel(component,"PositionValue",UnitGroup.UNITS_LENGTH); + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel( + new DoubleModel(component.getParent(), "Length", -1.0, UnitGroup.UNITS_NONE), + new DoubleModel(component.getParent(), "Length"))), + "w 100lp, wrap"); + + + tabbedPane.insertTab("General", null, panel, "General properties", 0); + tabbedPane.insertTab("Radial position", null, positionTab(), + "Radial position configuration", 1); + tabbedPane.setSelectedIndex(0); + } + + + protected JPanel positionTab() { + JPanel panel = new JPanel(new MigLayout("gap rel unrel","[][65lp::][30lp::]","")); + + //// Radial position + panel.add(new JLabel("Radial distance:")); + + DoubleModel m = new DoubleModel(component,"RadialPosition",UnitGroup.UNITS_LENGTH,0); + + JSpinner spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(0, 0.1, 1.0)),"w 100lp, wrap"); + + + //// Radial direction + panel.add(new JLabel("Radial direction:")); + + m = new DoubleModel(component,"RadialDirection",UnitGroup.UNITS_ANGLE,0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(-Math.PI, Math.PI)),"w 100lp, wrap"); + + + //// Reset button + JButton button = new JButton("Reset"); + button.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + ((MassComponent) component).setRadialDirection(0.0); + ((MassComponent) component).setRadialPosition(0.0); + } + }); + panel.add(button,"spanx, right"); + + return panel; + } +} diff --git a/src/net/sf/openrocket/gui/configdialog/MotorConfig.java b/src/net/sf/openrocket/gui/configdialog/MotorConfig.java new file mode 100644 index 000000000..47b941921 --- /dev/null +++ b/src/net/sf/openrocket/gui/configdialog/MotorConfig.java @@ -0,0 +1,203 @@ +package net.sf.openrocket.gui.configdialog; + + +import java.awt.Component; +import java.awt.Container; +import java.awt.Font; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSpinner; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.gui.BasicSlider; +import net.sf.openrocket.gui.SpinnerEditor; +import net.sf.openrocket.gui.UnitSelector; +import net.sf.openrocket.gui.adaptors.BooleanModel; +import net.sf.openrocket.gui.adaptors.DoubleModel; +import net.sf.openrocket.gui.adaptors.EnumModel; +import net.sf.openrocket.gui.adaptors.MotorConfigurationModel; +import net.sf.openrocket.gui.main.MotorChooserDialog; +import net.sf.openrocket.rocketcomponent.Configuration; +import net.sf.openrocket.rocketcomponent.Motor; +import net.sf.openrocket.rocketcomponent.MotorMount; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.rocketcomponent.MotorMount.IgnitionEvent; +import net.sf.openrocket.unit.UnitGroup; + +public class MotorConfig extends JPanel { + + private final Rocket rocket; + private final MotorMount mount; + private final Configuration configuration; + private JPanel panel; + private JLabel motorLabel; + + public MotorConfig(MotorMount motorMount) { + super(new MigLayout("fill")); + + this.rocket = ((RocketComponent)motorMount).getRocket(); + this.mount = motorMount; + this.configuration = ((RocketComponent)motorMount).getRocket() + .getDefaultConfiguration(); + + BooleanModel model; + + model = new BooleanModel(motorMount, "MotorMount"); + JCheckBox check = new JCheckBox(model); + check.setText("This component is a motor mount"); + this.add(check,"wrap"); + + + panel = new JPanel(new MigLayout("fill")); + this.add(panel,"grow, wrap"); + + + // Motor configuration selector + panel.add(new JLabel("Motor configuration:"), "shrink"); + + JComboBox combo = new JComboBox(new MotorConfigurationModel(configuration)); + panel.add(combo,"growx"); + + configuration.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + updateFields(); + } + }); + + JButton button = new JButton("New"); + button.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + String id = rocket.newMotorConfigurationID(); + configuration.setMotorConfigurationID(id); + } + }); + panel.add(button, "wrap unrel"); + + + // Current motor + panel.add(new JLabel("Current motor:"), "shrink"); + + motorLabel = new JLabel(); + motorLabel.setFont(motorLabel.getFont().deriveFont(Font.BOLD)); + updateFields(); + panel.add(motorLabel,"wrap unrel"); + + + + // Overhang + panel.add(new JLabel("Motor overhang:")); + + DoubleModel m = new DoubleModel(motorMount, "MotorOverhang", UnitGroup.UNITS_LENGTH); + + JSpinner spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"span, split, width :65lp:"); + + panel.add(new UnitSelector(m),"width :30lp:"); + panel.add(new BasicSlider(m.getSliderModel(-0.02,0.06)),"w 100lp, wrap unrel"); + + + + // Select ignition event + panel.add(new JLabel("Ignition at:"),""); + + combo = new JComboBox(new EnumModel(mount, "IgnitionEvent")); + panel.add(combo,"growx, wrap"); + + // ... and delay + panel.add(new JLabel("plus"),"gap indent, skip 1, span, split"); + + m = new DoubleModel(mount,"IgnitionDelay",0); + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"gap rel rel"); + + panel.add(new JLabel("seconds"),"wrap paragraph"); + + + + + // Select etc. buttons + button = new JButton("Select motor"); + button.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + String id = configuration.getMotorConfigurationID(); + + MotorChooserDialog dialog = new MotorChooserDialog(mount.getMotor(id), + mount.getMotorDelay(id), mount.getMotorMountDiameter()); + dialog.setVisible(true); + Motor m = dialog.getSelectedMotor(); + double d = dialog.getSelectedDelay(); + + if (m != null) { + if (id == null) { + id = rocket.newMotorConfigurationID(); + configuration.setMotorConfigurationID(id); + } + mount.setMotor(id, m); + mount.setMotorDelay(id, d); + } + updateFields(); + } + }); + panel.add(button,"span, split, grow"); + + button = new JButton("Remove motor"); + button.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + mount.setMotor(configuration.getMotorConfigurationID(), null); + updateFields(); + } + }); + panel.add(button,"grow, wrap"); + + + + + + // Set enabled status + + setDeepEnabled(panel, motorMount.isMotorMount()); + check.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + setDeepEnabled(panel, mount.isMotorMount()); + } + }); + + } + + public void updateFields() { + String id = configuration.getMotorConfigurationID(); + Motor m = mount.getMotor(id); + if (m == null) + motorLabel.setText("None"); + else + motorLabel.setText(m.getManufacturer() + " " + + m.getDesignation(mount.getMotorDelay(id))); + } + + + private static void setDeepEnabled(Component component, boolean enabled) { + component.setEnabled(enabled); + if (component instanceof Container) { + for (Component c: ((Container) component).getComponents()) { + setDeepEnabled(c,enabled); + } + } + } + +} diff --git a/src/net/sf/openrocket/gui/configdialog/NoseConeConfig.java b/src/net/sf/openrocket/gui/configdialog/NoseConeConfig.java new file mode 100644 index 000000000..1c75f8c17 --- /dev/null +++ b/src/net/sf/openrocket/gui/configdialog/NoseConeConfig.java @@ -0,0 +1,172 @@ +package net.sf.openrocket.gui.configdialog; + + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSlider; +import javax.swing.JSpinner; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.gui.BasicSlider; +import net.sf.openrocket.gui.DescriptionArea; +import net.sf.openrocket.gui.SpinnerEditor; +import net.sf.openrocket.gui.UnitSelector; +import net.sf.openrocket.gui.adaptors.BooleanModel; +import net.sf.openrocket.gui.adaptors.DoubleModel; +import net.sf.openrocket.material.Material; +import net.sf.openrocket.rocketcomponent.NoseCone; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.rocketcomponent.Transition; +import net.sf.openrocket.unit.UnitGroup; + +public class NoseConeConfig extends RocketComponentConfig { + + private JComboBox typeBox; + + private DescriptionArea description; + + private JLabel shapeLabel; + private JSpinner shapeSpinner; + private JSlider shapeSlider; + + // Prepended to the description from NoseCone.DESCRIPTIONS + private static final String PREDESC = "

"; + + public NoseConeConfig(RocketComponent c) { + super(c); + + DoubleModel m; + JPanel panel = new JPanel(new MigLayout("","[][65lp::][30lp::]")); + + + + + //// Shape selection + + panel.add(new JLabel("Nose cone shape:")); + + Transition.Shape selected = ((NoseCone)component).getType(); + Transition.Shape[] typeList = Transition.Shape.values(); + + typeBox = new JComboBox(typeList); + typeBox.setEditable(false); + typeBox.setSelectedItem(selected); + typeBox.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + Transition.Shape s = (Transition.Shape)typeBox.getSelectedItem(); + ((NoseCone)component).setType(s); + description.setText(PREDESC + s.getNoseConeDescription()); + updateEnabled(); + } + }); + panel.add(typeBox,"span, wrap rel"); + + + + + //// Shape parameter + shapeLabel = new JLabel("Shape parameter:"); + panel.add(shapeLabel); + + m = new DoubleModel(component,"ShapeParameter"); + + shapeSpinner = new JSpinner(m.getSpinnerModel()); + shapeSpinner.setEditor(new SpinnerEditor(shapeSpinner)); + panel.add(shapeSpinner,"growx"); + + DoubleModel min = new DoubleModel(component,"ShapeParameterMin"); + DoubleModel max = new DoubleModel(component,"ShapeParameterMax"); + shapeSlider = new BasicSlider(m.getSliderModel(min,max)); + panel.add(shapeSlider,"skip, w 100lp, wrap para"); + + updateEnabled(); + + + //// Length + + panel.add(new JLabel("Nose cone length:")); + + m = new DoubleModel(component,"Length",UnitGroup.UNITS_LENGTH,0); + JSpinner spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(0, 0.1, 0.7)),"w 100lp, wrap"); + + //// Diameter + + panel.add(new JLabel("Base diameter:")); + + m = new DoubleModel(component,"AftRadius",2.0,UnitGroup.UNITS_LENGTH,0); // Diameter = 2*Radius + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(0, 0.04, 0.2)),"w 100lp, wrap 0px"); + + JCheckBox check = new JCheckBox(m.getAutomaticAction()); + check.setText("Automatic"); + panel.add(check,"skip, span 2, wrap"); + + + //// Wall thickness + panel.add(new JLabel("Wall thickness:")); + + m = new DoubleModel(component,"Thickness",UnitGroup.UNITS_LENGTH,0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(0,0.01)),"w 100lp, wrap 0px"); + + + check = new JCheckBox(new BooleanModel(component,"Filled")); + check.setText("Filled"); + panel.add(check,"skip, span 2, wrap"); + + + panel.add(new JLabel(""), "growy"); + + + + //// Description + + JPanel panel2 = new JPanel(new MigLayout("ins 0")); + + description = new DescriptionArea(5); + description.setText(PREDESC + ((NoseCone)component).getType().getNoseConeDescription()); + panel2.add(description, "wmin 250lp, spanx, growx, wrap para"); + + + //// Material + + + materialPanel(panel2, Material.Type.BULK); + panel.add(panel2, "cell 4 0, gapleft paragraph, aligny 0%, spany"); + + + + tabbedPane.insertTab("General", null, panel, "General properties", 0); + tabbedPane.insertTab("Shoulder", null, shoulderTab(), "Shoulder properties", 1); + tabbedPane.setSelectedIndex(0); + } + + + private void updateEnabled() { + boolean e = ((NoseCone)component).getType().usesParameter(); + shapeLabel.setEnabled(e); + shapeSpinner.setEnabled(e); + shapeSlider.setEnabled(e); + } + + +} diff --git a/src/net/sf/openrocket/gui/configdialog/ParachuteConfig.java b/src/net/sf/openrocket/gui/configdialog/ParachuteConfig.java new file mode 100644 index 000000000..340dfb8c5 --- /dev/null +++ b/src/net/sf/openrocket/gui/configdialog/ParachuteConfig.java @@ -0,0 +1,274 @@ +package net.sf.openrocket.gui.configdialog; + + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSpinner; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.gui.BasicSlider; +import net.sf.openrocket.gui.SpinnerEditor; +import net.sf.openrocket.gui.UnitSelector; +import net.sf.openrocket.gui.adaptors.DoubleModel; +import net.sf.openrocket.gui.adaptors.EnumModel; +import net.sf.openrocket.gui.adaptors.IntegerModel; +import net.sf.openrocket.gui.adaptors.MaterialModel; +import net.sf.openrocket.material.Material; +import net.sf.openrocket.rocketcomponent.MassComponent; +import net.sf.openrocket.rocketcomponent.Parachute; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.rocketcomponent.MotorMount.IgnitionEvent; +import net.sf.openrocket.unit.UnitGroup; + +public class ParachuteConfig extends RecoveryDeviceConfig { + + public ParachuteConfig(final RocketComponent component) { + super(component); + + JPanel primary = new JPanel(new MigLayout()); + + JPanel panel = new JPanel(new MigLayout("gap rel unrel","[][65lp::][30lp::][]","")); + + + //// Canopy + panel.add(new JLabel("Canopy:"), "wrap unrel"); + + + panel.add(new JLabel("Diameter:")); + + DoubleModel m = new DoubleModel(component,"Diameter",UnitGroup.UNITS_LENGTH,0); + + JSpinner spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(0, 0.4, 1.5)),"w 100lp, wrap"); + + + panel.add(new JLabel("Material:")); + + JComboBox combo = new JComboBox(new MaterialModel(component, Material.Type.SURFACE)); + combo.setToolTipText("The component material affects the weight of the component."); + panel.add(combo,"spanx 3, growx, wrap paragraph"); + +// materialPanel(panel, Material.Type.SURFACE, "Material:", null); + + + + // CD + JLabel label = new JLabel("Drag coefficient CD:"); + String tip = "The drag coefficient relative to the total area of the parachute.
" + + "A larger drag coefficient yields a slowed descent rate. " + + "A typical value for parachutes is 0.8."; + label.setToolTipText(tip); + panel.add(label); + + m = new DoubleModel(component,"CD",UnitGroup.UNITS_COEFFICIENT,0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setToolTipText(tip); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + JButton button = new JButton("Reset"); + button.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + Parachute p = (Parachute)component; + p.setCD(Parachute.DEFAULT_CD); + } + }); + panel.add(button,"spanx, wrap 30lp"); + + + + //// Shroud lines + panel.add(new JLabel("Shroud lines:"), "wrap unrel"); + + + panel.add(new JLabel("Number of lines:")); + IntegerModel im = new IntegerModel(component,"LineCount",0); + + spin = new JSpinner(im.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx, wrap"); + + + panel.add(new JLabel("Line length:")); + + m = new DoubleModel(component,"LineLength",UnitGroup.UNITS_LENGTH,0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(0, 0.4, 1.5)),"w 100lp, wrap"); + + + panel.add(new JLabel("Material:")); + + combo = new JComboBox(new MaterialModel(component, Material.Type.LINE, + "LineMaterial")); + panel.add(combo,"spanx 3, growx, wrap"); + + + + primary.add(panel, "grow, gapright 20lp"); + panel = new JPanel(new MigLayout("gap rel unrel","[][65lp::][30lp::][]","")); + + + + + //// Position + + panel.add(new JLabel("Position relative to:")); + + combo = new JComboBox( + new EnumModel(component, "RelativePosition", + new RocketComponent.Position[] { + RocketComponent.Position.TOP, + RocketComponent.Position.MIDDLE, + RocketComponent.Position.BOTTOM, + RocketComponent.Position.ABSOLUTE + })); + panel.add(combo,"spanx, growx, wrap"); + + panel.add(new JLabel("plus"),"right"); + + m = new DoubleModel(component,"PositionValue",UnitGroup.UNITS_LENGTH); + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel( + new DoubleModel(component.getParent(), "Length", -1.0, UnitGroup.UNITS_NONE), + new DoubleModel(component.getParent(), "Length"))), + "w 100lp, wrap"); + + + //// Spatial length + panel.add(new JLabel("Packed length:")); + + m = new DoubleModel(component,"Length",UnitGroup.UNITS_LENGTH,0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(0, 0.1, 0.5)),"w 100lp, wrap"); + + + //// Tube diameter + panel.add(new JLabel("Packed diameter:")); + + DoubleModel od = new DoubleModel(component,"Radius",2,UnitGroup.UNITS_LENGTH,0); + // Diameter = 2*Radius + + spin = new JSpinner(od.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(od),"growx"); + panel.add(new BasicSlider(od.getSliderModel(0, 0.04, 0.2)),"w 100lp, wrap 30lp"); + + + //// Deployment + + panel.add(new JLabel("Deploys at:"),""); + + combo = new JComboBox(new EnumModel(component, "DeployEvent")); + panel.add(combo,"spanx 3, growx, wrap"); + + // ... and delay + panel.add(new JLabel("plus"),"right"); + + m = new DoubleModel(component,"DeployDelay",0); + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"spanx, split"); + + panel.add(new JLabel("seconds"),"wrap paragraph"); + + // Altitude + label = new JLabel("Altitude:"); + altitudeComponents.add(label); + panel.add(label); + + m = new DoubleModel(component,"DeployAltitude",UnitGroup.UNITS_DISTANCE,0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + altitudeComponents.add(spin); + panel.add(spin,"growx"); + UnitSelector unit = new UnitSelector(m); + altitudeComponents.add(unit); + panel.add(unit,"growx"); + BasicSlider slider = new BasicSlider(m.getSliderModel(100, 1000)); + altitudeComponents.add(slider); + panel.add(slider,"w 100lp, wrap"); + + + primary.add(panel, "grow"); + + updateFields(); + + tabbedPane.insertTab("General", null, primary, "General properties", 0); + tabbedPane.insertTab("Radial position", null, positionTab(), + "Radial position configuration", 1); + tabbedPane.setSelectedIndex(0); + } + + + + + + protected JPanel positionTab() { + JPanel panel = new JPanel(new MigLayout("gap rel unrel","[][65lp::][30lp::]","")); + + //// Radial position + panel.add(new JLabel("Radial distance:")); + + DoubleModel m = new DoubleModel(component,"RadialPosition",UnitGroup.UNITS_LENGTH,0); + + JSpinner spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(0, 0.1, 1.0)),"w 100lp, wrap"); + + + //// Radial direction + panel.add(new JLabel("Radial direction:")); + + m = new DoubleModel(component,"RadialDirection",UnitGroup.UNITS_ANGLE,0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(-Math.PI, Math.PI)),"w 100lp, wrap"); + + + //// Reset button + JButton button = new JButton("Reset"); + button.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + ((MassComponent) component).setRadialDirection(0.0); + ((MassComponent) component).setRadialPosition(0.0); + } + }); + panel.add(button,"spanx, right"); + + return panel; + } +} diff --git a/src/net/sf/openrocket/gui/configdialog/RecoveryDeviceConfig.java b/src/net/sf/openrocket/gui/configdialog/RecoveryDeviceConfig.java new file mode 100644 index 000000000..a6b2ef8ac --- /dev/null +++ b/src/net/sf/openrocket/gui/configdialog/RecoveryDeviceConfig.java @@ -0,0 +1,36 @@ +package net.sf.openrocket.gui.configdialog; + +import java.util.ArrayList; +import java.util.List; + +import javax.swing.JComponent; + +import net.sf.openrocket.rocketcomponent.RecoveryDevice; +import net.sf.openrocket.rocketcomponent.RocketComponent; + + +public abstract class RecoveryDeviceConfig extends RocketComponentConfig { + + protected final List altitudeComponents = new ArrayList(); + + public RecoveryDeviceConfig(RocketComponent component) { + super(component); + } + + + + @Override + public void updateFields() { + super.updateFields(); + + if (altitudeComponents == null) + return; + + boolean enabled = (((RecoveryDevice)component).getDeployEvent() + == RecoveryDevice.DeployEvent.ALTITUDE); + + for (JComponent c: altitudeComponents) { + c.setEnabled(enabled); + } + } +} diff --git a/src/net/sf/openrocket/gui/configdialog/RingComponentConfig.java b/src/net/sf/openrocket/gui/configdialog/RingComponentConfig.java new file mode 100644 index 000000000..9acbbda08 --- /dev/null +++ b/src/net/sf/openrocket/gui/configdialog/RingComponentConfig.java @@ -0,0 +1,208 @@ +package net.sf.openrocket.gui.configdialog; + + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSpinner; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.gui.BasicSlider; +import net.sf.openrocket.gui.SpinnerEditor; +import net.sf.openrocket.gui.UnitSelector; +import net.sf.openrocket.gui.adaptors.DoubleModel; +import net.sf.openrocket.gui.adaptors.EnumModel; +import net.sf.openrocket.material.Material; +import net.sf.openrocket.rocketcomponent.RingComponent; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.unit.UnitGroup; + +public class RingComponentConfig extends RocketComponentConfig { + + public RingComponentConfig(RocketComponent component) { + super(component); + } + + + protected JPanel generalTab(String outer, String inner, String thickness, String length) { + JPanel panel = new JPanel(new MigLayout("gap rel unrel","[][65lp::][30lp::]","")); + DoubleModel m; + JSpinner spin; + DoubleModel od=null; + + + //// Outer diameter + if (outer != null) { + panel.add(new JLabel(outer)); + + od = new DoubleModel(component,"OuterRadius",2,UnitGroup.UNITS_LENGTH,0); + // Diameter = 2*Radius + + spin = new JSpinner(od.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(od),"growx"); + panel.add(new BasicSlider(od.getSliderModel(0, 0.04, 0.2)),"w 100lp, wrap"); + + if (od.isAutomaticAvailable()) { + JCheckBox check = new JCheckBox(od.getAutomaticAction()); + check.setText("Automatic"); + panel.add(check,"skip, span 2, wrap"); + } + } + + + //// Inner diameter + if (inner != null) { + panel.add(new JLabel(inner)); + + m = new DoubleModel(component,"InnerRadius",2,UnitGroup.UNITS_LENGTH,0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + if (od == null) + panel.add(new BasicSlider(m.getSliderModel(0, 0.04, 0.2)), "w 100lp, wrap"); + else + panel.add(new BasicSlider(m.getSliderModel(new DoubleModel(0), od)), + "w 100lp, wrap"); + + if (m.isAutomaticAvailable()) { + JCheckBox check = new JCheckBox(m.getAutomaticAction()); + check.setText("Automatic"); + panel.add(check,"skip, span 2, wrap"); + } + } + + + //// Wall thickness + if (thickness != null) { + panel.add(new JLabel(thickness)); + + m = new DoubleModel(component,"Thickness",UnitGroup.UNITS_LENGTH,0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(0,0.01)),"w 100lp, wrap"); + } + + + //// Inner tube length + if (length != null) { + panel.add(new JLabel(length)); + + m = new DoubleModel(component,"Length",UnitGroup.UNITS_LENGTH,0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(0, 0.1, 1.0)),"w 100lp, wrap"); + } + + + //// Position + + panel.add(new JLabel("Position relative to:")); + + JComboBox combo = new JComboBox( + new EnumModel(component, "RelativePosition", + new RocketComponent.Position[] { + RocketComponent.Position.TOP, + RocketComponent.Position.MIDDLE, + RocketComponent.Position.BOTTOM, + RocketComponent.Position.ABSOLUTE + })); + panel.add(combo,"spanx 3, growx, wrap"); + + panel.add(new JLabel("plus"),"right"); + + m = new DoubleModel(component,"PositionValue",UnitGroup.UNITS_LENGTH); + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel( + new DoubleModel(component.getParent(), "Length", -1.0, UnitGroup.UNITS_NONE), + new DoubleModel(component.getParent(), "Length"))), + "w 100lp, wrap"); + + + //// Material + panel.add(materialPanel(new JPanel(new MigLayout()), Material.Type.BULK), + "cell 4 0, gapleft paragraph, aligny 0%, spany"); + + return panel; + } + + + protected JPanel positionTab() { + JPanel panel = new JPanel(new MigLayout("align 20% 20%, gap rel unrel", + "[][65lp::][30lp::]","")); + + //// Radial position + JLabel l = new JLabel("Radial distance:"); + l.setToolTipText("Distance from the rocket centerline"); + panel.add(l); + + DoubleModel m = new DoubleModel(component,"RadialPosition",UnitGroup.UNITS_LENGTH,0); + + JSpinner spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + spin.setToolTipText("Distance from the rocket centerline"); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + BasicSlider bs = new BasicSlider(m.getSliderModel(0, 0.1, 1.0)); + bs.setToolTipText("Distance from the rocket centerline"); + panel.add(bs,"w 100lp, wrap"); + + + //// Radial direction + l = new JLabel("Radial direction:"); + l.setToolTipText("The radial direction from the rocket centerline"); + panel.add(l); + + m = new DoubleModel(component,"RadialDirection",UnitGroup.UNITS_ANGLE,0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + spin.setToolTipText("The radial direction from the rocket centerline"); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + bs = new BasicSlider(m.getSliderModel(-Math.PI, Math.PI)); + bs.setToolTipText("The radial direction from the rocket centerline"); + panel.add(bs,"w 100lp, wrap"); + + + //// Reset button + JButton button = new JButton("Reset"); + button.setToolTipText("Reset the component to the rocket centerline"); + button.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + ((RingComponent) component).setRadialDirection(0.0); + ((RingComponent) component).setRadialPosition(0.0); + } + }); + panel.add(button,"spanx, right"); + + + return panel; + } + +} diff --git a/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java b/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java new file mode 100644 index 000000000..9b19bcb82 --- /dev/null +++ b/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java @@ -0,0 +1,595 @@ +package net.sf.openrocket.gui.configdialog; + + +import java.awt.Color; +import java.awt.Component; +import java.awt.Graphics; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.util.Iterator; + +import javax.swing.BorderFactory; +import javax.swing.Icon; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JColorChooser; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSpinner; +import javax.swing.JTabbedPane; +import javax.swing.JTextArea; +import javax.swing.JTextField; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.gui.BasicSlider; +import net.sf.openrocket.gui.ResizeLabel; +import net.sf.openrocket.gui.SpinnerEditor; +import net.sf.openrocket.gui.UnitSelector; +import net.sf.openrocket.gui.adaptors.BooleanModel; +import net.sf.openrocket.gui.adaptors.DoubleModel; +import net.sf.openrocket.gui.adaptors.EnumModel; +import net.sf.openrocket.gui.adaptors.MaterialModel; +import net.sf.openrocket.material.Material; +import net.sf.openrocket.rocketcomponent.ComponentAssembly; +import net.sf.openrocket.rocketcomponent.ExternalComponent; +import net.sf.openrocket.rocketcomponent.NoseCone; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish; +import net.sf.openrocket.unit.UnitGroup; +import net.sf.openrocket.util.GUIUtil; +import net.sf.openrocket.util.LineStyle; +import net.sf.openrocket.util.Prefs; + +public class RocketComponentConfig extends JPanel { + + protected final RocketComponent component; + protected final JTabbedPane tabbedPane; + + + protected final JTextField componentNameField; + protected JTextArea commentTextArea; + private final TextFieldListener textFieldListener; + private JButton colorButton; + private JCheckBox colorDefault; + private JPanel buttonPanel; + + private JLabel massLabel; + + + public RocketComponentConfig(RocketComponent component) { + setLayout(new MigLayout("fill","[grow, fill]")); + this.component = component; + + JLabel label = new JLabel("Component name:"); + label.setToolTipText("The component name."); + this.add(label,"split, gapright 10"); + + componentNameField = new JTextField(15); + textFieldListener = new TextFieldListener(); + componentNameField.addActionListener(textFieldListener); + componentNameField.addFocusListener(textFieldListener); + componentNameField.setToolTipText("The component name."); + this.add(componentNameField,"growx, growy 0, wrap"); + + + tabbedPane = new JTabbedPane(); + this.add(tabbedPane,"growx, growy 1, wrap"); + + tabbedPane.addTab("Override", null, overrideTab(), "Mass and CG override options"); + if (component.isMassive()) + tabbedPane.addTab("Figure", null, figureTab(), "Figure style options"); + tabbedPane.addTab("Comment", null, commentTab(), "Specify a comment for the component"); + + addButtons(); + + updateFields(); + } + + + protected void addButtons(JButton... buttons) { + if (buttonPanel != null) { + this.remove(buttonPanel); + } + + buttonPanel = new JPanel(new MigLayout("fill, ins 0")); + + massLabel = new ResizeLabel("Mass: ", -1); + buttonPanel.add(massLabel, "growx"); + + for (JButton b: buttons) { + buttonPanel.add(b, "right, gap para"); + } + + JButton closeButton = new JButton("Close"); + closeButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) { + ComponentConfigDialog.hideDialog(); + } + }); + buttonPanel.add(closeButton, "right, gap 30lp"); + + updateFields(); + + this.add(buttonPanel, "spanx, growx"); + } + + + /** + * Called when a change occurs, so that the fields can be updated if necessary. + * When overriding this method, the supermethod must always be called. + */ + public void updateFields() { + // Component name + componentNameField.setText(component.getName()); + + // Component color and "Use default color" checkbox + if (colorButton != null && colorDefault != null) { + colorButton.setIcon(new ColorIcon(component.getColor())); + + if ((component.getColor()==null) != colorDefault.isSelected()) + colorDefault.setSelected(component.getColor()==null); + } + + // Mass label + if (component.isMassive()) { + String text = "Component mass: "; + text += UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit( + component.getComponentMass()); + + String overridetext = null; + if (component.isMassOverridden()) { + overridetext = "(overridden to " + UnitGroup.UNITS_MASS.getDefaultUnit(). + toStringUnit(component.getOverrideMass()) + ")"; + } + + for (RocketComponent c = component.getParent(); c != null; c = c.getParent()) { + if (c.isMassOverridden() && c.getOverrideSubcomponents()) { + overridetext = "(overridden by " + c.getName() + ")"; + } + } + + if (overridetext != null) + text = text + " " + overridetext; + + massLabel.setText(text); + } else { + massLabel.setText(""); + } + } + + + protected JPanel materialPanel(JPanel panel, Material.Type type) { + return materialPanel(panel, type, "Component material:", "Component finish:"); + } + + protected JPanel materialPanel(JPanel panel, Material.Type type, + String materialString, String finishString) { + JLabel label = new JLabel(materialString); + label.setToolTipText("The component material affects the weight of the component."); + panel.add(label,"spanx 4, wrap rel"); + + JComboBox combo = new JComboBox(new MaterialModel(component,type)); + combo.setToolTipText("The component material affects the weight of the component."); + panel.add(combo,"spanx 4, growx, wrap paragraph"); + + + if (component instanceof ExternalComponent) { + label = new JLabel(finishString); + String tip = "The component finish affects the aerodynamic drag of the " + +"component.
" + + "The value indicated is the average roughness height of the surface."; + label.setToolTipText(tip); + panel.add(label,"spanx 4, wmin 220lp, wrap rel"); + + combo = new JComboBox(new EnumModel(component,"Finish")); + combo.setToolTipText(tip); + panel.add(combo,"spanx 4, growx, split"); + + JButton button = new JButton("Set for all"); + button.setToolTipText("Set this finish for all components of the rocket."); + button.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + Finish f = ((ExternalComponent)component).getFinish(); + Rocket rocket = component.getRocket(); + try { + rocket.freeze(); + // Store previous undo description + String desc = ComponentConfigDialog.getUndoDescription(); + ComponentConfigDialog.addUndoPosition("Set rocket finish"); + // Do changes + Iterator iter = rocket.deepIterator(); + while (iter.hasNext()) { + RocketComponent c = iter.next(); + if (c instanceof ExternalComponent) { + ((ExternalComponent)c).setFinish(f); + } + } + // Restore undo description + ComponentConfigDialog.addUndoPosition(desc); + } finally { + rocket.thaw(); + } + } + }); + panel.add(button, "wrap paragraph"); + } + + return panel; + } + + + private JPanel overrideTab() { + JPanel panel = new JPanel(new MigLayout("align 50% 20%, fillx, gap rel unrel", + "[][65lp::][30lp::][]","")); + + panel.add(new JLabel("Override the mass or center of gravity of the " + + component.getComponentName() + ":"),"spanx, wrap 20lp"); + + JCheckBox check; + BooleanModel bm; + UnitSelector us; + BasicSlider bs; + + //// Mass + bm = new BooleanModel(component, "MassOverridden"); + check = new JCheckBox(bm); + check.setText("Override mass:"); + panel.add(check, "growx 1, gapright 20lp"); + + DoubleModel m = new DoubleModel(component,"OverrideMass",UnitGroup.UNITS_MASS,0); + + JSpinner spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + bm.addEnableComponent(spin, true); + panel.add(spin,"growx 1"); + + us = new UnitSelector(m); + bm.addEnableComponent(us, true); + panel.add(us,"growx 1"); + + bs = new BasicSlider(m.getSliderModel(0, 0.03, 1.0)); + bm.addEnableComponent(bs); + panel.add(bs,"growx 5, w 100lp, wrap"); + + + //// CG override + bm = new BooleanModel(component, "CGOverridden"); + check = new JCheckBox(bm); + check.setText("Override center of gravity:"); + panel.add(check, "growx 1, gapright 20lp"); + + m = new DoubleModel(component,"OverrideCGX",UnitGroup.UNITS_LENGTH,0); + // Calculate suitable length for slider + DoubleModel length; + if (component instanceof ComponentAssembly) { + double l=0; + + Iterator iterator = component.deepIterator(); + while (iterator.hasNext()) { + RocketComponent c = iterator.next(); + if (c.getRelativePosition() == RocketComponent.Position.AFTER) + l += c.getLength(); + } + length = new DoubleModel(l); + } else { + length = new DoubleModel(component, "Length", UnitGroup.UNITS_LENGTH,0); + } + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + bm.addEnableComponent(spin, true); + panel.add(spin,"growx 1"); + + us = new UnitSelector(m); + bm.addEnableComponent(us, true); + panel.add(us,"growx 1"); + + bs = new BasicSlider(m.getSliderModel(new DoubleModel(0), length)); + bm.addEnableComponent(bs); + panel.add(bs,"growx 5, w 100lp, wrap 35lp"); + + + // Override subcomponents checkbox + bm = new BooleanModel(component, "OverrideSubcomponents"); + check = new JCheckBox(bm); + check.setText("Override mass and CG of all subcomponents"); + panel.add(check, "gap para, spanx, wrap para"); + + + panel.add(new ResizeLabel("The overridden mass does not include motors.
" + + "The center of gravity is measured from the front end of the " + + component.getComponentName().toLowerCase()+".", -1), + "spanx, wrap, gap para, height 0::30lp"); + + return panel; + } + + + private JPanel commentTab() { + JPanel panel = new JPanel(new MigLayout("fill")); + + panel.add(new JLabel("Comments on the "+component.getComponentName()+":"), "wrap"); + + // TODO: LOW: Changes in comment from other sources not reflected in component + commentTextArea = new JTextArea(component.getComment()); + commentTextArea.setLineWrap(true); + commentTextArea.setWrapStyleWord(true); + commentTextArea.setEditable(true); + GUIUtil.setTabToFocusing(commentTextArea); + commentTextArea.addFocusListener(textFieldListener); + + panel.add(new JScrollPane(commentTextArea), "growx, growy"); + + return panel; + } + + + + private JPanel figureTab() { + JPanel panel = new JPanel(new MigLayout("align 20% 20%")); + + panel.add(new JLabel("Figure style:"), "wrap para"); + + + panel.add(new JLabel("Component color:"), "gapleft para, gapright 10lp"); + + colorButton = new JButton(new ColorIcon(component.getColor())); + colorButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + Color c = component.getColor(); + if (c == null) { + c = Prefs.getDefaultColor(component.getClass()); + } + + c = JColorChooser.showDialog(tabbedPane, "Choose color", c); + if (c!=null) { + component.setColor(c); + } + } + }); + panel.add(colorButton, "gapright 10lp"); + + colorDefault = new JCheckBox("Use default color"); + if (component.getColor()==null) + colorDefault.setSelected(true); + colorDefault.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (colorDefault.isSelected()) + component.setColor(null); + else + component.setColor(Prefs.getDefaultColor(component.getClass())); + } + }); + panel.add(colorDefault, "wrap para"); + + + panel.add(new JLabel("Component line style:"), "gapleft para, gapright 10lp"); + + LineStyle[] list = new LineStyle[LineStyle.values().length+1]; + System.arraycopy(LineStyle.values(), 0, list, 1, LineStyle.values().length); + + JComboBox combo = new JComboBox(new EnumModel(component, "LineStyle", + list, "Default style")); + panel.add(combo, "spanx 2, growx, wrap 50lp"); + + + JButton button = new JButton("Save as default style"); + button.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (component.getColor() != null) { + Prefs.setDefaultColor(component.getClass(), component.getColor()); + component.setColor(null); + } + if (component.getLineStyle() != null) { + Prefs.setDefaultLineStyle(component.getClass(), component.getLineStyle()); + component.setLineStyle(null); + } + } + }); + panel.add(button, "gapleft para, spanx 3, growx, wrap"); + + return panel; + } + + + + + protected JPanel shoulderTab() { + JPanel panel = new JPanel(new MigLayout("fill")); + JPanel sub; + DoubleModel m, m2; + DoubleModel m0 = new DoubleModel(0); + BooleanModel bm; + JCheckBox check; + JSpinner spin; + + + //// Fore shoulder, not for NoseCone + + if (!(component instanceof NoseCone)) { + sub = new JPanel(new MigLayout("gap rel unrel","[][65lp::][30lp::]","")); + + sub.setBorder(BorderFactory.createTitledBorder("Fore shoulder")); + + + //// Radius + sub.add(new JLabel("Diameter:")); + + m = new DoubleModel(component,"ForeShoulderRadius",2,UnitGroup.UNITS_LENGTH,0); + m2 = new DoubleModel(component,"ForeRadius",2,UnitGroup.UNITS_LENGTH); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + sub.add(spin,"growx"); + + sub.add(new UnitSelector(m),"growx"); + sub.add(new BasicSlider(m.getSliderModel(m0, m2)),"w 100lp, wrap"); + + + //// Length + sub.add(new JLabel("Length:")); + + m = new DoubleModel(component,"ForeShoulderLength",UnitGroup.UNITS_LENGTH,0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + sub.add(spin,"growx"); + + sub.add(new UnitSelector(m),"growx"); + sub.add(new BasicSlider(m.getSliderModel(0, 0.02, 0.2)),"w 100lp, wrap"); + + + //// Thickness + sub.add(new JLabel("Thickness:")); + + m = new DoubleModel(component,"ForeShoulderThickness",UnitGroup.UNITS_LENGTH,0); + m2 = new DoubleModel(component,"ForeShoulderRadius",UnitGroup.UNITS_LENGTH); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + sub.add(spin,"growx"); + + sub.add(new UnitSelector(m),"growx"); + sub.add(new BasicSlider(m.getSliderModel(m0, m2)),"w 100lp, wrap"); + + + //// Capped + bm = new BooleanModel(component, "ForeShoulderCapped"); + check = new JCheckBox(bm); + check.setText("End capped"); + check.setToolTipText("Whether the end of the shoulder is capped."); + sub.add(check, "spanx"); + + + panel.add(sub); + } + + + //// Aft shoulder + sub = new JPanel(new MigLayout("gap rel unrel","[][65lp::][30lp::]","")); + + if (component instanceof NoseCone) + sub.setBorder(BorderFactory.createTitledBorder("Nose cone shoulder")); + else + sub.setBorder(BorderFactory.createTitledBorder("Aft shoulder")); + + + //// Radius + sub.add(new JLabel("Diameter:")); + + m = new DoubleModel(component,"AftShoulderRadius",2,UnitGroup.UNITS_LENGTH,0); + m2 = new DoubleModel(component,"AftRadius",2,UnitGroup.UNITS_LENGTH); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + sub.add(spin,"growx"); + + sub.add(new UnitSelector(m),"growx"); + sub.add(new BasicSlider(m.getSliderModel(m0, m2)),"w 100lp, wrap"); + + + //// Length + sub.add(new JLabel("Length:")); + + m = new DoubleModel(component,"AftShoulderLength",UnitGroup.UNITS_LENGTH,0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + sub.add(spin,"growx"); + + sub.add(new UnitSelector(m),"growx"); + sub.add(new BasicSlider(m.getSliderModel(0, 0.02, 0.2)),"w 100lp, wrap"); + + + //// Thickness + sub.add(new JLabel("Thickness:")); + + m = new DoubleModel(component,"AftShoulderThickness",UnitGroup.UNITS_LENGTH,0); + m2 = new DoubleModel(component,"AftShoulderRadius",UnitGroup.UNITS_LENGTH); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + sub.add(spin,"growx"); + + sub.add(new UnitSelector(m),"growx"); + sub.add(new BasicSlider(m.getSliderModel(m0, m2)),"w 100lp, wrap"); + + + //// Capped + bm = new BooleanModel(component, "AftShoulderCapped"); + check = new JCheckBox(bm); + check.setText("End capped"); + check.setToolTipText("Whether the end of the shoulder is capped."); + sub.add(check, "spanx"); + + + panel.add(sub); + + + return panel; + } + + + + + /* + * Private inner class to handle events in componentNameField. + */ + private class TextFieldListener implements ActionListener, FocusListener { + public void actionPerformed(ActionEvent e) { + setName(); + } + public void focusGained(FocusEvent e) { } + public void focusLost(FocusEvent e) { + setName(); + } + private void setName() { + if (!component.getName().equals(componentNameField.getText())) { + component.setName(componentNameField.getText()); + } + if (!component.getComment().equals(commentTextArea.getText())) { + component.setComment(commentTextArea.getText()); + } + } + } + + + private class ColorIcon implements Icon { + private final Color color; + + public ColorIcon(Color c) { + this.color = c; + } + + @Override + public int getIconHeight() { + return 15; + } + + @Override + public int getIconWidth() { + return 25; + } + + @Override + public void paintIcon(Component c, Graphics g, int x, int y) { + if (color==null) { + g.setColor(Prefs.getDefaultColor(component.getClass())); + } else { + g.setColor(color); + } + g.fill3DRect(x, y, getIconWidth(), getIconHeight(), false); + } + + } + +} diff --git a/src/net/sf/openrocket/gui/configdialog/RocketConfig.java b/src/net/sf/openrocket/gui/configdialog/RocketConfig.java new file mode 100644 index 000000000..2e3131601 --- /dev/null +++ b/src/net/sf/openrocket/gui/configdialog/RocketConfig.java @@ -0,0 +1,92 @@ +package net.sf.openrocket.gui.configdialog; + + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; + +import javax.swing.JLabel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.util.GUIUtil; + +public class RocketConfig extends RocketComponentConfig { + + private TextFieldListener textFieldListener; + + private JTextArea designerTextArea; + private JTextArea revisionTextArea; + + private final Rocket rocket; + + public RocketConfig(RocketComponent c) { + super(c); + + rocket = (Rocket)c; + + this.removeAll(); + setLayout(new MigLayout("fill")); + + this.add(new JLabel("Design name:"), "top, pad 4lp, gapright 10lp"); + this.add(componentNameField, "growx, wrap para"); + + + + this.add(new JLabel("Designer:"), "top, pad 4lp, gapright 10lp"); + + textFieldListener = new TextFieldListener(); + designerTextArea = new JTextArea(rocket.getDesigner()); + designerTextArea.setLineWrap(true); + designerTextArea.setWrapStyleWord(true); + designerTextArea.setEditable(true); + GUIUtil.setTabToFocusing(designerTextArea); + designerTextArea.addFocusListener(textFieldListener); + this.add(new JScrollPane(designerTextArea), "wmin 300lp, hmin 45lp, grow 30, wrap para"); + + + this.add(new JLabel("Comments:"), "top, pad 4lp, gapright 10lp"); + this.add(new JScrollPane(commentTextArea), "wmin 300lp, hmin 105lp, grow 100, wrap para"); + + + this.add(new JLabel("Revision history:"), "top, pad 4lp, gapright 10lp"); + revisionTextArea = new JTextArea(rocket.getRevision()); + revisionTextArea.setLineWrap(true); + revisionTextArea.setWrapStyleWord(true); + revisionTextArea.setEditable(true); + GUIUtil.setTabToFocusing(revisionTextArea); + revisionTextArea.addFocusListener(textFieldListener); + + this.add(new JScrollPane(revisionTextArea), "wmin 300lp, hmin 45lp, grow 30, wrap para"); + + + addButtons(); + } + + + + private class TextFieldListener implements ActionListener, FocusListener { + public void actionPerformed(ActionEvent e) { + setName(); + } + public void focusGained(FocusEvent e) { } + public void focusLost(FocusEvent e) { + setName(); + } + private void setName() { + if (!rocket.getDesigner().equals(designerTextArea.getText())) { + rocket.setDesigner(designerTextArea.getText()); + } + if (!rocket.getRevision().equals(revisionTextArea.getText())) { + rocket.setRevision(revisionTextArea.getText()); + } + } + } + + + +} diff --git a/src/net/sf/openrocket/gui/configdialog/ShockCordConfig.java b/src/net/sf/openrocket/gui/configdialog/ShockCordConfig.java new file mode 100644 index 000000000..9b82ea9f3 --- /dev/null +++ b/src/net/sf/openrocket/gui/configdialog/ShockCordConfig.java @@ -0,0 +1,122 @@ +package net.sf.openrocket.gui.configdialog; + + +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSpinner; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.gui.BasicSlider; +import net.sf.openrocket.gui.SpinnerEditor; +import net.sf.openrocket.gui.UnitSelector; +import net.sf.openrocket.gui.adaptors.DoubleModel; +import net.sf.openrocket.gui.adaptors.EnumModel; +import net.sf.openrocket.material.Material; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.unit.UnitGroup; + +public class ShockCordConfig extends RocketComponentConfig { + + + public ShockCordConfig(RocketComponent component) { + super(component); + + JPanel panel = new JPanel(new MigLayout("gap rel unrel","[][65lp::][30lp::]","")); + JLabel label; + DoubleModel m; + JSpinner spin; + String tip; + + + ////// Left side + + // Cord length + label = new JLabel("Shock cord length"); + panel.add(label); + + m = new DoubleModel(component,"CordLength",UnitGroup.UNITS_LENGTH,0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(0, 1, 10)),"w 100lp, wrap"); + + + // Material + materialPanel(panel, Material.Type.LINE, "Shock cord material:", null); + + + + ///// Right side + JPanel panel2 = new JPanel(new MigLayout("gap rel unrel","[][65lp::][30lp::]","")); + panel.add(panel2, "cell 4 0, gapleft paragraph, aligny 0%, spany"); + + + //// Position + + panel2.add(new JLabel("Position relative to:")); + + JComboBox combo = new JComboBox( + new EnumModel(component, "RelativePosition", + new RocketComponent.Position[] { + RocketComponent.Position.TOP, + RocketComponent.Position.MIDDLE, + RocketComponent.Position.BOTTOM, + RocketComponent.Position.ABSOLUTE + })); + panel2.add(combo,"spanx, growx, wrap"); + + panel2.add(new JLabel("plus"),"right"); + + m = new DoubleModel(component,"PositionValue",UnitGroup.UNITS_LENGTH); + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel2.add(spin,"growx"); + + panel2.add(new UnitSelector(m),"growx"); + panel2.add(new BasicSlider(m.getSliderModel( + new DoubleModel(component.getParent(), "Length", -1.0, UnitGroup.UNITS_NONE), + new DoubleModel(component.getParent(), "Length"))), + "w 100lp, wrap"); + + + //// Spatial length + panel2.add(new JLabel("Packed length:")); + + m = new DoubleModel(component,"Length",UnitGroup.UNITS_LENGTH,0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel2.add(spin,"growx"); + + panel2.add(new UnitSelector(m),"growx"); + panel2.add(new BasicSlider(m.getSliderModel(0, 0.1, 0.5)),"w 100lp, wrap"); + + + //// Tube diameter + panel2.add(new JLabel("Packed diameter:")); + + DoubleModel od = new DoubleModel(component,"Radius",2,UnitGroup.UNITS_LENGTH,0); + // Diameter = 2*Radius + + spin = new JSpinner(od.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel2.add(spin,"growx"); + + panel2.add(new UnitSelector(od),"growx"); + panel2.add(new BasicSlider(od.getSliderModel(0, 0.04, 0.2)),"w 100lp, wrap"); + + + + + tabbedPane.insertTab("General", null, panel, "General properties", 0); +// tabbedPane.insertTab("Radial position", null, positionTab(), +// "Radial position configuration", 1); + tabbedPane.setSelectedIndex(0); + } + + +} diff --git a/src/net/sf/openrocket/gui/configdialog/SleeveConfig.java b/src/net/sf/openrocket/gui/configdialog/SleeveConfig.java new file mode 100644 index 000000000..fbb24806f --- /dev/null +++ b/src/net/sf/openrocket/gui/configdialog/SleeveConfig.java @@ -0,0 +1,22 @@ +package net.sf.openrocket.gui.configdialog; + + +import javax.swing.JPanel; + +import net.sf.openrocket.rocketcomponent.RocketComponent; + + + +public class SleeveConfig extends RingComponentConfig { + + public SleeveConfig(RocketComponent c) { + super(c); + + JPanel tab; + + tab = generalTab("Outer diameter:", "Inner diameter:", "Wall thickness:", "Length:"); + tabbedPane.insertTab("General", null, tab, "General properties", 0); + tabbedPane.setSelectedIndex(0); + } + +} \ No newline at end of file diff --git a/src/net/sf/openrocket/gui/configdialog/StreamerConfig.java b/src/net/sf/openrocket/gui/configdialog/StreamerConfig.java new file mode 100644 index 000000000..951969e6a --- /dev/null +++ b/src/net/sf/openrocket/gui/configdialog/StreamerConfig.java @@ -0,0 +1,270 @@ +package net.sf.openrocket.gui.configdialog; + + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSpinner; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.gui.BasicSlider; +import net.sf.openrocket.gui.ResizeLabel; +import net.sf.openrocket.gui.SpinnerEditor; +import net.sf.openrocket.gui.UnitSelector; +import net.sf.openrocket.gui.adaptors.DoubleModel; +import net.sf.openrocket.gui.adaptors.EnumModel; +import net.sf.openrocket.gui.adaptors.MaterialModel; +import net.sf.openrocket.material.Material; +import net.sf.openrocket.rocketcomponent.MassComponent; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.rocketcomponent.MotorMount.IgnitionEvent; +import net.sf.openrocket.unit.UnitGroup; + +public class StreamerConfig extends RecoveryDeviceConfig { + + public StreamerConfig(final RocketComponent component) { + super(component); + + JPanel primary = new JPanel(new MigLayout()); + + JPanel panel = new JPanel(new MigLayout("gap rel unrel","[][65lp::][30lp::][]","")); + + + + panel.add(new JLabel("Strip length:")); + + DoubleModel m = new DoubleModel(component,"StripLength",UnitGroup.UNITS_LENGTH,0); + + JSpinner spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(0, 0.6, 1.5)),"w 100lp, wrap"); + + + panel.add(new JLabel("Strip width:")); + + m = new DoubleModel(component,"StripWidth",UnitGroup.UNITS_LENGTH,0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(0, 0.2)),"w 100lp, wrap 20lp"); + + + + + panel.add(new JLabel("Strip area:")); + + m = new DoubleModel(component,"Area",UnitGroup.UNITS_AREA,0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(0, 0.04, 0.25)),"w 100lp, wrap"); + + + panel.add(new JLabel("Aspect ratio:")); + + m = new DoubleModel(component,"AspectRatio",UnitGroup.UNITS_NONE,0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); +// panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(2, 15)),"skip, w 100lp, wrap 20lp"); + + + + panel.add(new JLabel("Material:")); + + JComboBox combo = new JComboBox(new MaterialModel(component, Material.Type.SURFACE)); + combo.setToolTipText("The component material affects the weight of the component."); + panel.add(combo,"spanx 3, growx, wrap 20lp"); + + + + // CD + JLabel label = new JLabel("Drag coefficient CD:"); + String tip = "The drag coefficient relative to the total area of the streamer.
" + + "A larger drag coefficient yields a slowed descent rate."; + label.setToolTipText(tip); + panel.add(label); + + m = new DoubleModel(component,"CD",UnitGroup.UNITS_COEFFICIENT,0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setToolTipText(tip); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + JCheckBox check = new JCheckBox(m.getAutomaticAction()); + check.setText("Automatic"); + panel.add(check,"skip, span, wrap"); + + panel.add(new ResizeLabel("The drag coefficient is relative to the area of the streamer.", + -2), "span, wrap"); + + + + primary.add(panel, "grow, gapright 20lp"); + panel = new JPanel(new MigLayout("gap rel unrel","[][65lp::][30lp::][]","")); + + + + + //// Position + + panel.add(new JLabel("Position relative to:")); + + combo = new JComboBox( + new EnumModel(component, "RelativePosition", + new RocketComponent.Position[] { + RocketComponent.Position.TOP, + RocketComponent.Position.MIDDLE, + RocketComponent.Position.BOTTOM, + RocketComponent.Position.ABSOLUTE + })); + panel.add(combo,"spanx, growx, wrap"); + + panel.add(new JLabel("plus"),"right"); + + m = new DoubleModel(component,"PositionValue",UnitGroup.UNITS_LENGTH); + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel( + new DoubleModel(component.getParent(), "Length", -1.0, UnitGroup.UNITS_NONE), + new DoubleModel(component.getParent(), "Length"))), + "w 100lp, wrap"); + + + //// Spatial length + panel.add(new JLabel("Packed length:")); + + m = new DoubleModel(component,"Length",UnitGroup.UNITS_LENGTH,0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(0, 0.1, 0.5)),"w 100lp, wrap"); + + + //// Tube diameter + panel.add(new JLabel("Packed diameter:")); + + DoubleModel od = new DoubleModel(component,"Radius",2,UnitGroup.UNITS_LENGTH,0); + // Diameter = 2*Radius + + spin = new JSpinner(od.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(od),"growx"); + panel.add(new BasicSlider(od.getSliderModel(0, 0.04, 0.2)),"w 100lp, wrap 30lp"); + + + //// Deployment + + panel.add(new JLabel("Deploys at:"),""); + + combo = new JComboBox(new EnumModel(component, "DeployEvent")); + panel.add(combo,"spanx 3, growx, wrap"); + + // ... and delay + panel.add(new JLabel("plus"),"right"); + + m = new DoubleModel(component,"DeployDelay",0); + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"spanx, split"); + + panel.add(new JLabel("seconds"),"wrap paragraph"); + + // Altitude + label = new JLabel("Altitude:"); + altitudeComponents.add(label); + panel.add(label); + + m = new DoubleModel(component,"DeployAltitude",UnitGroup.UNITS_DISTANCE,0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + altitudeComponents.add(spin); + panel.add(spin,"growx"); + UnitSelector unit = new UnitSelector(m); + altitudeComponents.add(unit); + panel.add(unit,"growx"); + BasicSlider slider = new BasicSlider(m.getSliderModel(100, 1000)); + altitudeComponents.add(slider); + panel.add(slider,"w 100lp, wrap"); + + + primary.add(panel, "grow"); + + updateFields(); + + tabbedPane.insertTab("General", null, primary, "General properties", 0); + tabbedPane.insertTab("Radial position", null, positionTab(), + "Radial position configuration", 1); + tabbedPane.setSelectedIndex(0); + } + + + + + + protected JPanel positionTab() { + JPanel panel = new JPanel(new MigLayout("gap rel unrel","[][65lp::][30lp::]","")); + + //// Radial position + panel.add(new JLabel("Radial distance:")); + + DoubleModel m = new DoubleModel(component,"RadialPosition",UnitGroup.UNITS_LENGTH,0); + + JSpinner spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(0, 0.1, 1.0)),"w 100lp, wrap"); + + + //// Radial direction + panel.add(new JLabel("Radial direction:")); + + m = new DoubleModel(component,"RadialDirection",UnitGroup.UNITS_ANGLE,0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(-Math.PI, Math.PI)),"w 100lp, wrap"); + + + //// Reset button + JButton button = new JButton("Reset"); + button.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + ((MassComponent) component).setRadialDirection(0.0); + ((MassComponent) component).setRadialPosition(0.0); + } + }); + panel.add(button,"spanx, right"); + + return panel; + } +} diff --git a/src/net/sf/openrocket/gui/configdialog/ThicknessRingComponentConfig.java b/src/net/sf/openrocket/gui/configdialog/ThicknessRingComponentConfig.java new file mode 100644 index 000000000..2230979b6 --- /dev/null +++ b/src/net/sf/openrocket/gui/configdialog/ThicknessRingComponentConfig.java @@ -0,0 +1,22 @@ +package net.sf.openrocket.gui.configdialog; + + +import javax.swing.JPanel; + +import net.sf.openrocket.rocketcomponent.RocketComponent; + + + +public class ThicknessRingComponentConfig extends RingComponentConfig { + + public ThicknessRingComponentConfig(RocketComponent c) { + super(c); + + JPanel tab; + + tab = generalTab("Outer diameter:", "Inner diameter:", "Wall thickness:", "Length:"); + tabbedPane.insertTab("General", null, tab, "General properties", 0); + tabbedPane.setSelectedIndex(0); + } + +} \ No newline at end of file diff --git a/src/net/sf/openrocket/gui/configdialog/TransitionConfig.java b/src/net/sf/openrocket/gui/configdialog/TransitionConfig.java new file mode 100644 index 000000000..61a6ff244 --- /dev/null +++ b/src/net/sf/openrocket/gui/configdialog/TransitionConfig.java @@ -0,0 +1,196 @@ +package net.sf.openrocket.gui.configdialog; + + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSpinner; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.gui.BasicSlider; +import net.sf.openrocket.gui.DescriptionArea; +import net.sf.openrocket.gui.SpinnerEditor; +import net.sf.openrocket.gui.UnitSelector; +import net.sf.openrocket.gui.adaptors.BooleanModel; +import net.sf.openrocket.gui.adaptors.DoubleModel; +import net.sf.openrocket.material.Material; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.rocketcomponent.Transition; +import net.sf.openrocket.unit.UnitGroup; + +public class TransitionConfig extends RocketComponentConfig { + + private JComboBox typeBox; + //private JLabel description; + + private JLabel shapeLabel; + private JSpinner shapeSpinner; + private BasicSlider shapeSlider; + private DescriptionArea description; + + + // Prepended to the description from Transition.DESCRIPTIONS + private static final String PREDESC = "

"; + + + public TransitionConfig(RocketComponent c) { + super(c); + + DoubleModel m; + JSpinner spin; + JCheckBox checkbox; + + JPanel panel = new JPanel(new MigLayout("gap rel unrel","[][65lp::][30lp::]","")); + + + + //// Shape selection + + panel.add(new JLabel("Transition shape:")); + + Transition.Shape selected = ((Transition)component).getType(); + Transition.Shape[] typeList = Transition.Shape.values(); + + typeBox = new JComboBox(typeList); + typeBox.setEditable(false); + typeBox.setSelectedItem(selected); + typeBox.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + Transition.Shape s = (Transition.Shape)typeBox.getSelectedItem(); + ((Transition)component).setType(s); + description.setText(PREDESC + s.getTransitionDescription()); + updateEnabled(); + } + }); + panel.add(typeBox,"span, split 2"); + + + checkbox = new JCheckBox(new BooleanModel(component,"Clipped")); + checkbox.setText("Clipped"); + panel.add(checkbox,"wrap"); + + + //// Shape parameter + shapeLabel = new JLabel("Shape parameter:"); + panel.add(shapeLabel); + + m = new DoubleModel(component,"ShapeParameter"); + + shapeSpinner = new JSpinner(m.getSpinnerModel()); + shapeSpinner.setEditor(new SpinnerEditor(shapeSpinner)); + panel.add(shapeSpinner,"growx"); + + DoubleModel min = new DoubleModel(component,"ShapeParameterMin"); + DoubleModel max = new DoubleModel(component,"ShapeParameterMax"); + shapeSlider = new BasicSlider(m.getSliderModel(min,max)); + panel.add(shapeSlider,"skip, w 100lp, wrap"); + + updateEnabled(); + + + //// Length + panel.add(new JLabel("Transition length:")); + + m = new DoubleModel(component,"Length",UnitGroup.UNITS_LENGTH,0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(0, 0.05, 0.3)),"w 100lp, wrap"); + + + //// Transition diameter 1 + panel.add(new JLabel("Fore diameter:")); + + DoubleModel od = new DoubleModel(component,"ForeRadius",2,UnitGroup.UNITS_LENGTH,0); + // Diameter = 2*Radius + + spin = new JSpinner(od.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(od),"growx"); + panel.add(new BasicSlider(od.getSliderModel(0, 0.04, 0.2)),"w 100lp, wrap 0px"); + + checkbox = new JCheckBox(od.getAutomaticAction()); + checkbox.setText("Automatic"); + panel.add(checkbox,"skip, span 2, wrap"); + + + //// Transition diameter 2 + panel.add(new JLabel("Aft diameter:")); + + od = new DoubleModel(component,"AftRadius",2,UnitGroup.UNITS_LENGTH,0); + // Diameter = 2*Radius + + spin = new JSpinner(od.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(od),"growx"); + panel.add(new BasicSlider(od.getSliderModel(0, 0.04, 0.2)),"w 100lp, wrap 0px"); + + checkbox = new JCheckBox(od.getAutomaticAction()); + checkbox.setText("Automatic"); + panel.add(checkbox,"skip, span 2, wrap"); + + + //// Wall thickness + panel.add(new JLabel("Wall thickness:")); + + m = new DoubleModel(component,"Thickness",UnitGroup.UNITS_LENGTH,0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(0,0.01)),"w 100lp, wrap 0px"); + + + checkbox = new JCheckBox(new BooleanModel(component,"Filled")); + checkbox.setText("Filled"); + panel.add(checkbox,"skip, span 2, wrap"); + + + + //// Description + + JPanel panel2 = new JPanel(new MigLayout("ins 0")); + + description = new DescriptionArea(5); + description.setText(PREDESC + ((Transition)component).getType(). + getTransitionDescription()); + panel2.add(description, "wmin 250lp, spanx, growx, wrap para"); + + + //// Material + + + materialPanel(panel2, Material.Type.BULK); + panel.add(panel2, "cell 4 0, gapleft paragraph, aligny 0%, spany"); + + + tabbedPane.insertTab("General", null, panel, "General properties", 0); + tabbedPane.insertTab("Shoulder", null, shoulderTab(), "Shoulder properties", 1); + tabbedPane.setSelectedIndex(0); + } + + + + + + private void updateEnabled() { + boolean e = ((Transition)component).getType().usesParameter(); + shapeLabel.setEnabled(e); + shapeSpinner.setEnabled(e); + shapeSlider.setEnabled(e); + } + +} diff --git a/src/net/sf/openrocket/gui/configdialog/TrapezoidFinSetConfig.java b/src/net/sf/openrocket/gui/configdialog/TrapezoidFinSetConfig.java new file mode 100644 index 000000000..4a640ec9e --- /dev/null +++ b/src/net/sf/openrocket/gui/configdialog/TrapezoidFinSetConfig.java @@ -0,0 +1,235 @@ +package net.sf.openrocket.gui.configdialog; + + +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSeparator; +import javax.swing.JSpinner; +import javax.swing.SwingConstants; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.gui.BasicSlider; +import net.sf.openrocket.gui.SpinnerEditor; +import net.sf.openrocket.gui.UnitSelector; +import net.sf.openrocket.gui.adaptors.DoubleModel; +import net.sf.openrocket.gui.adaptors.EnumModel; +import net.sf.openrocket.gui.adaptors.IntegerModel; +import net.sf.openrocket.material.Material; +import net.sf.openrocket.rocketcomponent.FinSet; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.rocketcomponent.TrapezoidFinSet; +import net.sf.openrocket.unit.UnitGroup; + + +public class TrapezoidFinSetConfig extends FinSetConfig { + + public TrapezoidFinSetConfig(final RocketComponent component) { + super(component); + + DoubleModel m; + JSpinner spin; + JComboBox combo; + + JPanel mainPanel = new JPanel(new MigLayout()); + + + JPanel panel = new JPanel(new MigLayout("gap rel unrel","[][65lp::][30lp::]","")); + + //// Number of fins + JLabel label = new JLabel("Number of fins:"); + label.setToolTipText("The number of fins in the fin set."); + panel.add(label); + + IntegerModel im = new IntegerModel(component,"FinCount",1,8); + + spin = new JSpinner(im.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + spin.setToolTipText("The number of fins in the fin set."); + panel.add(spin,"growx, wrap"); + + + //// Base rotation + label = new JLabel("Fin rotation:"); + label.setToolTipText("The angle of the first fin in the fin set."); + panel.add(label); + + m = new DoubleModel(component, "BaseRotation", UnitGroup.UNITS_ANGLE,-Math.PI,Math.PI); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(-Math.PI,Math.PI)),"w 100lp, wrap"); + + + //// Fin cant + label = new JLabel("Fin cant:"); + label.setToolTipText("The angle that the fins are canted with respect to the rocket " + + "body."); + panel.add(label); + + m = new DoubleModel(component, "CantAngle", UnitGroup.UNITS_ANGLE, + -FinSet.MAX_CANT, FinSet.MAX_CANT); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(-FinSet.MAX_CANT,FinSet.MAX_CANT)), + "w 100lp, wrap"); + + + //// Root chord + panel.add(new JLabel("Root chord:")); + + m = new DoubleModel(component,"RootChord",UnitGroup.UNITS_LENGTH,0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(0,0.05,0.2)),"w 100lp, wrap"); + + + + //// Tip chord + panel.add(new JLabel("Tip chord:")); + + m = new DoubleModel(component,"TipChord",UnitGroup.UNITS_LENGTH,0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(0,0.05,0.2)),"w 100lp, wrap"); + + + //// Height + panel.add(new JLabel("Height:")); + + m = new DoubleModel(component,"Height",UnitGroup.UNITS_LENGTH,0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(0,0.05,0.2)),"w 100lp, wrap"); + + + + //// Sweep + panel.add(new JLabel("Sweep length:")); + + m = new DoubleModel(component,"Sweep",UnitGroup.UNITS_LENGTH); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + + // sweep slider from -1.1*TipChord to 1.1*RootChord + DoubleModel tc = new DoubleModel(component,"TipChord",-1.1,UnitGroup.UNITS_LENGTH); + DoubleModel rc = new DoubleModel(component,"RootChord",1.1,UnitGroup.UNITS_LENGTH); + panel.add(new BasicSlider(m.getSliderModel(tc,rc)),"w 100lp, wrap"); + + + //// Sweep angle + panel.add(new JLabel("Sweep angle:")); + + m = new DoubleModel(component, "SweepAngle",UnitGroup.UNITS_ANGLE, + -TrapezoidFinSet.MAX_SWEEP_ANGLE,TrapezoidFinSet.MAX_SWEEP_ANGLE); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(-Math.PI/4,Math.PI/4)), + "w 100lp, wrap paragraph"); + + + + + + mainPanel.add(panel,"aligny 20%"); + + mainPanel.add(new JSeparator(SwingConstants.VERTICAL),"growy"); + + + + panel = new JPanel(new MigLayout("gap rel unrel","[][65lp::][30lp::]","")); + + + + //// Cross section + panel.add(new JLabel("Fin cross section:")); + combo = new JComboBox( + new EnumModel(component,"CrossSection")); + panel.add(combo,"span, growx, wrap"); + + + //// Thickness + panel.add(new JLabel("Thickness:")); + + m = new DoubleModel(component,"Thickness",UnitGroup.UNITS_LENGTH,0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel(0,0.01)),"w 100lp, wrap para"); + + + //// Position + + panel.add(new JLabel("Position relative to:")); + + combo = new JComboBox( + new EnumModel(component, "RelativePosition", + new RocketComponent.Position[] { + RocketComponent.Position.TOP, + RocketComponent.Position.MIDDLE, + RocketComponent.Position.BOTTOM, + RocketComponent.Position.ABSOLUTE + })); + panel.add(combo,"spanx, growx, wrap"); + + panel.add(new JLabel("plus"),"right"); + + m = new DoubleModel(component,"PositionValue",UnitGroup.UNITS_LENGTH); + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin,"growx"); + + panel.add(new UnitSelector(m),"growx"); + panel.add(new BasicSlider(m.getSliderModel( + new DoubleModel(component.getParent(), "Length", -1.0, UnitGroup.UNITS_NONE), + new DoubleModel(component.getParent(), "Length"))), + "w 100lp, wrap para"); + + + + //// Material + materialPanel(panel, Material.Type.BULK); + + + + + mainPanel.add(panel,"aligny 20%"); + + + tabbedPane.insertTab("General", null, mainPanel, "General properties", 0); + tabbedPane.setSelectedIndex(0); + + addFinSetButtons(); + + } +} diff --git a/src/net/sf/openrocket/gui/figureelements/CGCaret.java b/src/net/sf/openrocket/gui/figureelements/CGCaret.java new file mode 100644 index 000000000..14a8a677d --- /dev/null +++ b/src/net/sf/openrocket/gui/figureelements/CGCaret.java @@ -0,0 +1,62 @@ +package net.sf.openrocket.gui.figureelements; + +import java.awt.Color; +import java.awt.geom.Area; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Rectangle2D; + +/** + * A mark indicating the position of the center of gravity. It is a blue circle with every + * second quarter filled with blue. + * + * @author Sampo Niskanen + */ + +public class CGCaret extends Caret { + private static final float RADIUS = 7; + + private static Area caret = null; + + /** + * Create a new CGCaret at the specified coordinates. + */ + public CGCaret(double x, double y) { + super(x,y); + } + + /** + * Returns the Area corresponding to the caret. The Area object is created only once, + * after which the object is cloned for new copies. + */ + @Override + protected Area getCaret() { + if (caret != null) { + return (Area)caret.clone(); + } + + Ellipse2D.Float e = new Ellipse2D.Float(-RADIUS,-RADIUS,2*RADIUS,2*RADIUS); + caret = new Area(e); + + Area a; + a = new Area(new Rectangle2D.Float(-RADIUS,-RADIUS,RADIUS,RADIUS)); + caret.subtract(a); + a = new Area(new Rectangle2D.Float(0,0,RADIUS,RADIUS)); + caret.subtract(a); + + a = new Area(new Ellipse2D.Float(-RADIUS,-RADIUS,2*RADIUS,2*RADIUS)); + a.subtract(new Area(new Ellipse2D.Float(-RADIUS*0.9f,-RADIUS*0.9f, + 2*0.9f*RADIUS,2*0.9f*RADIUS))); + caret.add(a); + + return (Area) caret.clone(); + } + + /** + * Return the color of the caret (blue). + */ + @Override + protected Color getColor() { + return Color.BLUE; + } + +} diff --git a/src/net/sf/openrocket/gui/figureelements/CPCaret.java b/src/net/sf/openrocket/gui/figureelements/CPCaret.java new file mode 100644 index 000000000..09e9cceba --- /dev/null +++ b/src/net/sf/openrocket/gui/figureelements/CPCaret.java @@ -0,0 +1,56 @@ +package net.sf.openrocket.gui.figureelements; + +import java.awt.Color; +import java.awt.geom.Area; +import java.awt.geom.Ellipse2D; + +/** + * A mark indicating the position of the center of pressure. It is a red filled circle + * inside a slightly larger red circle. + * + * @author Sampo Niskanen + */ + +public class CPCaret extends Caret { + private static final float RADIUS = 7; + + private static Area caret = null; + + /** + * Create a new CPCaret at the specified coordinates. + */ + public CPCaret(double x, double y) { + super(x,y); + } + + /** + * Returns the Area object of the caret. The Area object is created only once, + * after which new copies are cloned from it. + */ + @Override + protected Area getCaret() { + if (caret != null) { + return (Area)caret.clone(); + } + + Ellipse2D.Float e = new Ellipse2D.Float(-RADIUS,-RADIUS,2*RADIUS,2*RADIUS); + caret = new Area(e); + + caret.subtract(new Area(new Ellipse2D.Float(-RADIUS*0.9f,-RADIUS*0.9f, + 2*0.9f*RADIUS,2*0.9f*RADIUS))); + + caret.add(new Area(new Ellipse2D.Float(-RADIUS*0.75f,-RADIUS*0.75f, + 2*0.75f*RADIUS,2*0.75f*RADIUS))); + + return (Area) caret.clone(); + } + + + /** + * Return the color of the caret (red). + */ + @Override + protected Color getColor() { + return Color.RED; + } +} diff --git a/src/net/sf/openrocket/gui/figureelements/Caret.java b/src/net/sf/openrocket/gui/figureelements/Caret.java new file mode 100644 index 000000000..b82731ff2 --- /dev/null +++ b/src/net/sf/openrocket/gui/figureelements/Caret.java @@ -0,0 +1,55 @@ +package net.sf.openrocket.gui.figureelements; + + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.geom.AffineTransform; +import java.awt.geom.Area; + +public abstract class Caret implements FigureElement { + private double x,y; + + /** + * Creates a new caret at the specified coordinates. + */ + public Caret(double x, double y) { + this.x = x; + this.y = y; + } + + /** + * Sets the position of the caret to the new coordinates. + */ + public void setPosition(double x, double y) { + this.x = x; + this.y = y; + } + + /** + * Paints the caret to the Graphics2D element. + */ + public void paint(Graphics2D g2, double scale) { + Area caret = getCaret(); + AffineTransform t = new AffineTransform(1.0/scale, 0, 0, 1.0/scale, x, y); + caret.transform(t); + + g2.setColor(getColor()); + g2.fill(caret); + } + + + public void paint(Graphics2D g2, double scale, Rectangle visible) { + throw new UnsupportedOperationException("paint() with rectangle unsupported."); + } + + /** + * Return the Area object corresponding to the mark. + */ + protected abstract Area getCaret(); + + /** + * Return the color to be used when drawing the mark. + */ + protected abstract Color getColor(); +} diff --git a/src/net/sf/openrocket/gui/figureelements/FigureElement.java b/src/net/sf/openrocket/gui/figureelements/FigureElement.java new file mode 100644 index 000000000..953d1918e --- /dev/null +++ b/src/net/sf/openrocket/gui/figureelements/FigureElement.java @@ -0,0 +1,12 @@ +package net.sf.openrocket.gui.figureelements; + +import java.awt.Graphics2D; +import java.awt.Rectangle; + +public interface FigureElement { + + public void paint(Graphics2D g2, double scale); + + public void paint(Graphics2D g2, double scale, Rectangle visible); + +} diff --git a/src/net/sf/openrocket/gui/figureelements/RocketInfo.java b/src/net/sf/openrocket/gui/figureelements/RocketInfo.java new file mode 100644 index 000000000..a70e87311 --- /dev/null +++ b/src/net/sf/openrocket/gui/figureelements/RocketInfo.java @@ -0,0 +1,343 @@ +package net.sf.openrocket.gui.figureelements; + +import java.awt.Color; +import java.awt.Font; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.font.GlyphVector; +import java.awt.geom.Rectangle2D; + +import net.sf.openrocket.aerodynamics.Warning; +import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.rocketcomponent.Configuration; +import net.sf.openrocket.simulation.FlightData; +import net.sf.openrocket.unit.UnitGroup; +import net.sf.openrocket.util.MathUtil; +import net.sf.openrocket.util.Prefs; + + + +/** + * A FigureElement that draws text at different positions in the figure + * with general data about the rocket. + * + * @author Sampo Niskanen + */ +public class RocketInfo implements FigureElement { + + // Margin around the figure edges, pixels + private static final int MARGIN = 8; + + // Font to use + private static final Font FONT = new Font(Font.SANS_SERIF, Font.PLAIN, 11); + private static final Font SMALLFONT = new Font(Font.SANS_SERIF, Font.PLAIN, 9); + + + private final Caret cpCaret = new CPCaret(0,0); + private final Caret cgCaret = new CGCaret(0,0); + + private final Configuration configuration; + private final UnitGroup stabilityUnits; + + private double cg = 0, cp = 0; + private double length = 0, diameter = 0; + private double mass = 0; + private double aoa = Double.NaN, theta = Double.NaN, mach = Prefs.getDefaultMach(); + + private WarningSet warnings = null; + + private boolean calculatingData = false; + private FlightData flightData = null; + + private Graphics2D g2 = null; + private float line = 0; + private float x1, x2, y1, y2; + + + + + + public RocketInfo(Configuration configuration) { + this.configuration = configuration; + this.stabilityUnits = UnitGroup.stabilityUnits(configuration); + } + + + @Override + public void paint(Graphics2D g2, double scale) { + throw new UnsupportedOperationException("paint() must be called with coordinates"); + } + + @Override + public void paint(Graphics2D g2, double scale, Rectangle visible) { + this.g2 = g2; + this.line = FONT.getLineMetrics("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", + g2.getFontRenderContext()).getHeight(); + + x1 = visible.x + MARGIN; + x2 = visible.x + visible.width - MARGIN; + y1 = visible.y + line ; + y2 = visible.y + visible.height - MARGIN; + + drawMainInfo(); + drawStabilityInfo(); + drawWarnings(); + drawFlightInformation(); + } + + + public void setCG(double cg) { + this.cg = cg; + } + + public void setCP(double cp) { + this.cp = cp; + } + + public void setLength(double length) { + this.length = length; + } + + public void setDiameter(double diameter) { + this.diameter = diameter; + } + + public void setMass(double mass) { + this.mass = mass; + } + + public void setWarnings(WarningSet warnings) { + this.warnings = warnings.clone(); + } + + public void setAOA(double aoa) { + this.aoa = aoa; + } + + public void setTheta(double theta) { + this.theta = theta; + } + + public void setMach(double mach) { + this.mach = mach; + } + + + public void setFlightData(FlightData data) { + this.flightData = data; + } + + public void setCalculatingData(boolean calc) { + this.calculatingData = calc; + } + + + + + private void drawMainInfo() { + GlyphVector name = createText(configuration.getRocket().getName()); + GlyphVector lengthLine = createText( + "Length " + UnitGroup.UNITS_LENGTH.getDefaultUnit().toStringUnit(length) + + ", max. diameter " + + UnitGroup.UNITS_LENGTH.getDefaultUnit().toStringUnit(diameter)); + + String massText; + if (configuration.hasMotors()) + massText = "Mass with motors "; + else + massText = "Mass with no motors "; + + massText += UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(mass); + + GlyphVector massLine = createText(massText); + + + g2.setColor(Color.BLACK); + + g2.drawGlyphVector(name, x1, y1); + g2.drawGlyphVector(lengthLine, x1, y1+line); + g2.drawGlyphVector(massLine, x1, y1+2*line); + + } + + + private void drawStabilityInfo() { + String at; + + at = "at M="+UnitGroup.UNITS_COEFFICIENT.getDefaultUnit().toStringUnit(mach); + if (!Double.isNaN(aoa)) { + at += " \u03b1=" + UnitGroup.UNITS_ANGLE.getDefaultUnit().toStringUnit(aoa); + } + if (!Double.isNaN(theta)) { + at += " \u0398=" + UnitGroup.UNITS_ANGLE.getDefaultUnit().toStringUnit(theta); + } + + GlyphVector cgValue = createText( + UnitGroup.UNITS_LENGTH.getDefaultUnit().toStringUnit(cg)); + GlyphVector cpValue = createText( + UnitGroup.UNITS_LENGTH.getDefaultUnit().toStringUnit(cp)); + GlyphVector stabValue = createText( + stabilityUnits.getDefaultUnit().toStringUnit(cp-cg)); + + GlyphVector cgText = createText("CG: "); + GlyphVector cpText = createText("CP: "); + GlyphVector stabText = createText("Stability: "); + GlyphVector atText = createSmallText(at); + + Rectangle2D cgRect = cgValue.getVisualBounds(); + Rectangle2D cpRect = cpValue.getVisualBounds(); + Rectangle2D cgTextRect = cgText.getVisualBounds(); + Rectangle2D cpTextRect = cpText.getVisualBounds(); + Rectangle2D stabRect = stabValue.getVisualBounds(); + Rectangle2D stabTextRect = stabText.getVisualBounds(); + Rectangle2D atTextRect = atText.getVisualBounds(); + + double unitWidth = MathUtil.max(cpRect.getWidth(), cgRect.getWidth(), + stabRect.getWidth()); + double textWidth = Math.max(cpTextRect.getWidth(), cgTextRect.getWidth()); + + + g2.setColor(Color.BLACK); + + g2.drawGlyphVector(stabValue, (float)(x2-stabRect.getWidth()), y1); + g2.drawGlyphVector(cgValue, (float)(x2-cgRect.getWidth()), y1+line); + g2.drawGlyphVector(cpValue, (float)(x2-cpRect.getWidth()), y1+2*line); + + g2.drawGlyphVector(stabText, (float)(x2-unitWidth-stabTextRect.getWidth()), y1); + g2.drawGlyphVector(cgText, (float)(x2-unitWidth-cgTextRect.getWidth()), y1+line); + g2.drawGlyphVector(cpText, (float)(x2-unitWidth-cpTextRect.getWidth()), y1+2*line); + + cgCaret.setPosition(x2 - unitWidth - textWidth - 10, y1+line-0.3*line); + cgCaret.paint(g2, 1.7); + + cpCaret.setPosition(x2 - unitWidth - textWidth - 10, y1+2*line-0.3*line); + cpCaret.paint(g2, 1.7); + + float atPos; + if (unitWidth + textWidth + 10 > atTextRect.getWidth()) { + atPos = (float)(x2-(unitWidth+textWidth+10+atTextRect.getWidth())/2); + } else { + atPos = (float)(x2 - atTextRect.getWidth()); + } + + g2.setColor(Color.GRAY); + g2.drawGlyphVector(atText, atPos, y1 + 3*line); + + } + + + private void drawWarnings() { + if (warnings == null || warnings.isEmpty()) + return; + + GlyphVector[] texts = new GlyphVector[warnings.size()+1]; + double max = 0; + + texts[0] = createText("Warning:"); + int i=1; + for (Warning w: warnings) { + texts[i] = createText(w.toString()); + i++; + } + + for (GlyphVector v: texts) { + Rectangle2D rect = v.getVisualBounds(); + if (rect.getWidth() > max) + max = rect.getWidth(); + } + + + float y = y2 - line * warnings.size(); + g2.setColor(new Color(255,0,0,130)); + + for (GlyphVector v: texts) { + Rectangle2D rect = v.getVisualBounds(); + g2.drawGlyphVector(v, (float)(x2 - max/2 - rect.getWidth()/2), y); + y += line; + } + } + + + private void drawFlightInformation() { + double height = drawFlightData(); + + if (calculatingData) { + GlyphVector calculating = createText("Calculating..."); + g2.setColor(Color.BLACK); + g2.drawGlyphVector(calculating, x1, (float)(y2-height)); + } + } + + + private double drawFlightData() { + if (flightData == null) + return 0; + + double width=0; + + GlyphVector apogee = createText("Apogee: "); + GlyphVector maxVelocity = createText("Max. velocity: "); + GlyphVector maxAcceleration = createText("Max. acceleration: "); + + GlyphVector apogeeValue, velocityValue, accelerationValue; + if (!Double.isNaN(flightData.getMaxAltitude())) { + apogeeValue = createText( + UnitGroup.UNITS_DISTANCE.toStringUnit(flightData.getMaxAltitude())); + } else { + apogeeValue = createText("N/A"); + } + if (!Double.isNaN(flightData.getMaxVelocity())) { + velocityValue = createText( + UnitGroup.UNITS_VELOCITY.toStringUnit(flightData.getMaxVelocity()) + + " (Mach " + + UnitGroup.UNITS_COEFFICIENT.toString(flightData.getMaxMachNumber()) + ")"); + } else { + velocityValue = createText("N/A"); + } + if (!Double.isNaN(flightData.getMaxAcceleration())) { + accelerationValue = createText( + UnitGroup.UNITS_ACCELERATION.toStringUnit(flightData.getMaxAcceleration())); + } else { + accelerationValue = createText("N/A"); + } + + Rectangle2D rect; + rect = apogee.getVisualBounds(); + width = MathUtil.max(width, rect.getWidth()); + + rect = maxVelocity.getVisualBounds(); + width = MathUtil.max(width, rect.getWidth()); + + rect = maxAcceleration.getVisualBounds(); + width = MathUtil.max(width, rect.getWidth()); + + width += 5; + + if (!calculatingData) + g2.setColor(new Color(0,0,127)); + else + g2.setColor(new Color(0,0,127,127)); + + + g2.drawGlyphVector(apogee, (float)x1, (float)(y2-2*line)); + g2.drawGlyphVector(maxVelocity, (float)x1, (float)(y2-line)); + g2.drawGlyphVector(maxAcceleration, (float)x1, (float)(y2)); + + g2.drawGlyphVector(apogeeValue, (float)(x1+width), (float)(y2-2*line)); + g2.drawGlyphVector(velocityValue, (float)(x1+width), (float)(y2-line)); + g2.drawGlyphVector(accelerationValue, (float)(x1+width), (float)(y2)); + + return 3*line; + } + + + + private GlyphVector createText(String text) { + return FONT.createGlyphVector(g2.getFontRenderContext(), text); + } + + private GlyphVector createSmallText(String text) { + return SMALLFONT.createGlyphVector(g2.getFontRenderContext(), text); + } + +} diff --git a/src/net/sf/openrocket/gui/main/AboutDialog.java b/src/net/sf/openrocket/gui/main/AboutDialog.java new file mode 100644 index 000000000..ca302bcc2 --- /dev/null +++ b/src/net/sf/openrocket/gui/main/AboutDialog.java @@ -0,0 +1,88 @@ +package net.sf.openrocket.gui.main; + +import java.awt.Desktop; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; + +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.gui.ResizeLabel; +import net.sf.openrocket.util.GUIUtil; +import net.sf.openrocket.util.Prefs; + +public class AboutDialog extends JDialog { + + public static final String OPENROCKET_URL = "http://openrocket.sourceforge.net/"; + + + public AboutDialog(JFrame parent) { + super(parent, true); + + final String version = Prefs.getVersion(); + + JPanel panel = new JPanel(new MigLayout("fill")); + + panel.add(new ResizeLabel("OpenRocket", 20), "ax 50%, wrap para"); + panel.add(new ResizeLabel("Version " + version, 3), "ax 50%, wrap 30lp"); + + panel.add(new ResizeLabel("Copyright \u00A9 2007-2009 Sampo Niskanen"), "ax 50%, wrap para"); + + JLabel link; + + if (Desktop.isDesktopSupported()) { + + link = new JLabel("" + + OPENROCKET_URL + ""); + link.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + Desktop d = Desktop.getDesktop(); + try { + d.browse(new URI(OPENROCKET_URL)); + + } catch (URISyntaxException e1) { + throw new RuntimeException("BUG: Illegal OpenRocket URL: "+OPENROCKET_URL, + e1); + } catch (IOException e1) { + System.err.println("Unable to launch browser:"); + e1.printStackTrace(); + } + } + }); + + } else { + link = new JLabel(OPENROCKET_URL); + } + panel.add(link, "ax 50%, wrap para"); + + + JButton close = new JButton("Close"); + close.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + AboutDialog.this.dispose(); + } + }); + panel.add(close, "right"); + + this.add(panel); + this.setTitle("OpenRocket " + version); + this.pack(); + this.setResizable(false); + this.setLocationRelativeTo(null); + GUIUtil.setDefaultButton(close); + GUIUtil.installEscapeCloseOperation(this); + } + + +} diff --git a/src/net/sf/openrocket/gui/main/BareComponentTreeModel.java b/src/net/sf/openrocket/gui/main/BareComponentTreeModel.java new file mode 100644 index 000000000..d2071bcdc --- /dev/null +++ b/src/net/sf/openrocket/gui/main/BareComponentTreeModel.java @@ -0,0 +1,184 @@ +package net.sf.openrocket.gui.main; + +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.Iterator; + +import javax.swing.JTree; +import javax.swing.event.TreeModelEvent; +import javax.swing.event.TreeModelListener; +import javax.swing.tree.TreeModel; +import javax.swing.tree.TreePath; + +import net.sf.openrocket.rocketcomponent.ComponentChangeEvent; +import net.sf.openrocket.rocketcomponent.ComponentChangeListener; +import net.sf.openrocket.rocketcomponent.RocketComponent; + + +/** + * A TreeModel that implements viewing of the rocket tree structure. + * This class shows the internal structure of the tree, as opposed to the regular + * user-side view. + * + * @author Sampo Niskanen + */ + +public class BareComponentTreeModel implements TreeModel, ComponentChangeListener { + ArrayList listeners = new ArrayList(); + + private final RocketComponent root; + private final JTree tree; + + public BareComponentTreeModel(RocketComponent root, JTree tree) { + this.root = root; + this.tree = tree; + root.addComponentChangeListener(this); + } + + + public Object getChild(Object parent, int index) { + RocketComponent component = (RocketComponent)parent; + try { + return component.getChild(index); + } catch (IndexOutOfBoundsException e) { + return null; + } + } + + public int getChildCount(Object parent) { + return ((RocketComponent)parent).getChildCount(); + } + + public int getIndexOfChild(Object parent, Object child) { + RocketComponent p = (RocketComponent)parent; + RocketComponent c = (RocketComponent)child; + return p.getChildPosition(c); + } + + public Object getRoot() { + return root; + } + + public boolean isLeaf(Object node) { + RocketComponent c = (RocketComponent)node; + return (c.getChildCount()==0); + } + + public void addTreeModelListener(TreeModelListener l) { + listeners.add(l); + } + + public void removeTreeModelListener(TreeModelListener l) { + listeners.remove(l); + } + + private void fireTreeNodesChanged() { + Object[] path = { root }; + TreeModelEvent e = new TreeModelEvent(this,path); + Object[] l = listeners.toArray(); + for (int i=0; i enumer = tree.getExpandedDescendants(new TreePath(path)); + ArrayList expanded = new ArrayList(); + if (enumer != null) { + while (enumer.hasMoreElements()) { + TreePath p = enumer.nextElement(); + expanded.add(((RocketComponent)p.getLastPathComponent()).getID()); + } + } + + // Send structure change event + TreeModelEvent e = new TreeModelEvent(this,path); + Object[] l = listeners.toArray(); + for (int i=0; i iter = expanded.iterator(); + while (iter.hasNext()) { + RocketComponent c = root.findComponent(iter.next()); + if (c==null) + continue; + tree.expandPath(makeTreePath(c)); + } + if (source != null) { + TreePath p = makeTreePath(source); + tree.makeVisible(p); + tree.expandPath(p); + } + } + + public void valueForPathChanged(TreePath path, Object newValue) { + System.err.println("ERROR: valueForPathChanged called?!"); + } + + + public void componentChanged(ComponentChangeEvent e) { + if (e.isTreeChange() || e.isUndoChange()) { + // Tree must be fully updated also in case of an undo change + fireTreeStructureChanged((RocketComponent)e.getSource()); + if (e.isTreeChange() && e.isUndoChange()) { + // If the undo has changed the tree structure, some elements may be hidden unnecessarily + // TODO: LOW: Could this be performed better? + expandAll(); + } + } else if (e.isOtherChange()) { + fireTreeNodesChanged(); + } + } + + public void expandAll() { + Iterator iterator = root.deepIterator(); + while (iterator.hasNext()) { + tree.makeVisible(makeTreePath(iterator.next())); + } + } + + public static TreePath makeTreePath(RocketComponent component) { + int count = 0; + RocketComponent c = component; + + while (c != null) { + count++; + c = c.getParent(); + } + + Object[] list = new Object[count]; + + count--; + c=component; + while (c!=null) { + list[count] = c; + count--; + c = c.getParent(); + } + + return new TreePath(list); + } + +} diff --git a/src/net/sf/openrocket/gui/main/BasicFrame.java b/src/net/sf/openrocket/gui/main/BasicFrame.java new file mode 100644 index 000000000..4bd73ca1d --- /dev/null +++ b/src/net/sf/openrocket/gui/main/BasicFrame.java @@ -0,0 +1,840 @@ +package net.sf.openrocket.gui.main; + +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; + +import javax.swing.Action; +import javax.swing.InputMap; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSeparator; +import javax.swing.JSplitPane; +import javax.swing.JTabbedPane; +import javax.swing.KeyStroke; +import javax.swing.LookAndFeel; +import javax.swing.ScrollPaneConstants; +import javax.swing.SwingUtilities; +import javax.swing.ToolTipManager; +import javax.swing.UIManager; +import javax.swing.border.TitledBorder; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.filechooser.FileFilter; +import javax.swing.tree.DefaultTreeSelectionModel; +import javax.swing.tree.TreePath; +import javax.swing.tree.TreeSelectionModel; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.aerodynamics.Warning; +import net.sf.openrocket.document.OpenRocketDocument; +import net.sf.openrocket.file.GeneralRocketLoader; +import net.sf.openrocket.file.OpenRocketSaver; +import net.sf.openrocket.file.RocketLoadException; +import net.sf.openrocket.file.RocketLoader; +import net.sf.openrocket.file.RocketSaver; +import net.sf.openrocket.gui.ComponentAnalysisDialog; +import net.sf.openrocket.gui.PreferencesDialog; +import net.sf.openrocket.gui.StorageOptionChooser; +import net.sf.openrocket.gui.configdialog.ComponentConfigDialog; +import net.sf.openrocket.gui.scalefigure.RocketPanel; +import net.sf.openrocket.rocketcomponent.ComponentChangeEvent; +import net.sf.openrocket.rocketcomponent.ComponentChangeListener; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.rocketcomponent.Stage; +import net.sf.openrocket.util.Icons; +import net.sf.openrocket.util.Prefs; + +public class BasicFrame extends JFrame { + private static final long serialVersionUID = 1L; + + /** + * The RocketLoader instance used for loading all rocket designs. + */ + private static final RocketLoader ROCKET_LOADER = new GeneralRocketLoader(); + + + /** + * File filter for filtering only rocket designs. + */ + private static final FileFilter ROCKET_DESIGN_FILTER = new FileFilter() { + @Override + public String getDescription() { + return "OpenRocket designs (*.ork)"; + } + @Override + public boolean accept(File f) { + String name = f.getName().toLowerCase(); + return name.endsWith(".ork") || name.endsWith(".ork.gz"); + } + }; + + + + /** + * List of currently open frames. When the list goes empty + * it is time to exit the application. + */ + private static final ArrayList frames = new ArrayList(); + + + + + + /** + * Whether "New" and "Open" should replace this frame. + * Should be set to false on the first rocket modification. + */ + private boolean replaceable = false; + + + + private final OpenRocketDocument document; + private final Rocket rocket; + + private RocketPanel rocketpanel; + private ComponentTree tree = null; + private final TreeSelectionModel selectionModel; + + /** Actions available for rocket modifications */ + private final RocketActions actions; + + + + /** + * Sole constructor. Creates a new frame based on the supplied document + * and adds it to the current frames list. + * + * @param document the document to show. + */ + public BasicFrame(OpenRocketDocument document) { + + this.document = document; + this.rocket = document.getRocket(); + this.rocket.getDefaultConfiguration().setAllStages(); + + + // Set replaceable flag to false at first modification + rocket.addComponentChangeListener(new ComponentChangeListener() { + public void componentChanged(ComponentChangeEvent e) { + replaceable = false; + BasicFrame.this.rocket.removeComponentChangeListener(this); + } + }); + + + // Create the selection model that will be used + selectionModel = new DefaultTreeSelectionModel(); + selectionModel.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); + + actions = new RocketActions(document, selectionModel, this); + + + // The main vertical split pane + JSplitPane vertical = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true); + vertical.setResizeWeight(0.5); + this.add(vertical); + + + // The top tabbed pane + JTabbedPane tabbed = new JTabbedPane(); + tabbed.addTab("Rocket design", null, designTab()); + tabbed.addTab("Flight simulations", null, simulationsTab()); + + vertical.setTopComponent(tabbed); + + + + // Bottom segment, rocket figure + + rocketpanel = new RocketPanel(document); + vertical.setBottomComponent(rocketpanel); + + rocketpanel.setSelectionModel(tree.getSelectionModel()); + + + createMenu(); + + + rocket.addComponentChangeListener(new ComponentChangeListener() { + public void componentChanged(ComponentChangeEvent e) { + setTitle(); + } + }); + + setTitle(); + this.pack(); + + Dimension size = Prefs.getWindowSize(this.getClass()); + if (size == null) { + size = Toolkit.getDefaultToolkit().getScreenSize(); + size.width = size.width*9/10; + size.height = size.height*9/10; + } + this.setSize(size); + this.addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent e) { + Prefs.setWindowSize(BasicFrame.this.getClass(), BasicFrame.this.getSize()); + } + }); + this.setLocationByPlatform(true); + + this.validate(); + vertical.setDividerLocation(0.4); + setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + closeAction(); + } + }); + frames.add(this); + + } + + + /** + * Construct the "Rocket design" tab. This contains a horizontal split pane + * with the left component the design tree and the right component buttons + * for adding components. + */ + private JComponent designTab() { + JSplitPane horizontal = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,true); + horizontal.setResizeWeight(0.5); + + + // Upper-left segment, component tree + + JPanel panel = new JPanel(new MigLayout("fill, flowy","","[grow]")); + + tree = new ComponentTree(rocket); + tree.setSelectionModel(selectionModel); + + // Remove JTree key events that interfere with menu accelerators + InputMap im = SwingUtilities.getUIInputMap(tree, JComponent.WHEN_FOCUSED); + im.put(KeyStroke.getKeyStroke(KeyEvent.VK_X, ActionEvent.CTRL_MASK), null); + im.put(KeyStroke.getKeyStroke(KeyEvent.VK_C, ActionEvent.CTRL_MASK), null); + im.put(KeyStroke.getKeyStroke(KeyEvent.VK_V, ActionEvent.CTRL_MASK), null); + im.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, ActionEvent.CTRL_MASK), null); + im.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, ActionEvent.CTRL_MASK), null); + im.put(KeyStroke.getKeyStroke(KeyEvent.VK_O, ActionEvent.CTRL_MASK), null); + im.put(KeyStroke.getKeyStroke(KeyEvent.VK_N, ActionEvent.CTRL_MASK), null); + + + + // Double-click opens config dialog + MouseListener ml = new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + int selRow = tree.getRowForLocation(e.getX(), e.getY()); + TreePath selPath = tree.getPathForLocation(e.getX(), e.getY()); + if(selRow != -1) { + if(e.getClickCount() == 2) { + // Double-click + RocketComponent c = (RocketComponent)selPath.getLastPathComponent(); + ComponentConfigDialog.showDialog(BasicFrame.this, + BasicFrame.this.document, c); + } + } + } + }; + tree.addMouseListener(ml); + + // Update dialog when selection is changed + selectionModel.addTreeSelectionListener(new TreeSelectionListener() { + public void valueChanged(TreeSelectionEvent e) { + // Scroll tree to the selected item + TreePath path = selectionModel.getSelectionPath(); + if (path == null) + return; + tree.scrollPathToVisible(path); + + if (!ComponentConfigDialog.isDialogVisible()) + return; + RocketComponent c = (RocketComponent)path.getLastPathComponent(); + ComponentConfigDialog.showDialog(BasicFrame.this, + BasicFrame.this.document, c); + } + }); + + // Place tree inside scroll pane + JScrollPane scroll = new JScrollPane(tree); + panel.add(scroll,"spany, grow, wrap"); + + + // Buttons + JButton button = new JButton(actions.getMoveUpAction()); + panel.add(button,"sizegroup buttons, aligny 65%"); + + button = new JButton(actions.getMoveDownAction()); + panel.add(button,"sizegroup buttons, aligny 0%"); + + button = new JButton(actions.getEditAction()); + panel.add(button, "sizegroup buttons"); + + button = new JButton(actions.getNewStageAction()); + panel.add(button,"sizegroup buttons"); + + button = new JButton(actions.getDeleteAction()); + button.setIcon(null); + button.setMnemonic(0); + panel.add(button,"sizegroup buttons"); + + horizontal.setLeftComponent(panel); + + + // Upper-right segment, component addition buttons + + panel = new JPanel(new MigLayout("fill, insets 0","[0::]")); + + scroll = new JScrollPane(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, + ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + scroll.setViewportView(new ComponentAddButtons(document, selectionModel, + scroll.getViewport())); + scroll.setBorder(null); + scroll.setViewportBorder(null); + + TitledBorder border = new TitledBorder("Add new component"); + border.setTitleFont(border.getTitleFont().deriveFont(Font.BOLD)); + scroll.setBorder(border); + + panel.add(scroll,"grow"); + + horizontal.setRightComponent(panel); + + return horizontal; + } + + + /** + * Construct the "Flight simulations" tab. + * @return + */ + private JComponent simulationsTab() { + return new SimulationPanel(document); + } + + + + /** + * Creates the menu for the window. + */ + private void createMenu() { + JMenuBar menubar = new JMenuBar(); + JMenu menu; + JMenuItem item; + + //// File + menu = new JMenu("File"); + menu.setMnemonic(KeyEvent.VK_F); + menu.getAccessibleContext().setAccessibleDescription("File-handling related tasks"); + menubar.add(menu); + + item = new JMenuItem("New",KeyEvent.VK_N); + item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, ActionEvent.CTRL_MASK)); + item.setMnemonic(KeyEvent.VK_N); + item.getAccessibleContext().setAccessibleDescription("Create a new rocket design"); + item.setIcon(Icons.FILE_NEW); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + newAction(); + if (replaceable) + closeAction(); + } + }); + menu.add(item); + + item = new JMenuItem("Open...",KeyEvent.VK_O); + item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, ActionEvent.CTRL_MASK)); + item.getAccessibleContext().setAccessibleDescription("Open a rocket design"); + item.setIcon(Icons.FILE_OPEN); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + openAction(); + } + }); + menu.add(item); + + menu.addSeparator(); + + item = new JMenuItem("Save",KeyEvent.VK_S); + item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, ActionEvent.CTRL_MASK)); + item.getAccessibleContext().setAccessibleDescription("Save the current rocket design"); + item.setIcon(Icons.FILE_SAVE); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + saveAction(); + } + }); + menu.add(item); + + item = new JMenuItem("Save as...",KeyEvent.VK_A); + item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, + ActionEvent.CTRL_MASK | ActionEvent.SHIFT_MASK)); + item.getAccessibleContext().setAccessibleDescription("Save the current rocket design "+ + "to a new file"); + item.setIcon(Icons.FILE_SAVE_AS); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + saveAsAction(); + } + }); + menu.add(item); + +// menu.addSeparator(); + menu.add(new JSeparator()); + + item = new JMenuItem("Close",KeyEvent.VK_C); + item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_W, ActionEvent.CTRL_MASK)); + item.getAccessibleContext().setAccessibleDescription("Close the current rocket design"); + item.setIcon(Icons.FILE_CLOSE); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + closeAction(); + } + }); + menu.add(item); + + menu.addSeparator(); + + item = new JMenuItem("Quit",KeyEvent.VK_Q); + item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, ActionEvent.CTRL_MASK)); + item.getAccessibleContext().setAccessibleDescription("Quit the program"); + item.setIcon(Icons.FILE_QUIT); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + quitAction(); + } + }); + menu.add(item); + + + + //// Edit + menu = new JMenu("Edit"); + menu.setMnemonic(KeyEvent.VK_E); + menu.getAccessibleContext().setAccessibleDescription("Rocket editing"); + menubar.add(menu); + + + Action action = document.getUndoAction(); + item = new JMenuItem(action); + item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, ActionEvent.CTRL_MASK)); + item.setMnemonic(KeyEvent.VK_U); + item.getAccessibleContext().setAccessibleDescription("Undo the previous operation"); + + menu.add(item); + + action = document.getRedoAction(); + item = new JMenuItem(action); + item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Y, ActionEvent.CTRL_MASK)); + item.setMnemonic(KeyEvent.VK_R); + item.getAccessibleContext().setAccessibleDescription("Redo the previously undone " + + "operation"); + menu.add(item); + + menu.addSeparator(); + + + item = new JMenuItem(actions.getCutAction()); + menu.add(item); + + item = new JMenuItem(actions.getCopyAction()); + menu.add(item); + + item = new JMenuItem(actions.getPasteAction()); + menu.add(item); + + item = new JMenuItem(actions.getDeleteAction()); + menu.add(item); + + menu.addSeparator(); + + item = new JMenuItem("Preferences"); + item.setIcon(Icons.PREFERENCES); + item.getAccessibleContext().setAccessibleDescription("Setup the application "+ + "preferences"); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + PreferencesDialog.showPreferences(); + } + }); + menu.add(item); + + + + + //// Analyze + menu = new JMenu("Analyze"); + menu.setMnemonic(KeyEvent.VK_A); + menu.getAccessibleContext().setAccessibleDescription("Analyzing the rocket"); + menubar.add(menu); + + item = new JMenuItem("Component analysis",KeyEvent.VK_C); + item.getAccessibleContext().setAccessibleDescription("Analyze the rocket components " + + "separately"); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + ComponentAnalysisDialog.showDialog(rocketpanel); + } + }); + menu.add(item); + + + + //// Help + + menu = new JMenu("Help"); + menu.setMnemonic(KeyEvent.VK_H); + menu.getAccessibleContext().setAccessibleDescription("Information about OpenRocket"); + menubar.add(menu); + + item = new JMenuItem("License",KeyEvent.VK_L); + item.getAccessibleContext().setAccessibleDescription("OpenRocket license information"); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + new LicenseDialog(BasicFrame.this).setVisible(true); + } + }); + menu.add(item); + + item = new JMenuItem("About",KeyEvent.VK_A); + item.getAccessibleContext().setAccessibleDescription("About OpenRocket"); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + new AboutDialog(BasicFrame.this).setVisible(true); + } + }); + menu.add(item); + + + this.setJMenuBar(menubar); + } + + + + // TODO: HIGH: Remember last directory on open/save + + private void openAction() { + JFileChooser chooser = new JFileChooser(); + chooser.setFileFilter(ROCKET_DESIGN_FILTER); + chooser.setMultiSelectionEnabled(true); + chooser.setCurrentDirectory(Prefs.getDefaultDirectory()); + if (chooser.showOpenDialog(BasicFrame.this) != JFileChooser.APPROVE_OPTION) + return; + + Prefs.setDefaultDirectory(chooser.getCurrentDirectory()); + + File[] files = chooser.getSelectedFiles(); + boolean opened = false; + + for (File file: files) { + System.out.println("Opening file: " + file); + if (open(file)) { + opened = true; + } + } + + // Close this frame if replaceable and file opened successfully + if (replaceable && opened) { + closeAction(); + } + } + + + /** + * Open the specified file in a new design frame. If an error occurs, an error dialog + * is shown and false is returned. + * + * @param file the file to open. + * @return whether the file was successfully loaded and opened. + */ + private static boolean open(File file) { + OpenRocketDocument doc = null; + try { + doc = ROCKET_LOADER.load(file); + } catch (RocketLoadException e) { + JOptionPane.showMessageDialog(null, "Unable to open file '" + file.getName() + +"': " + e.getMessage(), "Error opening file", JOptionPane.ERROR_MESSAGE); + e.printStackTrace(); + return false; + } + + if (doc == null) { + throw new RuntimeException("BUG: Rocket loader returned null"); + } + + // Show warnings + Iterator warns = ROCKET_LOADER.getWarnings().iterator(); + System.out.println("Warnings:"); + while (warns.hasNext()) { + System.out.println(" "+warns.next()); + // TODO: HIGH: dialog + } + + // Set document state + doc.setFile(file); + doc.setSaved(true); + + // Open the frame + BasicFrame frame = new BasicFrame(doc); + frame.setVisible(true); + + return true; + } + + + + private boolean saveAction() { + File file = document.getFile(); + if (file==null) { + return saveAsAction(); + } else { + return saveAs(file); + } + } + + private boolean saveAsAction() { + File file = null; + while (file == null) { + StorageOptionChooser storageChooser = + new StorageOptionChooser(document.getDefaultStorageOptions()); + JFileChooser chooser = new JFileChooser(); + chooser.setFileFilter(ROCKET_DESIGN_FILTER); + chooser.setCurrentDirectory(Prefs.getDefaultDirectory()); + chooser.setAccessory(storageChooser); + if (document.getFile() != null) + chooser.setSelectedFile(document.getFile()); + + if (chooser.showSaveDialog(BasicFrame.this) != JFileChooser.APPROVE_OPTION) + return false; + + file = chooser.getSelectedFile(); + if (file == null) + return false; + + Prefs.setDefaultDirectory(chooser.getCurrentDirectory()); + storageChooser.storeOptions(document.getDefaultStorageOptions()); + + if (file.getName().indexOf('.') < 0) { + String name = file.getAbsolutePath(); + name = name + ".ork"; + file = new File(name); + } + + if (file.exists()) { + int result = JOptionPane.showConfirmDialog(this, + "File '"+file.getName()+"' exists. Do you want to overwrite it?", + "File exists", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); + if (result != JOptionPane.YES_OPTION) + return false; + } + } + saveAs(file); + return true; + } + + + private boolean saveAs(File file) { + System.out.println("Saving to file: " + file.getName()); + boolean saved = false; + + if (!StorageOptionChooser.verifyStorageOptions(document, this)) { + // User cancelled the dialog + return false; + } + + RocketSaver saver = new OpenRocketSaver(); + try { + saver.save(file, document); + document.setFile(file); + document.setSaved(true); + saved = true; + } catch (IOException e) { + JOptionPane.showMessageDialog(this, new String[] { + "An I/O error occurred while saving:", + e.getMessage() }, "Saving failed", JOptionPane.ERROR_MESSAGE); + } + setTitle(); + return saved; + } + + + private boolean closeAction() { + if (!document.isSaved()) { + ComponentConfigDialog.hideDialog(); + int result = JOptionPane.showConfirmDialog(this, + "Design '"+rocket.getName()+"' has not been saved. " + + "Do you want to save it?", + "Design not saved", JOptionPane.YES_NO_CANCEL_OPTION, + JOptionPane.QUESTION_MESSAGE); + if (result == JOptionPane.YES_OPTION) { + // Save + if (!saveAction()) + return false; // If save was interrupted + } else if (result == JOptionPane.NO_OPTION) { + // Don't save: No-op + } else { + // Cancel or close + return false; + } + } + + // Rocket has been saved or discarded + this.dispose(); + + // TODO: LOW: Close only dialogs that have this frame as their parent + ComponentConfigDialog.hideDialog(); + ComponentAnalysisDialog.hideDialog(); + + frames.remove(this); + if (frames.isEmpty()) + System.exit(0); + return true; + } + + /** + * Open a new design window with a basic rocket+stage. + */ + public static void newAction() { + Rocket rocket = new Rocket(); + Stage stage = new Stage(); + stage.setName("Sustainer"); + rocket.addChild(stage); + OpenRocketDocument doc = new OpenRocketDocument(rocket); + doc.setSaved(true); + + BasicFrame frame = new BasicFrame(doc); + frame.replaceable = true; + frame.setVisible(true); + ComponentConfigDialog.showDialog(frame, doc, rocket); + } + + /** + * Quit the application. Confirms saving unsaved designs. The action of File->Quit. + */ + public static void quitAction() { + for (int i=frames.size()-1; i>=0; i--) { + if (!frames.get(i).closeAction()) { + // Close canceled + return; + } + } + // Should not be reached, but just in case + System.exit(0); + } + + + /** + * Set the title of the frame, taking into account the name of the rocket, file it + * has been saved to (if any) and saved status. + */ + private void setTitle() { + File file = document.getFile(); + boolean saved = document.isSaved(); + String title; + + title = rocket.getName(); + if (file!=null) { + title = title + " ("+file.getName()+")"; + } + if (!saved) + title = "*" + title; + + setTitle(title); + } + + + + + + + public static void main(String[] args) { + + /* + * Set the look-and-feel. On Linux, Motif/Metal is sometimes incorrectly used + * which is butt-ugly, so if the system l&f is Motif/Metal, we search for a few + * other alternatives. + */ + try { + UIManager.LookAndFeelInfo[] info = UIManager.getInstalledLookAndFeels(); +// System.out.println("Available look-and-feels:"); +// for (int i=0; i + */ + +public class ComponentAddButtons extends JPanel implements Scrollable { + + private static final int ROWS = 3; + private static final int MAXCOLS = 6; + private static final String BUTTONPARAM = "grow, sizegroup buttons"; + + private static final int GAP = 5; + private static final int EXTRASPACE = 0; + + private final ComponentButton[][] buttons; + + private final OpenRocketDocument document; + private final TreeSelectionModel selectionModel; + private final JViewport viewport; + private final MigLayout layout; + + private final int width, height; + + + public ComponentAddButtons(OpenRocketDocument document, TreeSelectionModel model, + JViewport viewport) { + + super(); + String constaint = "[min!]"; + for (int i=1; i w) + w = d.width; + if (d.height > h) + h = d.height; + } + } + + // Set all buttons to maximum size + System.out.println("Setting w="+w+" h="+h); + width=w; + height=h; + Dimension d = new Dimension(width,height); + for (row=0; row < buttons.length; row++) { + for (int col=0; col < buttons[row].length; col++) { + buttons[row][col].setMinimumSize(d); + buttons[row][col].setPreferredSize(d); + buttons[row][col].getComponent(0).validate(); + } + } + + // Add viewport listener if viewport provided + if (viewport != null) { + viewport.addChangeListener(new ChangeListener() { + private int oldWidth = -1; + public void stateChanged(ChangeEvent e) { + Dimension d = ComponentAddButtons.this.viewport.getExtentSize(); + if (d.width != oldWidth) { + oldWidth = d.width; + flowButtons(); + } + } + }); + } + + add(new JPanel(),"grow"); + } + + + /** + * Adds a row of buttons to the panel. + * @param label Label placed before the row + * @param row Row number + * @param b List of ComponentButtons to place on the row + */ + private void addButtonRow(String label, int row, ComponentButton ... b) { + if (row>0) + add(new JLabel(label),"span, gaptop unrel, wrap"); + else + add(new JLabel(label),"span, gaptop 0, wrap"); + + int col=0; + buttons[row] = new ComponentButton[b.length]; + + for (int i=0; i d.width) { + param = param + ",newline"; + w = GAP+width; + } + if (col == buttons[row].length-1) + param = param + ",wrap"; + layout.setComponentConstraints(buttons[row][col], param); + } + } + revalidate(); + } + + + + /** + * Class for a component button. + */ + private class ComponentButton extends JButton implements TreeSelectionListener { + protected Class componentClass = null; + private Constructor constructor = null; + + /** Only label, no icon. */ + public ComponentButton(String text) { + this(text,null,null); + } + + /** + * Constructor with icon and label. The icon and label are placed into the button. + * The label may contain "\n" as a newline. + */ + public ComponentButton(String text, Icon enabled, Icon disabled) { + super(); + setLayout(new MigLayout("fill, flowy, insets 0, gap 0","","")); + + add(new JLabel(),"push, sizegroup spacing"); + + // Add Icon + if (enabled != null) { + JLabel label = new JLabel(enabled); + if (disabled != null) + label.setDisabledIcon(disabled); + add(label,"growx"); + } + + // Add labels + String[] l = text.split("\n"); + for (int i=0; i c, String text) { + this(text,ComponentIcons.getLargeIcon(c),ComponentIcons.getLargeDisabledIcon(c)); + + if (c==null) + return; + + componentClass = c; + + try { + constructor = c.getConstructor(); + } catch (NoSuchMethodException e) { + throw new IllegalArgumentException("Unable to get default "+ + "constructor for class "+c,e); + } + } + + + /** + * Return whether the current component is addable when the component c is selected. + * c is null if there is no selection. The default is to use c.isCompatible(class). + */ + public boolean isAddable(RocketComponent c) { + if (c==null) + return false; + if (componentClass==null) + return false; + return c.isCompatible(componentClass); + } + + /** + * Return the position to add the component if component c is selected currently. + * The first element of the returned array is the RocketComponent to add the component + * to, and the second (in any) an Integer telling the position of the component. + * A return value of null means that the user cancelled addition of the component. + * If the array has only one element, the component is added at the end of the sibling + * list. By default returns the end of the currently selected component. + * + * @param c The component currently selected + * @return The position to add the new component to, or null if should not add. + */ + public Object[] getAdditionPosition(RocketComponent c) { + return new Object[] { c }; + } + + /** + * Updates the enabled status of the button. + * TODO: LOW: What about updates to the rocket tree? + */ + public void valueChanged(TreeSelectionEvent e) { + updateEnabled(); + } + + /** + * Sets the enabled status of the button and all subcomponents. + */ + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + Component[] c = getComponents(); + for (int i=0; i1) + position = (Integer)pos[1]; + } + + if (c == null) { + // Should not occur + System.err.println("ERROR: Could not place new component."); + Thread.dumpStack(); + updateEnabled(); + return; + } + + if (constructor == null) { + System.err.println("ERROR: Construction of type not supported yet."); + return; + } + + RocketComponent component; + try { + component = (RocketComponent)constructor.newInstance(); + } catch (Exception e) { + throw new RuntimeException("Could not construct new instance of class "+ + constructor,e); + } + + // Next undo position is set by opening the configuration dialog + document.addUndoPosition("Add " + component.getComponentName()); + + + if (position == null) + c.addChild(component); + else + c.addChild(component, position); + + // Select new component and open config dialog + selectionModel.setSelectionPath(ComponentTreeModel.makeTreePath(component)); + + JFrame parent = null; + for (Component comp = ComponentAddButtons.this; comp != null; + comp = comp.getParent()) { + if (comp instanceof JFrame) { + parent = (JFrame) comp; + break; + } + } + + ComponentConfigDialog.showDialog(parent, document, component); + } + } + + /** + * A class suitable for BodyComponents. Addition is allowed ... + */ + private class BodyComponentButton extends ComponentButton { + + public BodyComponentButton(Class c, String text) { + super(c, text); + } + + public BodyComponentButton(String text, Icon enabled, Icon disabled) { + super(text, enabled, disabled); + } + + public BodyComponentButton(String text) { + super(text); + } + + @Override + public boolean isAddable(RocketComponent c) { + if (super.isAddable(c)) + return true; + if (c instanceof BodyComponent) // Handled separately + return true; + return false; + } + + @Override + public Object[] getAdditionPosition(RocketComponent c) { + if (super.isAddable(c)) // Handled automatically + return super.getAdditionPosition(c); + + // Handle BodyComponent separately + if (!(c instanceof BodyComponent)) + return null; + RocketComponent parent = c.getParent(); + assert(parent != null); + + // Check whether to insert between or at the end. + // 0 = ask, 1 = in between, 2 = at the end + int pos = Prefs.getChoise(Prefs.BODY_COMPONENT_INSERT_POSITION_KEY, 2, 0); + if (pos==0) { + if (parent.getChildPosition(c) == parent.getChildCount()-1) + pos = 2; // Selected component is the last component + else + pos = askPosition(); + } + + switch (pos) { + case 0: + // Cancel + return null; + case 1: + // Insert after current position + return new Object[] { parent, new Integer(parent.getChildPosition(c)+1) }; + case 2: + // Insert at the end of the parent + return new Object[] { parent }; + default: + System.err.println("ERROR: Bad position type: "+pos); + Thread.dumpStack(); + return null; + } + } + + private int askPosition() { + Object[] options = { "Insert here", "Add to the end", "Cancel" }; + + JPanel panel = new JPanel(new MigLayout()); + JCheckBox check = new JCheckBox("Do not ask me again"); + panel.add(check,"wrap"); + panel.add(new ResizeLabel("You can change the default operation in the " + + "preferences.",-2)); + + int sel = JOptionPane.showOptionDialog(null, // parent component + new Object[] { + "Insert the component after the current component or as the last " + + "component?", + panel }, + "Select component position", // title + JOptionPane.DEFAULT_OPTION, // default selections + JOptionPane.QUESTION_MESSAGE, // dialog type + null, // icon + options, // options + options[0]); // initial value + + switch (sel) { + case JOptionPane.CLOSED_OPTION: + case 2: + // Cancel + return 0; + case 0: + // Insert + sel = 1; + break; + case 1: + // Add + sel = 2; + break; + default: + System.err.println("ERROR: JOptionPane returned "+sel); + Thread.dumpStack(); + return 0; + } + + if (check.isSelected()) { + // Save the preference + Prefs.NODE.putInt(Prefs.BODY_COMPONENT_INSERT_POSITION_KEY, sel); + } + return sel; + } + + } + + + + /** + * Class for fin sets, that attach only to BodyTubes. + */ + private class FinButton extends ComponentButton { + public FinButton(Class c, String text) { + super(c, text); + } + + public FinButton(String text, Icon enabled, Icon disabled) { + super(text, enabled, disabled); + } + + public FinButton(String text) { + super(text); + } + + @Override + public boolean isAddable(RocketComponent c) { + if (c==null) + return false; + return (c.getClass().equals(BodyTube.class)); + } + } + + + + ///////// Scrolling functionality + + @Override + public Dimension getPreferredScrollableViewportSize() { + return getPreferredSize(); + } + + + @Override + public int getScrollableBlockIncrement(Rectangle visibleRect, + int orientation, int direction) { + if (orientation == SwingConstants.VERTICAL) + return visibleRect.height * 8 / 10; + return 10; + } + + + @Override + public boolean getScrollableTracksViewportHeight() { + return false; + } + + + @Override + public boolean getScrollableTracksViewportWidth() { + return true; + } + + + @Override + public int getScrollableUnitIncrement(Rectangle visibleRect, + int orientation, int direction) { + return 10; + } + +} + diff --git a/src/net/sf/openrocket/gui/main/ComponentIcons.java b/src/net/sf/openrocket/gui/main/ComponentIcons.java new file mode 100644 index 000000000..0c140b26b --- /dev/null +++ b/src/net/sf/openrocket/gui/main/ComponentIcons.java @@ -0,0 +1,145 @@ +package net.sf.openrocket.gui.main; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.net.URL; +import java.util.HashMap; + +import javax.imageio.ImageIO; +import javax.swing.Icon; +import javax.swing.ImageIcon; + +import net.sf.openrocket.rocketcomponent.BodyTube; +import net.sf.openrocket.rocketcomponent.Bulkhead; +import net.sf.openrocket.rocketcomponent.CenteringRing; +import net.sf.openrocket.rocketcomponent.EllipticalFinSet; +import net.sf.openrocket.rocketcomponent.EngineBlock; +import net.sf.openrocket.rocketcomponent.FreeformFinSet; +import net.sf.openrocket.rocketcomponent.InnerTube; +import net.sf.openrocket.rocketcomponent.LaunchLug; +import net.sf.openrocket.rocketcomponent.MassComponent; +import net.sf.openrocket.rocketcomponent.NoseCone; +import net.sf.openrocket.rocketcomponent.Parachute; +import net.sf.openrocket.rocketcomponent.ShockCord; +import net.sf.openrocket.rocketcomponent.Streamer; +import net.sf.openrocket.rocketcomponent.Transition; +import net.sf.openrocket.rocketcomponent.TrapezoidFinSet; +import net.sf.openrocket.rocketcomponent.TubeCoupler; + + +public class ComponentIcons { + + private static final String ICON_DIRECTORY = "pix/componenticons/"; + private static final String SMALL_SUFFIX = "-small.png"; + private static final String LARGE_SUFFIX = "-large.png"; + + private static final HashMap,ImageIcon> SMALL_ICONS = + new HashMap,ImageIcon>(); + private static final HashMap,ImageIcon> LARGE_ICONS = + new HashMap,ImageIcon>(); + private static final HashMap,ImageIcon> DISABLED_ICONS = + new HashMap,ImageIcon>(); + + static { + load("nosecone", "Nose cone", NoseCone.class); + load("bodytube", "Body tube", BodyTube.class); + load("transition", "Transition", Transition.class); + load("trapezoidfin", "Trapezoidal fin set", TrapezoidFinSet.class); + load("ellipticalfin", "Elliptical fin set", EllipticalFinSet.class); + load("freeformfin", "Freeform fin set", FreeformFinSet.class); + load("launchlug", "Launch lug", LaunchLug.class); + load("innertube", "Inner tube", InnerTube.class); + load("tubecoupler", "Tube coupler", TubeCoupler.class); + load("centeringring", "Centering ring", CenteringRing.class); + load("bulkhead", "Bulk head", Bulkhead.class); + load("engineblock", "Engine block", EngineBlock.class); + load("parachute", "Parachute", Parachute.class); + load("streamer", "Streamer", Streamer.class); + load("shockcord", "Shock cord", ShockCord.class); + load("mass", "Mass component", MassComponent.class); + } + + private static void load(String filename, String name, Class componentClass) { + ImageIcon icon = loadSmall(ICON_DIRECTORY + filename + SMALL_SUFFIX, name); + SMALL_ICONS.put(componentClass, icon); + + ImageIcon[] icons = loadLarge(ICON_DIRECTORY + filename + LARGE_SUFFIX, name); + LARGE_ICONS.put(componentClass, icons[0]); + DISABLED_ICONS.put(componentClass, icons[1]); + } + + + + public static Icon getSmallIcon(Class c) { + return SMALL_ICONS.get(c); + } + public static Icon getLargeIcon(Class c) { + return LARGE_ICONS.get(c); + } + public static Icon getLargeDisabledIcon(Class c) { + return DISABLED_ICONS.get(c); + } + + + + + private static ImageIcon loadSmall(String file, String desc) { + URL url = ClassLoader.getSystemResource(file); + if (url==null) { + System.err.println("ERROR: Couldn't find file: " + file); + return null; + } + return new ImageIcon(url, desc); + } + + + private static ImageIcon[] loadLarge(String file, String desc) { + ImageIcon[] icons = new ImageIcon[2]; + + URL url = ClassLoader.getSystemResource(file); + if (url != null) { + BufferedImage bi,bi2; + try { + bi = ImageIO.read(url); + bi2 = ImageIO.read(url); // How the fsck can one duplicate a BufferedImage??? + } catch (IOException e) { + System.err.println("ERROR: Couldn't read file: "+file); + e.printStackTrace(); + return new ImageIcon[]{null,null}; + } + + icons[0] = new ImageIcon(bi,desc); + + // Create disabled icon + if (false) { // Fade using alpha + + int rgb[] = bi2.getRGB(0,0,bi2.getWidth(),bi2.getHeight(),null,0,bi2.getWidth()); + for (int i=0; i>24)&0xFF; + rgb[i] = (rgb[i]&0xFFFFFF) | (alpha/3)<<24; + + //rgb[i] = (rgb[i]&0xFFFFFF) | ((rgb[i]>>1)&0x3F000000); + } + bi2.setRGB(0, 0, bi2.getWidth(), bi2.getHeight(), rgb, 0, bi2.getWidth()); + + } else { // Raster alpha + + for (int x=0; x < bi.getWidth(); x++) { + for (int y=0; y < bi.getHeight(); y++) { + if ((x+y)%2 == 0) { + bi2.setRGB(x, y, 0); + } + } + } + + } + + icons[1] = new ImageIcon(bi2,desc + " (disabled)"); + + return icons; + } else { + System.err.println("ERROR: Couldn't find file: " + file); + return new ImageIcon[]{null,null}; + } + } +} diff --git a/src/net/sf/openrocket/gui/main/ComponentTree.java b/src/net/sf/openrocket/gui/main/ComponentTree.java new file mode 100644 index 000000000..8b4c034b5 --- /dev/null +++ b/src/net/sf/openrocket/gui/main/ComponentTree.java @@ -0,0 +1,92 @@ +package net.sf.openrocket.gui.main; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Component; +import java.awt.Graphics; +import java.awt.Graphics2D; + +import javax.swing.Icon; +import javax.swing.JTree; + +import net.sf.openrocket.rocketcomponent.*; + + +public class ComponentTree extends JTree { + private static final long serialVersionUID = 1L; + + public ComponentTree(RocketComponent root) { + super(); + this.setModel(new ComponentTreeModel(root,this)); +// this.setModel(new BareComponentTreeModel(root,this)); + setToggleClickCount(0); + + javax.swing.plaf.basic.BasicTreeUI ui = new javax.swing.plaf.basic.BasicTreeUI(); + this.setUI(ui); + + ui.setExpandedIcon(TreeIcon.MINUS); + ui.setCollapsedIcon(TreeIcon.PLUS); + + ui.setLeftChildIndent(15); + + + setBackground(Color.WHITE); + setShowsRootHandles(false); + + setCellRenderer(new ComponentTreeRenderer()); + + // Expand whole tree by default + expandTree(); + } + + + public void expandTree() { + for (int i=0; i + */ + +public class ComponentTreeModel implements TreeModel, ComponentChangeListener { + ArrayList listeners = new ArrayList(); + + private final RocketComponent root; + private final JTree tree; + + public ComponentTreeModel(RocketComponent root, JTree tree) { + this.root = root; + this.tree = tree; + root.addComponentChangeListener(this); + } + + + public Object getChild(Object parent, int index) { + RocketComponent component = (RocketComponent)parent; + + try { + return component.getChild(index); + } catch (IndexOutOfBoundsException e) { + return null; + } + } + + + public int getChildCount(Object parent) { + RocketComponent c = (RocketComponent)parent; + + return c.getChildCount(); + } + + + public int getIndexOfChild(Object parent, Object child) { + if (parent==null || child==null) + return -1; + + RocketComponent p = (RocketComponent)parent; + RocketComponent c = (RocketComponent)child; + + return p.getChildPosition(c); + } + + public Object getRoot() { + return root; + } + + public boolean isLeaf(Object node) { + RocketComponent c = (RocketComponent)node; + + return (c.getChildCount() == 0); + } + + public void addTreeModelListener(TreeModelListener l) { + listeners.add(l); + } + + public void removeTreeModelListener(TreeModelListener l) { + listeners.remove(l); + } + + private void fireTreeNodesChanged() { + Object[] path = { root }; + TreeModelEvent e = new TreeModelEvent(this,path); + Object[] l = listeners.toArray(); + for (int i=0; i enumer = tree.getExpandedDescendants(new TreePath(path)); + ArrayList expanded = new ArrayList(); + if (enumer != null) { + while (enumer.hasMoreElements()) { + TreePath p = enumer.nextElement(); + expanded.add(((RocketComponent)p.getLastPathComponent()).getID()); + } + } + + // Send structure change event + TreeModelEvent e = new TreeModelEvent(this,path); + Object[] l = listeners.toArray(); + for (int i=0; i


"; + + String desc = m.getDescription().trim(); + if (desc.length() > 0) { + tip += "" + desc.replace("\n", "
") + "


"; + } + + tip += ("Diameter: " + + UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(m.getDiameter()) + + "
"); + tip += ("Length: " + + UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(m.getLength()) + + "
"); + tip += ("Maximum thrust: " + + UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit(m.getMaxThrust()) + + "
"); + tip += ("Average thrust: " + + UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit(m.getAverageThrust()) + + "
"); + tip += ("Burn time: " + + UnitGroup.UNITS_SHORT_TIME.getDefaultUnit() + .toStringUnit(m.getAverageTime()) + "
"); + tip += ("Total impulse: " + + UnitGroup.UNITS_IMPULSE.getDefaultUnit() + .toStringUnit(m.getTotalImpulse()) + "
"); + tip += ("Launch mass: " + + UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(m.getMass(0)) + + "
"); + tip += ("Empty mass: " + + UnitGroup.UNITS_MASS.getDefaultUnit() + .toStringUnit(m.getMass(Double.MAX_VALUE))); + return tip; + } + + } + + + /** + * The JTable model. Includes an extra motor, given in the constructor, + * if it is not already in the database. + */ + private class MotorDatabaseModel extends AbstractTableModel { + private final Motor extra; + + public MotorDatabaseModel(Motor current) { + if (Databases.MOTOR.contains(current)) + extra = null; + else + extra = current; + } + + @Override + public int getColumnCount() { + return MotorColumns.values().length; + } + + @Override + public int getRowCount() { + if (extra == null) + return Databases.MOTOR.size(); + else + return Databases.MOTOR.size()+1; + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + MotorColumns column = getColumn(columnIndex); + if (extra == null) { + return column.getValue(Databases.MOTOR.get(rowIndex)); + } else { + if (rowIndex == 0) + return column.getValue(extra); + else + return column.getValue(Databases.MOTOR.get(rowIndex - 1)); + } + } + + @Override + public String getColumnName(int columnIndex) { + return getColumn(columnIndex).getTitle(); + } + + + public Motor getMotor(int rowIndex) { + if (extra == null) { + return Databases.MOTOR.get(rowIndex); + } else { + if (rowIndex == 0) + return extra; + else + return Databases.MOTOR.get(rowIndex-1); + } + } + + public int getIndex(Motor m) { + if (extra == null) { + return Databases.MOTOR.indexOf(m); + } else { + if (extra.equals(m)) + return 0; + else + return Databases.MOTOR.indexOf(m)+1; + } + } + + private MotorColumns getColumn(int index) { + return MotorColumns.values()[index]; + } + } + + + //////// Row filters + + /** + * Abstract adapter class. + */ + private abstract class MotorRowFilter extends RowFilter { + @Override + public boolean include( + RowFilter.Entry entry) { + int index = entry.getIdentifier(); + Motor m = model.getMotor(index); + return include(m); + } + + public abstract boolean include(Motor m); + } + + /** + * Show all motors. + */ + private class MotorRowFilterAll extends MotorRowFilter { + @Override + public boolean include(Motor m) { + return true; + } + } + + /** + * Show motors smaller than the mount. + */ + private class MotorRowFilterSmaller extends MotorRowFilter { + @Override + public boolean include(Motor m) { + return (m.getDiameter() <= diameter + 0.0004); + } + } + + /** + * Show motors that fit the mount. + */ + private class MotorRowFilterExact extends MotorRowFilter { + @Override + public boolean include(Motor m) { + return ((m.getDiameter() <= diameter + 0.0004) && + (m.getDiameter() >= diameter - 0.0015)); + } + } + + + private static Comparator numericalComparator = null; + private static Comparator getNumericalComparator() { + if (numericalComparator == null) + numericalComparator = new NumericalComparator(); + return numericalComparator; + } + + private static class NumericalComparator implements Comparator { + private Pattern pattern = + Pattern.compile("^\\s*([0-9]*[.,][0-9]+|[0-9]+[.,]?[0-9]*).*?$"); + private Collator collator = null; + @Override + public int compare(String s1, String s2) { + Matcher m1, m2; + + m1 = pattern.matcher(s1); + m2 = pattern.matcher(s2); + if (m1.find() && m2.find()) { + double d1 = Double.parseDouble(m1.group(1)); + double d2 = Double.parseDouble(m2.group(1)); + + return (int)((d1-d2)*1000); + } + + if (collator == null) + collator = Collator.getInstance(Locale.US); + return collator.compare(s1, s2); + } + } + +} diff --git a/src/net/sf/openrocket/gui/main/RocketActions.java b/src/net/sf/openrocket/gui/main/RocketActions.java new file mode 100644 index 000000000..05b31e4b4 --- /dev/null +++ b/src/net/sf/openrocket/gui/main/RocketActions.java @@ -0,0 +1,551 @@ +package net.sf.openrocket.gui.main; + + +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.JFrame; +import javax.swing.KeyStroke; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.tree.TreePath; +import javax.swing.tree.TreeSelectionModel; + +import net.sf.openrocket.document.OpenRocketDocument; +import net.sf.openrocket.gui.configdialog.ComponentConfigDialog; +import net.sf.openrocket.rocketcomponent.ComponentChangeEvent; +import net.sf.openrocket.rocketcomponent.ComponentChangeListener; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.rocketcomponent.Stage; +import net.sf.openrocket.util.Icons; +import net.sf.openrocket.util.Pair; + + + +/** + * A class that holds Actions for common rocket operations such as + * cut/copy/paste/delete etc. + * + * @author Sampo Niskanen + */ +public class RocketActions { + + private static RocketComponent clipboard = null; + private static List clipboardListeners = new ArrayList(); + + private final OpenRocketDocument document; + private final Rocket rocket; + private final JFrame parentFrame; + private final TreeSelectionModel selectionModel; + + + private final RocketAction deleteAction; + private final RocketAction cutAction; + private final RocketAction copyAction; + private final RocketAction pasteAction; + private final RocketAction editAction; + private final RocketAction newStageAction; + private final RocketAction moveUpAction; + private final RocketAction moveDownAction; + + + public RocketActions(OpenRocketDocument document, TreeSelectionModel selectionModel, + JFrame parentFrame) { + this.document = document; + this.rocket = document.getRocket(); + this.selectionModel = selectionModel; + this.parentFrame = parentFrame; + + // Add action also to updateActions() + this.deleteAction = new DeleteAction(); + this.cutAction = new CutAction(); + this.copyAction = new CopyAction(); + this.pasteAction = new PasteAction(); + this.editAction = new EditAction(); + this.newStageAction = new NewStageAction(); + this.moveUpAction = new MoveUpAction(); + this.moveDownAction = new MoveDownAction(); + + updateActions(); + + // Update all actions when tree selection or rocket changes + + selectionModel.addTreeSelectionListener(new TreeSelectionListener() { + @Override + public void valueChanged(TreeSelectionEvent e) { + updateActions(); + } + }); + document.getRocket().addComponentChangeListener(new ComponentChangeListener() { + @Override + public void componentChanged(ComponentChangeEvent e) { + updateActions(); + } + }); + } + + /** + * Update the state of all of the actions. + */ + private void updateActions() { + deleteAction.update(); + cutAction.update(); + copyAction.update(); + pasteAction.update(); + editAction.update(); + newStageAction.update(); + moveUpAction.update(); + moveDownAction.update(); + } + + + /** + * Update the state of all actions that depend on the clipboard. + */ + private void updateClipboardActions() { + RocketAction[] array = clipboardListeners.toArray(new RocketAction[0]); + for (RocketAction a: array) { + a.update(); + } + } + + + + public Action getDeleteAction() { + return deleteAction; + } + + public Action getCutAction() { + return cutAction; + } + + public Action getCopyAction() { + return copyAction; + } + + public Action getPasteAction() { + return pasteAction; + } + + public Action getEditAction() { + return editAction; + } + + public Action getNewStageAction() { + return newStageAction; + } + + public Action getMoveUpAction() { + return moveUpAction; + } + + public Action getMoveDownAction() { + return moveDownAction; + } + + + + //////// Helper methods for the actions + + /** + * Return the currently selected rocket component, or null if none selected. + * + * @return the currently selected component. + */ + private RocketComponent getSelectedComponent() { + RocketComponent c = null; + TreePath p = selectionModel.getSelectionPath(); + if (p != null) + c = (RocketComponent) p.getLastPathComponent(); + + if (c != null && c.getRocket() != rocket) { + throw new IllegalStateException("Selection not same as document rocket, " + + "report bug!"); + } + return c; + } + + private void setSelectedComponent(RocketComponent component) { + TreePath path = ComponentTreeModel.makeTreePath(component); + selectionModel.setSelectionPath(path); + } + + + private boolean isDeletable(RocketComponent c) { + // Sanity check + if (c == null || c.getParent() == null) + return false; + + // Cannot remove Rocket + if (c instanceof Rocket) + return false; + + // Cannot remove last stage + if ((c instanceof Stage) && (c.getParent().getChildCount() == 1)) { + return false; + } + + return true; + } + + private void delete(RocketComponent c) { + if (!isDeletable(c)) { + throw new IllegalArgumentException("Report bug! Component " + c + " not deletable."); + } + + RocketComponent parent = c.getParent(); + parent.removeChild(c); + } + + private boolean isCopyable(RocketComponent c) { + if (c==null) + return false; + if (c instanceof Rocket) + return false; + return true; + } + + + + + /** + * Return the component and position to which the current clipboard + * should be pasted. Returns null if the clipboard is empty or if the + * clipboard cannot be pasted to the current selection. + * + * @return a Pair with both components defined, or null. + */ + private Pair getPastePosition() { + RocketComponent selected = getSelectedComponent(); + if (selected == null) + return null; + + if (clipboard == null) + return null; + + if (selected.isCompatible(clipboard)) + return new Pair(selected, selected.getChildCount()); + + RocketComponent parent = selected.getParent(); + if (parent != null && parent.isCompatible(clipboard)) { + int index = parent.getChildPosition(selected) + 1; + return new Pair(parent, index); + } + + return null; + } + + + + + + /////// Action classes + + private abstract class RocketAction extends AbstractAction { + public abstract void update(); + } + + + /** + * Action that deletes the selected component. + */ + private class DeleteAction extends RocketAction { + public DeleteAction() { + this.putValue(NAME, "Delete"); + this.putValue(SHORT_DESCRIPTION, "Delete the selected component and subcomponents."); + this.putValue(MNEMONIC_KEY, KeyEvent.VK_D); + this.putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0)); + this.putValue(SMALL_ICON, Icons.EDIT_DELETE); + update(); + } + + @Override + public void actionPerformed(ActionEvent e) { + RocketComponent c = getSelectedComponent(); + if (!isDeletable(c)) + return; + + ComponentConfigDialog.hideDialog(); + + document.addUndoPosition("Delete " + c.getComponentName()); + delete(c); + } + + @Override + public void update() { + this.setEnabled(isDeletable(getSelectedComponent())); + } + } + + + + /** + * Action the cuts the selected component (copies to clipboard and deletes). + */ + private class CutAction extends RocketAction { + public CutAction() { + this.putValue(NAME, "Cut"); + this.putValue(MNEMONIC_KEY, KeyEvent.VK_T); + this.putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_X, + ActionEvent.CTRL_MASK)); + this.putValue(SHORT_DESCRIPTION, "Cut this component (and subcomponents) to " + + "the clipboard and remove from this design"); + this.putValue(SMALL_ICON, Icons.EDIT_CUT); + update(); + } + + @Override + public void actionPerformed(ActionEvent e) { + RocketComponent c = getSelectedComponent(); + if (!isDeletable(c) || !isCopyable(c)) + return; + + ComponentConfigDialog.hideDialog(); + + document.addUndoPosition("Cut " + c.getComponentName()); + clipboard = c.copy(); + delete(c); + updateClipboardActions(); + } + + @Override + public void update() { + RocketComponent c = getSelectedComponent(); + this.setEnabled(isDeletable(c) && isCopyable(c)); + } + } + + + + /** + * Action that copies the selected component to the clipboard. + */ + private class CopyAction extends RocketAction { + public CopyAction() { + this.putValue(NAME, "Copy"); + this.putValue(MNEMONIC_KEY, KeyEvent.VK_C); + this.putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_C, + ActionEvent.CTRL_MASK)); + this.putValue(SHORT_DESCRIPTION, "Copy this component (and subcomponents) to " + + "the clipboard."); + this.putValue(SMALL_ICON, Icons.EDIT_COPY); + update(); + } + + @Override + public void actionPerformed(ActionEvent e) { + RocketComponent c = getSelectedComponent(); + if (!isCopyable(c)) + return; + + clipboard = c.copy(); + updateClipboardActions(); + } + + @Override + public void update() { + this.setEnabled(isCopyable(getSelectedComponent())); + } + + } + + + + /** + * Action that pastes the current clipboard to the selected position. + * It first tries to paste the component to the end of the selected component + * as a child, and after that as a sibling after the selected component. + */ + private class PasteAction extends RocketAction { + public PasteAction() { + this.putValue(NAME, "Paste"); + this.putValue(MNEMONIC_KEY, KeyEvent.VK_P); + this.putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_V, + ActionEvent.CTRL_MASK)); + this.putValue(SHORT_DESCRIPTION, "Paste the component (and subcomponents) on " + + "the clipboard to the design."); + this.putValue(SMALL_ICON, Icons.EDIT_PASTE); + update(); + + // Listen to when the clipboard changes + clipboardListeners.add(this); + } + + @Override + public void actionPerformed(ActionEvent e) { + Pair position = getPastePosition(); + if (position == null) + return; + + ComponentConfigDialog.hideDialog(); + + RocketComponent pasted = clipboard.copy(); + document.addUndoPosition("Paste " + pasted.getComponentName()); + position.getU().addChild(pasted, position.getV()); + setSelectedComponent(pasted); + } + + @Override + public void update() { + this.setEnabled(getPastePosition() != null); + } + } + + + + + + + /** + * Action to edit the currently selected component. + */ + private class EditAction extends RocketAction { + public EditAction() { + this.putValue(NAME, "Edit"); + this.putValue(SHORT_DESCRIPTION, "Edit the selected component."); + update(); + } + + @Override + public void actionPerformed(ActionEvent e) { + RocketComponent c = getSelectedComponent(); + if (c == null) + return; + + ComponentConfigDialog.showDialog(parentFrame, document, c); + } + + @Override + public void update() { + this.setEnabled(getSelectedComponent() != null); + } + } + + + + + + + + /** + * Action to add a new stage to the rocket. + */ + private class NewStageAction extends RocketAction { + public NewStageAction() { + this.putValue(NAME, "New stage"); + this.putValue(SHORT_DESCRIPTION, "Add a new stage to the rocket design."); + update(); + } + + @Override + public void actionPerformed(ActionEvent e) { + + ComponentConfigDialog.hideDialog(); + + RocketComponent stage = new Stage(); + stage.setName("Booster stage"); + document.addUndoPosition("Add stage"); + rocket.addChild(stage); + rocket.getDefaultConfiguration().setAllStages(); + setSelectedComponent(stage); + ComponentConfigDialog.showDialog(parentFrame, document, stage); + + } + + @Override + public void update() { + this.setEnabled(true); + } + } + + + + + /** + * Action to move the selected component upwards in the parent's child list. + */ + private class MoveUpAction extends RocketAction { + public MoveUpAction() { + this.putValue(NAME, "Move up"); + this.putValue(SHORT_DESCRIPTION, "Move this component upwards."); + update(); + } + + @Override + public void actionPerformed(ActionEvent e) { + RocketComponent selected = getSelectedComponent(); + if (!canMove(selected)) + return; + + ComponentConfigDialog.hideDialog(); + + RocketComponent parent = selected.getParent(); + document.addUndoPosition("Move "+selected.getComponentName()); + parent.moveChild(selected, parent.getChildPosition(selected)-1); + setSelectedComponent(selected); + } + + @Override + public void update() { + this.setEnabled(canMove(getSelectedComponent())); + } + + private boolean canMove(RocketComponent c) { + if (c == null || c.getParent() == null) + return false; + RocketComponent parent = c.getParent(); + if (parent.getChildPosition(c) > 0) + return true; + return false; + } + } + + + + /** + * Action to move the selected component down in the parent's child list. + */ + private class MoveDownAction extends RocketAction { + public MoveDownAction() { + this.putValue(NAME, "Move down"); + this.putValue(SHORT_DESCRIPTION, "Move this component downwards."); + update(); + } + + @Override + public void actionPerformed(ActionEvent e) { + RocketComponent selected = getSelectedComponent(); + if (!canMove(selected)) + return; + + ComponentConfigDialog.hideDialog(); + + RocketComponent parent = selected.getParent(); + document.addUndoPosition("Move "+selected.getComponentName()); + parent.moveChild(selected, parent.getChildPosition(selected)+1); + setSelectedComponent(selected); + } + + @Override + public void update() { + this.setEnabled(canMove(getSelectedComponent())); + } + + private boolean canMove(RocketComponent c) { + if (c == null || c.getParent() == null) + return false; + RocketComponent parent = c.getParent(); + if (parent.getChildPosition(c) < parent.getChildCount()-1) + return true; + return false; + } + } + + + +} diff --git a/src/net/sf/openrocket/gui/main/SimulationEditDialog.java b/src/net/sf/openrocket/gui/main/SimulationEditDialog.java new file mode 100644 index 000000000..f9c1c4c65 --- /dev/null +++ b/src/net/sf/openrocket/gui/main/SimulationEditDialog.java @@ -0,0 +1,1008 @@ +package net.sf.openrocket.gui.main; + + +import java.awt.Color; +import java.awt.Component; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Arrays; +import java.util.List; + +import javax.swing.AbstractListModel; +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSpinner; +import javax.swing.JTabbedPane; +import javax.swing.JTextField; +import javax.swing.ListCellRenderer; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.aerodynamics.ExtendedISAModel; +import net.sf.openrocket.document.Simulation; +import net.sf.openrocket.gui.BasicSlider; +import net.sf.openrocket.gui.DescriptionArea; +import net.sf.openrocket.gui.SpinnerEditor; +import net.sf.openrocket.gui.UnitSelector; +import net.sf.openrocket.gui.adaptors.BooleanModel; +import net.sf.openrocket.gui.adaptors.DoubleModel; +import net.sf.openrocket.gui.adaptors.MotorConfigurationModel; +import net.sf.openrocket.gui.plot.Axis; +import net.sf.openrocket.gui.plot.PlotConfiguration; +import net.sf.openrocket.gui.plot.PlotPanel; +import net.sf.openrocket.rocketcomponent.Configuration; +import net.sf.openrocket.simulation.FlightData; +import net.sf.openrocket.simulation.FlightDataBranch; +import net.sf.openrocket.simulation.RK4Simulator; +import net.sf.openrocket.simulation.SimulationConditions; +import net.sf.openrocket.simulation.SimulationListener; +import net.sf.openrocket.simulation.listeners.CSVSaveListener; +import net.sf.openrocket.unit.Unit; +import net.sf.openrocket.unit.UnitGroup; +import net.sf.openrocket.util.GUIUtil; +import net.sf.openrocket.util.Icons; +import net.sf.openrocket.util.Prefs; + +import org.jfree.chart.ChartFactory; +import org.jfree.chart.ChartPanel; +import org.jfree.chart.JFreeChart; +import org.jfree.chart.axis.NumberAxis; +import org.jfree.chart.plot.PlotOrientation; +import org.jfree.chart.plot.ValueMarker; +import org.jfree.chart.plot.XYPlot; +import org.jfree.chart.renderer.xy.StandardXYItemRenderer; +import org.jfree.data.xy.XYSeries; +import org.jfree.data.xy.XYSeriesCollection; + + +public class SimulationEditDialog extends JDialog { + + public static final int DEFAULT = -1; + public static final int EDIT = 1; + public static final int PLOT = 2; + + + private final Window parentWindow; + private final Simulation simulation; + private final SimulationConditions conditions; + private final Configuration configuration; + + + public SimulationEditDialog(Window parent, Simulation s) { + this(parent, s, 0); + } + + public SimulationEditDialog(Window parent, Simulation s, int tab) { + super(parent, "Edit simulation", JDialog.ModalityType.DOCUMENT_MODAL); + + this.parentWindow = parent; + this.simulation = s; + this.conditions = simulation.getConditions(); + configuration = simulation.getConfiguration(); + + JPanel mainPanel = new JPanel(new MigLayout("fill","[grow, fill]")); + + mainPanel.add(new JLabel("Simulation name: "), "span, split 2, shrink"); + final JTextField field = new JTextField(simulation.getName()); + field.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void changedUpdate(DocumentEvent e) { + setText(); + } + @Override + public void insertUpdate(DocumentEvent e) { + setText(); + } + @Override + public void removeUpdate(DocumentEvent e) { + setText(); + } + private void setText() { + String name = field.getText(); + if (name == null || name.equals("")) + return; + System.out.println("Setting name:"+name); + simulation.setName(name); + + } + }); + mainPanel.add(field, "shrinky, growx, wrap"); + + JTabbedPane tabbedPane = new JTabbedPane(); + + + tabbedPane.addTab("Launch conditions", flightConditionsTab()); + tabbedPane.addTab("Simulation options", simulationOptionsTab()); + tabbedPane.addTab("Plot data", plotTab()); +// tabbedPane.addTab("Export data", exportTab()); + + // Select the initial tab + if (tab == EDIT) { + tabbedPane.setSelectedIndex(0); + } else if (tab == PLOT) { + tabbedPane.setSelectedIndex(2); + } else { + FlightData data = s.getSimulatedData(); + if (data == null || data.getBranchCount() == 0) + tabbedPane.setSelectedIndex(0); + else + tabbedPane.setSelectedIndex(2); + } + + mainPanel.add(tabbedPane, "spanx, grow, wrap"); + + + // Buttons + mainPanel.add(new JPanel(), "spanx, split, growx"); + + JButton button; + button = new JButton("Run simulation"); + button.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + SimulationEditDialog.this.dispose(); + SimulationRunDialog.runSimulations(parentWindow, simulation); + } + }); + mainPanel.add(button, "gapright para"); + + + JButton close = new JButton("Close"); + close.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + SimulationEditDialog.this.dispose(); + } + }); + mainPanel.add(close, ""); + + + this.add(mainPanel); + this.validate(); + this.pack(); + this.setLocationByPlatform(true); + GUIUtil.setDefaultButton(button); + GUIUtil.installEscapeCloseOperation(this); + } + + + + + + private JPanel flightConditionsTab() { + JPanel panel = new JPanel(new MigLayout("fill")); + JPanel sub; + String tip; + UnitSelector unit; + BasicSlider slider; + DoubleModel m; + JSpinner spin; + + //// Motor selector + JLabel label = new JLabel("Motor configuration:"); + label.setToolTipText("Select the motor configuration to use."); + panel.add(label, "shrinkx, spanx, split 2"); + + JComboBox combo = new JComboBox(new MotorConfigurationModel(configuration)); + combo.setToolTipText("Select the motor configuration to use."); + combo.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + conditions.setMotorConfigurationID(configuration.getMotorConfigurationID()); + } + }); + panel.add(combo, "growx, wrap para"); + + + //// Wind settings: Average wind speed, turbulence intensity, std. deviation + sub = new JPanel(new MigLayout("fill, gap rel unrel", + "[grow][65lp!][30lp!][75lp!]","")); + sub.setBorder(BorderFactory.createTitledBorder("Wind")); + panel.add(sub, "growx, split 2, aligny 0, flowy, gapright para"); + + + // Wind average + label = new JLabel("Average windspeed:"); + tip = "The average windspeed relative to the ground."; + label.setToolTipText(tip); + sub.add(label); + + m = new DoubleModel(conditions,"WindSpeedAverage", UnitGroup.UNITS_VELOCITY,0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + spin.setToolTipText(tip); + sub.add(spin,"w 65lp!"); + + unit = new UnitSelector(m); + unit.setToolTipText(tip); + sub.add(unit,"growx"); + slider = new BasicSlider(m.getSliderModel(0, 10.0)); + slider.setToolTipText(tip); + sub.add(slider,"w 75lp, wrap"); + + + + // Wind std. deviation + label = new JLabel("Standard deviation:"); + tip = "The standard deviation of the windspeed.
" + + "The windspeed is within twice the standard deviation from the average for " + + "95% of the time."; + label.setToolTipText(tip); + sub.add(label); + + m = new DoubleModel(conditions,"WindSpeedDeviation", UnitGroup.UNITS_VELOCITY,0); + DoubleModel m2 = new DoubleModel(conditions,"WindSpeedAverage", 0.25, + UnitGroup.UNITS_COEFFICIENT,0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + spin.setToolTipText(tip); + sub.add(spin,"w 65lp!"); + + unit = new UnitSelector(m); + unit.setToolTipText(tip); + sub.add(unit,"growx"); + slider = new BasicSlider(m.getSliderModel(new DoubleModel(0), m2)); + slider.setToolTipText(tip); + sub.add(slider,"w 75lp, wrap"); + + + // Wind turbulence intensity + label = new JLabel("Turbulence intensity:"); + tip = "The turbulence intensity is the standard deviation " + + "divided by the average windspeed.
" + + "Typical values range from "+ + UnitGroup.UNITS_RELATIVE.getDefaultUnit().toStringUnit(0.05) + + " to " + + UnitGroup.UNITS_RELATIVE.getDefaultUnit().toStringUnit(0.20) + "."; + label.setToolTipText(tip); + sub.add(label); + + m = new DoubleModel(conditions,"WindTurbulenceIntensity", UnitGroup.UNITS_RELATIVE,0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + spin.setToolTipText(tip); + sub.add(spin,"w 65lp!"); + + unit = new UnitSelector(m); + unit.setToolTipText(tip); + sub.add(unit,"growx"); + + final JLabel intensityLabel = new JLabel( + getIntensityDescription(conditions.getWindTurbulenceIntensity())); + intensityLabel.setToolTipText(tip); + sub.add(intensityLabel,"w 75lp, wrap"); + m.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + intensityLabel.setText( + getIntensityDescription(conditions.getWindTurbulenceIntensity())); + } + }); + + + + + + //// Temperature and pressure + sub = new JPanel(new MigLayout("fill, gap rel unrel", + "[grow][65lp!][30lp!][75lp!]","")); + sub.setBorder(BorderFactory.createTitledBorder("Atmospheric conditions")); + panel.add(sub, "growx, aligny 0, gapright para"); + + + BooleanModel isa = new BooleanModel(conditions, "ISAAtmosphere"); + JCheckBox check = new JCheckBox(isa); + check.setText("Use International Standard Atmosphere"); + check.setToolTipText("Select to use the International Standard Atmosphere model."+ + "
This model has a temperature of " + + UnitGroup.UNITS_TEMPERATURE.toStringUnit(ExtendedISAModel.STANDARD_TEMPERATURE)+ + " and a pressure of " + + UnitGroup.UNITS_PRESSURE.toStringUnit(ExtendedISAModel.STANDARD_PRESSURE) + + " at sea level."); + sub.add(check, "spanx, wrap unrel"); + + // Temperature + label = new JLabel("Temperature:"); + tip = "The temperature at the launch site."; + label.setToolTipText(tip); + isa.addEnableComponent(label, false); + sub.add(label); + + m = new DoubleModel(conditions,"LaunchTemperature", UnitGroup.UNITS_TEMPERATURE,0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + spin.setToolTipText(tip); + isa.addEnableComponent(spin, false); + sub.add(spin,"w 65lp!"); + + unit = new UnitSelector(m); + unit.setToolTipText(tip); + isa.addEnableComponent(unit, false); + sub.add(unit,"growx"); + slider = new BasicSlider(m.getSliderModel(253.15, 308.15)); // -20 ... 35 + slider.setToolTipText(tip); + isa.addEnableComponent(slider, false); + sub.add(slider,"w 75lp, wrap"); + + + + // Pressure + label = new JLabel("Pressure:"); + tip = "The atmospheric pressure at the launch site."; + label.setToolTipText(tip); + isa.addEnableComponent(label, false); + sub.add(label); + + m = new DoubleModel(conditions,"LaunchPressure", UnitGroup.UNITS_PRESSURE,0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + spin.setToolTipText(tip); + isa.addEnableComponent(spin, false); + sub.add(spin,"w 65lp!"); + + unit = new UnitSelector(m); + unit.setToolTipText(tip); + isa.addEnableComponent(unit, false); + sub.add(unit,"growx"); + slider = new BasicSlider(m.getSliderModel(0.950e5, 1.050e5)); + slider.setToolTipText(tip); + isa.addEnableComponent(slider, false); + sub.add(slider,"w 75lp, wrap"); + + + + + + //// Launch site conditions + sub = new JPanel(new MigLayout("fill, gap rel unrel", + "[grow][65lp!][30lp!][75lp!]","")); + sub.setBorder(BorderFactory.createTitledBorder("Launch site")); + panel.add(sub, "growx, split 2, aligny 0, flowy"); + + + // Latitude + label = new JLabel("Latitude:"); + tip = "The launch site latitude affects the gravitational pull of Earth.
" + + "Positive values are on the Northern hemisphere, negative values on the " + + "Southern hemisphere."; + label.setToolTipText(tip); + sub.add(label); + + m = new DoubleModel(conditions,"LaunchLatitude", UnitGroup.UNITS_NONE, -90, 90); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + spin.setToolTipText(tip); + sub.add(spin,"w 65lp!"); + + label = new JLabel("\u00b0 N"); + label.setToolTipText(tip); + sub.add(label,"growx"); + slider = new BasicSlider(m.getSliderModel(-90, 90)); + slider.setToolTipText(tip); + sub.add(slider,"w 75lp, wrap"); + + + + // Altitude + label = new JLabel("Altitude:"); + tip = "The launch altitude above mean sea level.
" + + "This affects the position of the rocket in the atmospheric model."; + label.setToolTipText(tip); + sub.add(label); + + m = new DoubleModel(conditions,"LaunchAltitude", UnitGroup.UNITS_DISTANCE,0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + spin.setToolTipText(tip); + sub.add(spin,"w 65lp!"); + + unit = new UnitSelector(m); + unit.setToolTipText(tip); + sub.add(unit,"growx"); + slider = new BasicSlider(m.getSliderModel(0, 250, 1000)); + slider.setToolTipText(tip); + sub.add(slider,"w 75lp, wrap"); + + + + + + //// Launch rod + sub = new JPanel(new MigLayout("fill, gap rel unrel", + "[grow][65lp!][30lp!][75lp!]","")); + sub.setBorder(BorderFactory.createTitledBorder("Launch rod")); + panel.add(sub, "growx, aligny 0, wrap"); + + + // Length + label = new JLabel("Length:"); + tip = "The length of the launch rod."; + label.setToolTipText(tip); + sub.add(label); + + m = new DoubleModel(conditions,"LaunchRodLength", UnitGroup.UNITS_LENGTH, 0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + spin.setToolTipText(tip); + sub.add(spin,"w 65lp!"); + + unit = new UnitSelector(m); + unit.setToolTipText(tip); + sub.add(unit,"growx"); + slider = new BasicSlider(m.getSliderModel(0, 1, 5)); + slider.setToolTipText(tip); + sub.add(slider,"w 75lp, wrap"); + + + + // Angle + label = new JLabel("Angle:"); + tip = "The angle of the launch rod from vertical."; + label.setToolTipText(tip); + sub.add(label); + + m = new DoubleModel(conditions,"LaunchRodAngle", UnitGroup.UNITS_ANGLE, + 0, SimulationConditions.MAX_LAUNCH_ROD_ANGLE); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + spin.setToolTipText(tip); + sub.add(spin,"w 65lp!"); + + unit = new UnitSelector(m); + unit.setToolTipText(tip); + sub.add(unit,"growx"); + slider = new BasicSlider(m.getSliderModel(0, Math.PI/9, + SimulationConditions.MAX_LAUNCH_ROD_ANGLE)); + slider.setToolTipText(tip); + sub.add(slider,"w 75lp, wrap"); + + + + // Direction + label = new JLabel("Direction:"); + tip = "Direction of the launch rod relative to the wind.
" + + UnitGroup.UNITS_ANGLE.toStringUnit(0) + + " = towards the wind, "+ + UnitGroup.UNITS_ANGLE.toStringUnit(Math.PI) + + " = downwind."; + label.setToolTipText(tip); + sub.add(label); + + m = new DoubleModel(conditions,"LaunchRodDirection", UnitGroup.UNITS_ANGLE, + -Math.PI, Math.PI); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + spin.setToolTipText(tip); + sub.add(spin,"w 65lp!"); + + unit = new UnitSelector(m); + unit.setToolTipText(tip); + sub.add(unit,"growx"); + slider = new BasicSlider(m.getSliderModel(-Math.PI, Math.PI)); + slider.setToolTipText(tip); + sub.add(slider,"w 75lp, wrap"); + + return panel; + } + + + private String getIntensityDescription(double i) { + if (i < 0.001) + return "None"; + if (i < 0.05) + return "Very low"; + if (i < 0.10) + return "Low"; + if (i < 0.15) + return "Medium"; + if (i < 0.20) + return "High"; + if (i < 0.25) + return "Very high"; + return "Extreme"; + } + + + + private JPanel simulationOptionsTab() { + JPanel panel = new JPanel(new MigLayout("fill")); + JPanel sub; + String tip; + JLabel label; + DoubleModel m; + JSpinner spin; + UnitSelector unit; + BasicSlider slider; + + + //// Simulation options + sub = new JPanel(new MigLayout("fill, gap rel unrel", + "[grow][65lp!][30lp!][75lp!]","")); + sub.setBorder(BorderFactory.createTitledBorder("Simulator options")); + panel.add(sub, "w 330lp!, growy, aligny 0"); + + + // Calculation method + tip = "" + + "The Extended Barrowman method calculates aerodynamic forces according
" + + "to the Barrowman equations extended to accommodate more components."; + + label = new JLabel("Calculation method:"); + label.setToolTipText(tip); + sub.add(label, "gaptop unrel, gapright para, spanx, split 2, w 150lp!"); + + label = new JLabel("Extended Barrowman"); + label.setToolTipText(tip); + sub.add(label, "growx, wrap para"); + + + // Simulation method + tip = "" + + "The six degree-of-freedom simulator allows the rocket total freedom during " + + "flight.
" + + "Integration is performed using a 4th order Runge-Kutta 4 " + + "numerical integration."; + + label = new JLabel("Simulation method:"); + label.setToolTipText(tip); + sub.add(label, "gaptop unrel, gapright para, spanx, split 2, w 150lp!"); + + label = new JLabel("6-DOF Runge-Kutta 4"); + label.setToolTipText(tip); + sub.add(label, "growx, wrap 35lp"); + + + // Wind average + label = new JLabel("Time step:"); + tip = "The time between simulation steps.
" + + "A smaller time step results in a more accurate but slower simulation.
" + + "The 4th order simulation method is quite accurate with a time " + + "step of " + + UnitGroup.UNITS_TIME_STEP.toStringUnit(RK4Simulator.RECOMMENDED_TIME_STEP) + + "."; + label.setToolTipText(tip); + sub.add(label); + + m = new DoubleModel(conditions,"TimeStep", UnitGroup.UNITS_TIME_STEP, 0, 1); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + spin.setToolTipText(tip); + sub.add(spin,"w 65lp!"); + + unit = new UnitSelector(m); + unit.setToolTipText(tip); + sub.add(unit,"growx"); + slider = new BasicSlider(m.getSliderModel(0, 0.2)); + slider.setToolTipText(tip); + sub.add(slider,"w 75lp, wrap"); + + + + // Maximum angle step + /* + label = new JLabel("Max. angle step:"); + tip = "" + + "This defines the maximum angle the rocket will turn during one time step.
"+ + "Smaller values result in a more accurate but possibly slower simulation.
"+ + "A recommended value is " + + UnitGroup.UNITS_ANGLE.toStringUnit(RK4Simulator.RECOMMENDED_ANGLE_STEP) + "."; + label.setToolTipText(tip); + sub.add(label); + + m = new DoubleModel(conditions,"MaximumStepAngle", UnitGroup.UNITS_ANGLE, + 1*Math.PI/180, Math.PI/9); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + spin.setToolTipText(tip); + sub.add(spin,"w 65lp!"); + + unit = new UnitSelector(m); + unit.setToolTipText(tip); + sub.add(unit,"growx"); + slider = new BasicSlider(m.getSliderModel(0, Math.toRadians(10))); + slider.setToolTipText(tip); + sub.add(slider,"w 75lp, wrap para"); + */ + + JButton button = new JButton("Reset to default"); + button.setToolTipText("Reset the time step to its default value (" + + UnitGroup.UNITS_SHORT_TIME.toStringUnit(RK4Simulator.RECOMMENDED_TIME_STEP) + + ")."); + +// button.setToolTipText("Reset the step value to its default:
" + +// "Time step " + +// UnitGroup.UNITS_SHORT_TIME.toStringUnit(RK4Simulator.RECOMMENDED_TIME_STEP) + +// "; maximum angle step " + +// UnitGroup.UNITS_ANGLE.toStringUnit(RK4Simulator.RECOMMENDED_ANGLE_STEP) + "."); + sub.add(button, "spanx, tag right, wrap para"); + + + + + //// Simulation listeners + sub = new JPanel(new MigLayout("fill, gap 0 0")); + sub.setBorder(BorderFactory.createTitledBorder("Simulator listeners")); + panel.add(sub, "growx, growy"); + + + DescriptionArea desc = new DescriptionArea(5, -1); + desc.setText("

" + + "Simulation listeners is an advanced feature that allows "+ + "user-written code to listen to and interact with the simulation. " + + "For details on writing simulation listeners, see the OpenRocket " + + "technical documentation.

"); + sub.add(desc, "aligny 0, growx, wrap para"); + + + label = new JLabel("Current listeners:"); + sub.add(label, "spanx, wrap rel"); + + final ListenerListModel listenerModel = new ListenerListModel(); + final JList list = new JList(listenerModel); + list.setCellRenderer(new ListenerCellRenderer()); + JScrollPane scroll = new JScrollPane(list); +// scroll.setPreferredSize(new Dimension(1,1)); + sub.add(scroll, "height 1px, grow, wrap rel"); + + + button = new JButton("Add"); + button.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + String previous = Prefs.NODE.get("previousListenerName", ""); + String input = (String)JOptionPane.showInputDialog(SimulationEditDialog.this, + new Object[] { + "Type the full Java class name of the simulation listener, for example:", + "" + CSVSaveListener.class.getName() + "" }, + "Add simulation listener", + JOptionPane.QUESTION_MESSAGE, + null, null, + previous + ); + if (input == null || input.equals("")) + return; + + Prefs.NODE.put("previousListenerName", input); + simulation.getSimulationListeners().add(input); + listenerModel.fireContentsChanged(); + } + }); + sub.add(button, "split 2, sizegroup buttons, alignx 50%, gapright para"); + + button = new JButton("Remove"); + button.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + int[] selected = list.getSelectedIndices(); + Arrays.sort(selected); + for (int i=selected.length-1; i>=0; i--) { + simulation.getSimulationListeners().remove(selected[i]); + } + listenerModel.fireContentsChanged(); + } + }); + sub.add(button, "sizegroup buttons, alignx 50%"); + + + return panel; + } + + + private class ListenerListModel extends AbstractListModel { + @Override + public String getElementAt(int index) { + if (index < 0 || index >= getSize()) + return null; + return simulation.getSimulationListeners().get(index); + } + @Override + public int getSize() { + return simulation.getSimulationListeners().size(); + } + public void fireContentsChanged() { + super.fireContentsChanged(this, 0, getSize()); + } + } + + + + + /** + * A panel for plotting the previously calculated data. + */ + private JPanel plotTab() { + + // Check that data exists + if (simulation.getSimulatedData() == null || + simulation.getSimulatedData().getBranchCount() == 0) { + return noDataPanel(); + } + + + if (true) + return new PlotPanel(simulation); + + JPanel panel = new JPanel(new MigLayout("fill")); + + + + + JButton button = new JButton("test"); + + button.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + PlotConfiguration config = new PlotConfiguration(); + config.addPlotDataType(FlightDataBranch.TYPE_ALTITUDE); + config.addPlotDataType(FlightDataBranch.TYPE_VELOCITY_Z); + config.addPlotDataType(FlightDataBranch.TYPE_ACCELERATION_Z); + config.addPlotDataType(FlightDataBranch.TYPE_ACCELERATION_TOTAL); + + config.setDomainAxisType(FlightDataBranch.TYPE_TIME); + + performPlot(config); + } + }); + panel.add(button); + + return panel; + } + + + + /** + * A panel for exporting the data. + */ + private JPanel exportTab() { + + // Check that data exists + if (simulation.getSimulatedData() == null || + simulation.getSimulatedData().getBranchCount() == 0) { + return noDataPanel(); + } + + + JPanel panel = new JPanel(new MigLayout("fill")); + + panel.add(new JLabel("Not implemented yet.")); // TODO: HIGH: Implement export + + return panel; + } + + + + + + /** + * Return a panel stating that there is no data available, and that the user + * should run the simulation first. + */ + private JPanel noDataPanel() { + JPanel panel = new JPanel(new MigLayout("fill")); + + // No data available + panel.add(new JLabel("No flight data available."), + "alignx 50%, aligny 100%, wrap para"); + panel.add(new JLabel("Please run the simulation first."), + "alignx 50%, aligny 0%, wrap"); + return panel; + } + + + private void performPlot(PlotConfiguration config) { + + // Fill the auto-selections + FlightDataBranch branch = simulation.getSimulatedData().getBranch(0); + PlotConfiguration filled = config.fillAutoAxes(branch); + List axes = filled.getAllAxes(); + + + // Create the data series for both axes + XYSeriesCollection[] data = new XYSeriesCollection[2]; + data[0] = new XYSeriesCollection(); + data[1] = new XYSeriesCollection(); + + + // Get the domain axis type + final FlightDataBranch.Type domainType = filled.getDomainAxisType(); + final Unit domainUnit = filled.getDomainAxisUnit(); + if (domainType == null) { + throw new IllegalArgumentException("Domain axis type not specified."); + } + List x = branch.get(domainType); + + + // Create the XYSeries objects from the flight data and store into the collections + int length = filled.getTypeCount(); + String[] axisLabel = new String[2]; + for (int i = 0; i < length; i++) { + // Get info + FlightDataBranch.Type type = filled.getType(i); + Unit unit = filled.getUnit(i); + int axis = filled.getAxis(i); + String name = getLabel(type, unit); + + // Store data in provided units + List y = branch.get(type); + XYSeries series = new XYSeries(name, false, true); + for (int j=0; j 0) { + // Create and set axis + double min = axes.get(i).getMinValue(); + double max = axes.get(i).getMaxValue(); + NumberAxis axis = new PresetNumberAxis(min, max); + axis.setLabel(axisLabel[i]); +// axis.setRange(axes.get(i).getMinValue(), axes.get(i).getMaxValue()); + plot.setRangeAxis(axisno, axis); + + // Add data and map to the axis + plot.setDataset(axisno, data[i]); + plot.setRenderer(axisno, new StandardXYItemRenderer()); + plot.mapDatasetToRangeAxis(axisno, axisno); + axisno++; + } + } + + plot.getDomainAxis().setLabel(getLabel(domainType,domainUnit)); + plot.addDomainMarker(new ValueMarker(0)); + plot.addRangeMarker(new ValueMarker(0)); + + + // Create the dialog + final JDialog dialog = new JDialog(this, "Simulation results"); + dialog.setModalityType(ModalityType.DOCUMENT_MODAL); + + JPanel panel = new JPanel(new MigLayout("fill")); + dialog.add(panel); + + ChartPanel chartPanel = new ChartPanel(chart, + false, // properties + true, // save + false, // print + true, // zoom + true); // tooltips + chartPanel.setMouseWheelEnabled(true); + chartPanel.setEnforceFileExtensions(true); + chartPanel.setInitialDelay(500); + + chartPanel.setBorder(BorderFactory.createLineBorder(Color.GRAY, 1)); + + panel.add(chartPanel, "grow, wrap 20lp"); + + JButton button = new JButton("Close"); + button.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + dialog.setVisible(false); + } + }); + panel.add(button, "right"); + + dialog.setLocationByPlatform(true); + dialog.pack(); + GUIUtil.installEscapeCloseOperation(dialog); + GUIUtil.setDefaultButton(button); + + dialog.setVisible(true); + } + + + private class PresetNumberAxis extends NumberAxis { + private final double min; + private final double max; + + public PresetNumberAxis(double min, double max) { + this.min = min; + this.max = max; + autoAdjustRange(); + } + + @Override + protected void autoAdjustRange() { + this.setRange(min, max); + } + } + + + private String getLabel(FlightDataBranch.Type type, Unit unit) { + String name = type.getName(); + if (unit != null && !UnitGroup.UNITS_NONE.contains(unit) && + !UnitGroup.UNITS_COEFFICIENT.contains(unit) && unit.getUnit().length() > 0) + name += " ("+unit.getUnit() + ")"; + return name; + } + + + + private class ListenerCellRenderer extends JLabel implements ListCellRenderer { + + public Component getListCellRendererComponent(JList list, Object value, + int index, boolean isSelected, boolean cellHasFocus) { + String s = value.toString(); + setText(s); + + // Attempt instantiating, catch any exceptions + Exception ex = null; + try { + Class c = Class.forName(s); + @SuppressWarnings("unused") + SimulationListener l = (SimulationListener)c.newInstance(); + } catch (Exception e) { + ex = e; + } + + if (ex == null) { + setIcon(Icons.SIMULATION_LISTENER_OK); + setToolTipText("Listener instantiated successfully."); + } else { + setIcon(Icons.SIMULATION_LISTENER_ERROR); + setToolTipText("Unable to instantiate listener due to exception:
" + + ex.toString()); + } + + if (isSelected) { + setBackground(list.getSelectionBackground()); + setForeground(list.getSelectionForeground()); + } else { + setBackground(list.getBackground()); + setForeground(list.getForeground()); + } + setOpaque(true); + return this; + } + } +} diff --git a/src/net/sf/openrocket/gui/main/SimulationPanel.java b/src/net/sf/openrocket/gui/main/SimulationPanel.java new file mode 100644 index 000000000..bb3cd3aa5 --- /dev/null +++ b/src/net/sf/openrocket/gui/main/SimulationPanel.java @@ -0,0 +1,532 @@ +package net.sf.openrocket.gui.main; + + +import java.awt.Color; +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.Arrays; + +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.SwingUtilities; +import javax.swing.table.DefaultTableCellRenderer; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.aerodynamics.Warning; +import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.document.OpenRocketDocument; +import net.sf.openrocket.document.Simulation; +import net.sf.openrocket.gui.ResizeLabel; +import net.sf.openrocket.gui.adaptors.Column; +import net.sf.openrocket.gui.adaptors.ColumnTableModel; +import net.sf.openrocket.rocketcomponent.ComponentChangeEvent; +import net.sf.openrocket.rocketcomponent.ComponentChangeListener; +import net.sf.openrocket.simulation.FlightData; +import net.sf.openrocket.unit.UnitGroup; +import net.sf.openrocket.util.Icons; +import net.sf.openrocket.util.Prefs; + +public class SimulationPanel extends JPanel { + + private static final Color WARNING_COLOR = Color.RED; + private static final String WARNING_TEXT = "\uFF01"; // Fullwidth exclamation mark + + private static final Color OK_COLOR = new Color(60,150,0); + private static final String OK_TEXT = "\u2714"; // Heavy check mark + + private static final String NAME_PREFIX = "Simulation "; + + + + private final OpenRocketDocument document; + + private final ColumnTableModel simulationTableModel; + private final JTable simulationTable; + + + public SimulationPanel(OpenRocketDocument doc) { + super(new MigLayout("fill","[grow][][][][][][grow]")); + + JButton button; + + + this.document = doc; + + + + //////// The simulation action buttons + + button = new JButton("New simulation"); + button.setToolTipText("Add a new simulation"); + button.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + + // Generate unique name for the simulation + int maxValue = 0; + for (Simulation s: document.getSimulations()) { + String name = s.getName(); + if (name.startsWith(NAME_PREFIX)) { + try { + maxValue = Math.max(maxValue, + Integer.parseInt(name.substring(NAME_PREFIX.length()))); + } catch (NumberFormatException ignore) { } + } + } + + Simulation sim = new Simulation(document.getRocket()); + sim.setName(NAME_PREFIX + (maxValue+1)); + + int n = document.getSimulationCount(); + document.addSimulation(sim); + simulationTableModel.fireTableDataChanged(); + simulationTable.clearSelection(); + simulationTable.addRowSelectionInterval(n, n); + + openDialog(sim, SimulationEditDialog.EDIT); + } + }); + this.add(button,"skip 1, gapright para"); + + button = new JButton("Edit simulation"); + button.setToolTipText("Edit the selected simulation"); + button.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + int selected = simulationTable.getSelectedRow(); + if (selected < 0) + return; // TODO: MEDIUM: "None selected" dialog + + selected = simulationTable.convertRowIndexToModel(selected); + simulationTable.clearSelection(); + simulationTable.addRowSelectionInterval(selected, selected); + + openDialog(document.getSimulations().get(selected), SimulationEditDialog.EDIT); + } + }); + this.add(button,"gapright para"); + + button = new JButton("Run simulations"); + button.setToolTipText("Re-run the selected simulations"); + button.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + int[] selection = simulationTable.getSelectedRows(); + if (selection.length == 0) + return; // TODO: LOW: "None selected" dialog + + Simulation[] sims = new Simulation[selection.length]; + for (int i=0; i < selection.length; i++) { + selection[i] = simulationTable.convertRowIndexToModel(selection[i]); + sims[i] = document.getSimulation(selection[i]); + } + + long t = System.currentTimeMillis(); + new SimulationRunDialog(SwingUtilities.getWindowAncestor( + SimulationPanel.this), sims).setVisible(true); + System.err.println("Running took "+(System.currentTimeMillis()-t) + " ms"); + fireMaintainSelection(); + } + }); + this.add(button,"gapright para"); + + button = new JButton("Delete simulations"); + button.setToolTipText("Delete the selected simulations"); + button.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + int[] selection = simulationTable.getSelectedRows(); + if (selection.length == 0) + return; // TODO: LOW: "None selected" dialog + + // Verify deletion + boolean verify = Prefs.NODE.getBoolean(Prefs.CONFIRM_DELETE_SIMULATION, true); + if (verify) { + + JPanel panel = new JPanel(new MigLayout()); + JCheckBox dontAsk = new JCheckBox("Do not ask me again"); + panel.add(dontAsk,"wrap"); + panel.add(new ResizeLabel("You can change the default operation in the " + + "preferences.",-2)); + + int ret = JOptionPane.showConfirmDialog(SimulationPanel.this, + new Object[] { + "Delete the selected simulations?", + "This operation cannot be undone.", + "", + panel }, + "Delete simulations", + JOptionPane.OK_CANCEL_OPTION, + JOptionPane.WARNING_MESSAGE); + if (ret != JOptionPane.OK_OPTION) + return; + + if (dontAsk.isSelected()) { + Prefs.NODE.putBoolean(Prefs.CONFIRM_DELETE_SIMULATION, false); + } + } + + // Delete simulations + for (int i=0; i < selection.length; i++) { + selection[i] = simulationTable.convertRowIndexToModel(selection[i]); + } + Arrays.sort(selection); + for (int i=selection.length-1; i>=0; i--) { + document.removeSimulation(selection[i]); + } + simulationTableModel.fireTableDataChanged(); + } + }); + this.add(button,"gapright para"); + + +// button = new JButton("Plot / export"); + button = new JButton("Plot flight"); + button.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + int selected = simulationTable.getSelectedRow(); + if (selected < 0) + return; // TODO: MEDIUM: "None selected" dialog + + selected = simulationTable.convertRowIndexToModel(selected); + simulationTable.clearSelection(); + simulationTable.addRowSelectionInterval(selected, selected); + + openDialog(document.getSimulations().get(selected), SimulationEditDialog.PLOT); + } + }); + this.add(button, "wrap para"); + + + + + //////// The simulation table + + simulationTableModel = new ColumnTableModel( + + //// Status and warning column + new Column("") { + private JLabel label = null; + @Override + public Object getValueAt(int row) { + if (row < 0 || row >= document.getSimulationCount()) + return null; + + // Initialize the label + if (label == null) { + label = new ResizeLabel(2f); + label.setIconTextGap(1); +// label.setFont(label.getFont().deriveFont(Font.BOLD)); + } + + // Set simulation status icon + Simulation.Status status = document.getSimulation(row).getStatus(); + label.setIcon(Icons.SIMULATION_STATUS_ICON_MAP.get(status)); + + + // Set warning marker + if (status == Simulation.Status.NOT_SIMULATED || + status == Simulation.Status.EXTERNAL) { + + label.setText(""); + + } else { + + WarningSet w = document.getSimulation(row).getSimulatedWarnings(); + if (w == null) { + label.setText(""); + } else if (w.isEmpty()) { + label.setForeground(OK_COLOR); + label.setText(OK_TEXT); + } else { + label.setForeground(WARNING_COLOR); + label.setText(WARNING_TEXT); + } + } + + return label; + } + @Override public int getExactWidth() { + return 32; + } + @Override public Class getColumnClass() { + return JLabel.class; + } + }, + + //// Simulation name + new Column("Name") { + @Override public Object getValueAt(int row) { + if (row < 0 || row >= document.getSimulationCount()) + return null; + return document.getSimulation(row).getName(); + } + @Override + public int getDefaultWidth() { + return 125; + } + }, + + //// Simulation motors + new Column("Motors") { + @Override public Object getValueAt(int row) { + if (row < 0 || row >= document.getSimulationCount()) + return null; + return document.getSimulation(row).getConfiguration() + .getMotorConfigurationDescription(); + } + @Override + public int getDefaultWidth() { + return 125; + } + }, + + //// Apogee + new Column("Apogee") { + @Override public Object getValueAt(int row) { + if (row < 0 || row >= document.getSimulationCount()) + return null; + + FlightData data = document.getSimulation(row).getSimulatedData(); + if (data==null) + return null; + + return UnitGroup.UNITS_DISTANCE.getDefaultUnit().toStringUnit( + data.getMaxAltitude()); + } + }, + + //// Maximum velocity + new Column("Max. velocity") { + @Override public Object getValueAt(int row) { + if (row < 0 || row >= document.getSimulationCount()) + return null; + + FlightData data = document.getSimulation(row).getSimulatedData(); + if (data==null) + return null; + + return UnitGroup.UNITS_VELOCITY.getDefaultUnit().toStringUnit( + data.getMaxVelocity()); + } + }, + + //// Maximum acceleration + new Column("Max. acceleration") { + @Override public Object getValueAt(int row) { + if (row < 0 || row >= document.getSimulationCount()) + return null; + + FlightData data = document.getSimulation(row).getSimulatedData(); + if (data==null) + return null; + + return UnitGroup.UNITS_ACCELERATION.getDefaultUnit().toStringUnit( + data.getMaxAcceleration()); + } + }, + + //// Time to apogee + new Column("Time to apogee") { + @Override public Object getValueAt(int row) { + if (row < 0 || row >= document.getSimulationCount()) + return null; + + FlightData data = document.getSimulation(row).getSimulatedData(); + if (data==null) + return null; + + return UnitGroup.UNITS_FLIGHT_TIME.getDefaultUnit().toStringUnit( + data.getTimeToApogee()); + } + }, + + //// Flight time + new Column("Flight time") { + @Override public Object getValueAt(int row) { + if (row < 0 || row >= document.getSimulationCount()) + return null; + + FlightData data = document.getSimulation(row).getSimulatedData(); + if (data==null) + return null; + + return UnitGroup.UNITS_FLIGHT_TIME.getDefaultUnit().toStringUnit( + data.getFlightTime()); + } + }, + + //// Ground hit velocity + new Column("Ground hit velocity") { + @Override public Object getValueAt(int row) { + if (row < 0 || row >= document.getSimulationCount()) + return null; + + FlightData data = document.getSimulation(row).getSimulatedData(); + if (data==null) + return null; + + return UnitGroup.UNITS_VELOCITY.getDefaultUnit().toStringUnit( + data.getGroundHitVelocity()); + } + } + + ) { + @Override + public int getRowCount() { + return document.getSimulationCount(); + } + }; + + simulationTable = new JTable(simulationTableModel); + simulationTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); + simulationTable.setDefaultRenderer(Object.class, new JLabelRenderer()); + simulationTableModel.setColumnWidths(simulationTable.getColumnModel()); + + // Mouse listener to act on double-clicks + simulationTable.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) { + int selected = simulationTable.getSelectedRow(); + if (selected < 0) + return; + + selected = simulationTable.convertRowIndexToModel(selected); + simulationTable.clearSelection(); + simulationTable.addRowSelectionInterval(selected, selected); + + openDialog(document.getSimulations().get(selected), + SimulationEditDialog.DEFAULT); + } + } + }); + + + + + // Fire table change event when the rocket changes + document.getRocket().addComponentChangeListener(new ComponentChangeListener() { + @Override + public void componentChanged(ComponentChangeEvent e) { + fireMaintainSelection(); + } + }); + + + JScrollPane scrollpane = new JScrollPane(simulationTable); + this.add(scrollpane,"spanx, grow, wrap rel"); + + + } + + + private void openDialog(final Simulation sim, int position) { + new SimulationEditDialog(SwingUtilities.getWindowAncestor(this), sim, position) + .setVisible(true); + fireMaintainSelection(); + } + + private void fireMaintainSelection() { + int[] selection = simulationTable.getSelectedRows(); + simulationTableModel.fireTableDataChanged(); + for (int row: selection) { + simulationTable.addRowSelectionInterval(row, row); + } + } + + + private class JLabelRenderer extends DefaultTableCellRenderer { + + @Override + public Component getTableCellRendererComponent(JTable table, + Object value, boolean isSelected, boolean hasFocus, int row, + int column) { + + if (row < 0 || row >= document.getSimulationCount()) + return super.getTableCellRendererComponent(table, value, + isSelected, hasFocus, row, column); + + // A JLabel is self-contained and has set its own tool tip + if (value instanceof JLabel) { + JLabel label = (JLabel)value; + if (isSelected) + label.setBackground(table.getSelectionBackground()); + else + label.setBackground(table.getBackground()); + label.setOpaque(true); + + label.setToolTipText(getSimulationToolTip(document.getSimulation(row))); + return label; + } + + Component component = super.getTableCellRendererComponent(table, value, + isSelected, hasFocus, row, column); + + if (component instanceof JComponent) { + ((JComponent)component).setToolTipText(getSimulationToolTip( + document.getSimulation(row))); + } + return component; + } + + private String getSimulationToolTip(Simulation sim) { + String tip; + FlightData data = sim.getSimulatedData(); + + tip = "" + sim.getName() + "
"; + switch (sim.getStatus()) { + case UPTODATE: + tip += "Up to date
"; + break; + + case LOADED: + tip += "Data loaded from a file
"; + break; + + case OUTDATED: + tip += "Data is out of date
"; + tip += "Click Run simulations to simulate.
"; + break; + + case EXTERNAL: + tip += "Imported data
"; + return tip; + + case NOT_SIMULATED: + tip += "Not simulated yet
"; + tip += "Click Run simulations to simulate."; + return tip; + } + + if (data == null) { + tip += "No simulation data available."; + return tip; + } + WarningSet warnings = data.getWarningSet(); + + if (warnings.isEmpty()) { + tip += "No warnings."; + return tip; + } + + tip += "Warnings:"; + for (Warning w: warnings) { + tip += "
" + w.toString(); + } + + return tip; + } + + } +} diff --git a/src/net/sf/openrocket/gui/main/SimulationRunDialog.java b/src/net/sf/openrocket/gui/main/SimulationRunDialog.java new file mode 100644 index 000000000..fa98140a6 --- /dev/null +++ b/src/net/sf/openrocket/gui/main/SimulationRunDialog.java @@ -0,0 +1,479 @@ +package net.sf.openrocket.gui.main; + + +import java.awt.Dialog; +import java.awt.Dimension; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.CharArrayWriter; +import java.io.PrintWriter; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JProgressBar; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.document.Simulation; +import net.sf.openrocket.gui.DetailDialog; +import net.sf.openrocket.rocketcomponent.Configuration; +import net.sf.openrocket.rocketcomponent.MotorMount; +import net.sf.openrocket.rocketcomponent.MotorMount.IgnitionEvent; +import net.sf.openrocket.simulation.FlightEvent; +import net.sf.openrocket.simulation.SimulationListener; +import net.sf.openrocket.simulation.SimulationStatus; +import net.sf.openrocket.simulation.exception.SimulationCancelledException; +import net.sf.openrocket.simulation.exception.SimulationException; +import net.sf.openrocket.simulation.exception.SimulationLaunchException; +import net.sf.openrocket.simulation.listeners.AbstractSimulationListener; +import net.sf.openrocket.unit.Unit; +import net.sf.openrocket.unit.UnitGroup; +import net.sf.openrocket.util.GUIUtil; +import net.sf.openrocket.util.MathUtil; +import net.sf.openrocket.util.Prefs; + + +public class SimulationRunDialog extends JDialog { + /** Update the dialog status every this many ms */ + private static final long UPDATE_MS = 200; + + /** Flight progress at motor burnout */ + private static final double BURNOUT_PROGRESS = 0.4; + + /** Flight progress at apogee */ + private static final double APOGEE_PROGRESS = 0.7; + + + /* + * The executor service is not static since we want concurrent simulation + * dialogs to run in parallel, ie. they both have their own executor service. + */ + private final ExecutorService executor = Executors.newFixedThreadPool( + Prefs.getMaxThreadCount()); + + + private final JLabel simLabel, timeLabel, altLabel, velLabel; + private final JProgressBar progressBar; + + + private final Simulation[] simulations; + private final SimulationWorker[] simulationWorkers; + private final SimulationStatus[] simulationStatuses; + private final double[] simulationMaxAltitude; + private final double[] simulationMaxVelocity; + private final boolean[] simulationDone; + + public SimulationRunDialog(Window window, Simulation ... simulations) { + super(window, "Running simulations...", Dialog.ModalityType.DOCUMENT_MODAL); + + if (simulations.length == 0) { + throw new IllegalArgumentException("Called with no simulations to run"); + } + + this.simulations = simulations; + + // Initialize the simulations + int n = simulations.length; + simulationWorkers = new SimulationWorker[n]; + simulationStatuses = new SimulationStatus[n]; + simulationMaxAltitude = new double[n]; + simulationMaxVelocity = new double[n]; + simulationDone = new boolean[n]; + + for (int i=0; i= simulations.length) { + // Everything is done, close the dialog + System.out.println("Everything done."); + this.dispose(); + return; + } + + // Update the progress bar status + int progress = 0; + for (SimulationWorker s: simulationWorkers) { + progress += s.getProgress(); + } + progress /= simulationWorkers.length; + progressBar.setValue(progress); + System.out.println("Progressbar value "+progress); + + // Update the simulation fields + simLabel.setText("Running " + simulations[index].getName()); + if (simulationStatuses[index] == null) { + timeLabel.setText(""); + altLabel.setText(""); + velLabel.setText(""); + System.out.println("Empty labels, how sad."); + return; + } + + Unit u = UnitGroup.UNITS_FLIGHT_TIME.getDefaultUnit(); + timeLabel.setText(u.toStringUnit(simulationStatuses[index].time)); + + u = UnitGroup.UNITS_DISTANCE.getDefaultUnit(); + altLabel.setText(u.toStringUnit(simulationStatuses[index].position.z) + " (max. " + + u.toStringUnit(simulationMaxAltitude[index]) + ")"); + + u = UnitGroup.UNITS_VELOCITY.getDefaultUnit(); + velLabel.setText(u.toStringUnit(simulationStatuses[index].velocity.z) + " (max. " + + u.toStringUnit(simulationMaxVelocity[index]) + ")"); + System.out.println("Set interesting labels."); + } + + + + /** + * A SwingWorker that performs a flight simulation. It periodically updates the + * simulation statuses of the parent class and calls updateProgress(). + * The progress of the simulation is stored in the progress property of the + * SwingWorker. + * + * @author Sampo Niskanen + */ + private class InteractiveSimulationWorker extends SimulationWorker { + + private final int index; + private final double burnoutTimeEstimate; + private volatile double burnoutVelocity; + private volatile double apogeeAltitude; + + /* + * -2 = time from 0 ... burnoutTimeEstimate + * -1 = velocity from v(burnoutTimeEstimate) ... 0 + * 0 ... n = stages from alt(max) ... 0 + */ + private volatile int simulationStage = -2; + + private int progress = 0; + + + public InteractiveSimulationWorker(Simulation sim, int index) { + super(sim); + this.index = index; + + // Calculate estimate of motor burn time + double launchBurn = 0; + double otherBurn = 0; + Configuration config = simulation.getConfiguration(); + String id = simulation.getConditions().getMotorConfigurationID(); + Iterator iterator = config.motorIterator(); + while (iterator.hasNext()) { + MotorMount m = iterator.next(); + if (m.getIgnitionEvent() == IgnitionEvent.LAUNCH) + launchBurn = MathUtil.max(launchBurn, m.getMotor(id).getTotalTime()); + else + otherBurn = otherBurn + m.getMotor(id).getTotalTime(); + } + burnoutTimeEstimate = Math.max(launchBurn + otherBurn, 0.1); + + } + + + /** + * Return the extra listeners to use, a progress listener and cancel listener. + */ + @Override + protected SimulationListener[] getExtraListeners() { + return new SimulationListener[] { + new SimulationProgressListener() + }; + } + + + /** + * Processes simulation statuses published by the simulation listener. + * The statuses of the parent class and the progress property are updated. + */ + @Override + protected void process(List chunks) { + + // Update max. altitude and velocity + for (SimulationStatus s: chunks) { + simulationMaxAltitude[index] = Math.max(simulationMaxAltitude[index], + s.position.z); + simulationMaxVelocity[index] = Math.max(simulationMaxVelocity[index], + s.velocity.length()); + } + + // Calculate the progress + SimulationStatus status = chunks.get(chunks.size()-1); + simulationStatuses[index] = status; + + // 1. time = 0 ... burnoutTimeEstimate + if (simulationStage == -2 && status.time < burnoutTimeEstimate) { + System.out.println("Method 1: t="+status.time + " est="+burnoutTimeEstimate); + setSimulationProgress(MathUtil.map(status.time, 0, burnoutTimeEstimate, + 0.0, BURNOUT_PROGRESS)); + updateProgress(); + return; + } + + if (simulationStage == -2) { + simulationStage++; + burnoutVelocity = MathUtil.max(status.velocity.z, 0.1); + System.out.println("CHANGING to Method 2, vel="+burnoutVelocity); + } + + // 2. z-velocity from burnout velocity to zero + if (simulationStage == -1 && status.velocity.z >= 0) { + System.out.println("Method 2: vel="+status.velocity.z + " burnout=" + + burnoutVelocity); + setSimulationProgress(MathUtil.map(status.velocity.z, burnoutVelocity, 0, + BURNOUT_PROGRESS, APOGEE_PROGRESS)); + updateProgress(); + return; + } + + if (simulationStage == -1 && status.velocity.z < 0) { + simulationStage++; + apogeeAltitude = status.position.z; + } + + // 3. z-position from apogee to zero + // TODO: MEDIUM: several stages + System.out.println("Method 3: alt="+status.position.z +" apogee="+apogeeAltitude); + setSimulationProgress(MathUtil.map(status.position.z, + apogeeAltitude, 0, APOGEE_PROGRESS, 1.0)); + updateProgress(); + } + + /** + * Marks this simulation as done and calls the progress update. + */ + @Override + protected void simulationDone() { + simulationDone[index] = true; + System.out.println("DONE, setting progress"); + setSimulationProgress(1.0); + updateProgress(); + } + + + /** + * Marks the simulation as done and shows a dialog presenting + * the error, unless the simulation was cancelled. + */ + @Override + protected void simulationInterrupted(Throwable t) { + + if (t instanceof SimulationCancelledException) { + simulationDone(); + return; // Ignore cancellations + } + + // Retrieve the stack trace in a textual form + CharArrayWriter arrayWriter = new CharArrayWriter(); + arrayWriter.append(t.toString() + "\n" + "\n"); + t.printStackTrace(new PrintWriter(arrayWriter)); + String stackTrace = arrayWriter.toString(); + + // Analyze the exception type + if (t instanceof SimulationLaunchException) { + + DetailDialog.showDetailedMessageDialog(SimulationRunDialog.this, + new Object[] { + "Unable to simulate:", + t.getMessage() + }, + null, simulation.getName(), JOptionPane.ERROR_MESSAGE); + + } else if (t instanceof SimulationException) { + + DetailDialog.showDetailedMessageDialog(SimulationRunDialog.this, + new Object[] { + "A error occurred during the simulation:", + t.getMessage() + }, + stackTrace, simulation.getName(), JOptionPane.ERROR_MESSAGE); + + } else if (t instanceof Exception) { + + DetailDialog.showDetailedMessageDialog(SimulationRunDialog.this, + new Object[] { + "An exception occurred during the simulation:", + t.getMessage(), + simulation.getSimulationListeners().isEmpty() ? + "Please report this as a bug along with the details below." : "" + }, + stackTrace, simulation.getName(), JOptionPane.ERROR_MESSAGE); + + } else if (t instanceof AssertionError) { + + DetailDialog.showDetailedMessageDialog(SimulationRunDialog.this, + new Object[] { + "A computation error occurred during the simulation.", + "Please report this as a bug along with the details below." + }, + stackTrace, simulation.getName(), JOptionPane.ERROR_MESSAGE); + + } else { + + // Probably an Error + DetailDialog.showDetailedMessageDialog(SimulationRunDialog.this, + new Object[] { + "An unknown error was encountered during the simulation.", + "The program may be unstable, you should save all your designs " + + "and restart OpenRocket now!" + }, + stackTrace, simulation.getName(), JOptionPane.ERROR_MESSAGE); + + } + simulationDone(); + } + + + + private void setSimulationProgress(double p) { + progress = Math.max(progress, (int)(100*p+0.5)); + progress = MathUtil.clamp(progress, 0, 100); + System.out.println("Setting progress to "+progress+ " (real " + + ((int)(100*p+0.5)) + ")"); + super.setProgress(progress); + } + + + /** + * A simulation listener that regularly updates the progress property of the + * SimulationWorker and publishes the simulation status for the run dialog to process. + * + * @author Sampo Niskanen + */ + private class SimulationProgressListener extends AbstractSimulationListener { + private long time = 0; + + @Override + public Collection handleEvent(FlightEvent event, + SimulationStatus status) { + + switch (event.getType()) { + case APOGEE: + simulationStage = 0; + apogeeAltitude = status.position.z; + System.out.println("APOGEE, setting progress"); + setSimulationProgress(APOGEE_PROGRESS); + publish(status); + break; + + case LAUNCH: + publish(status); + break; + + case SIMULATION_END: + System.out.println("END, setting progress"); + setSimulationProgress(1.0); + break; + } + return null; + } + + @Override + public Collection stepTaken(SimulationStatus status) { + if (System.currentTimeMillis() >= time + UPDATE_MS) { + time = System.currentTimeMillis(); + publish(status); + } + return null; + } + } + } +} diff --git a/src/net/sf/openrocket/gui/main/SimulationWorker.java b/src/net/sf/openrocket/gui/main/SimulationWorker.java new file mode 100644 index 000000000..e278da523 --- /dev/null +++ b/src/net/sf/openrocket/gui/main/SimulationWorker.java @@ -0,0 +1,129 @@ +package net.sf.openrocket.gui.main; + +import java.util.Arrays; +import java.util.Collection; + +import javax.swing.SwingWorker; + +import net.sf.openrocket.document.Simulation; +import net.sf.openrocket.simulation.FlightData; +import net.sf.openrocket.simulation.FlightEvent; +import net.sf.openrocket.simulation.SimulationListener; +import net.sf.openrocket.simulation.SimulationStatus; +import net.sf.openrocket.simulation.exception.SimulationCancelledException; +import net.sf.openrocket.simulation.listeners.AbstractSimulationListener; + + + +/** + * A SwingWorker that runs a simulation in a background thread. The simulation + * always includes a listener that checks whether this SwingWorked has been cancelled, + * and throws a {@link SimulationCancelledException} if it has. This allows the + * {@link #cancel(boolean)} method to be used to cancel the simulation. + * + * @author Sampo Niskanen + */ +public abstract class SimulationWorker extends SwingWorker { + + protected final Simulation simulation; + private Throwable throwable = null; + + public SimulationWorker(Simulation sim) { + this.simulation = sim; + } + + + /** + * Runs the simulation. + */ + @Override + protected FlightData doInBackground() { + if (isCancelled()) { + throwable = new SimulationCancelledException("The simulation was interrupted."); + return null; + } + + SimulationListener[] listeners = getExtraListeners(); + + if (listeners != null) { + listeners = Arrays.copyOf(listeners, listeners.length+1); + } else { + listeners = new SimulationListener[1]; + } + + listeners[listeners.length-1] = new CancelListener(); + + try { + simulation.simulate(listeners); + } catch (Throwable e) { +// System.out.println("Simulation interrupted:"); +// e.printStackTrace(); + throwable = e; + return null; + } + return simulation.getSimulatedData(); + } + + + /** + * Return additional listeners to use during the simulation. The default + * implementation returns an empty array. + * + * @return additional listeners to use, or null. + */ + protected SimulationListener[] getExtraListeners() { + return new SimulationListener[0]; + } + + + /** + * Called after a simulation is successfully simulated. This method is not + * called if the simulation ends in an exception. + * + * @param sim the simulation including the flight data + */ + protected abstract void simulationDone(); + + /** + * Called if the simulation is interrupted due to an exception. + * + * @param t the Throwable that caused the interruption + */ + protected abstract void simulationInterrupted(Throwable t); + + + + /** + * Marks this simulation as done and calls the progress update. + */ + @Override + protected final void done() { + if (throwable == null) + simulationDone(); + else + simulationInterrupted(throwable); + } + + + + /** + * A simulation listener that throws a {@link SimulationCancelledException} if + * this SwingWorker has been cancelled. The conditions is checked every time a step + * is taken. + * + * @author Sampo Niskanen + */ + private class CancelListener extends AbstractSimulationListener { + + @Override + public Collection stepTaken(SimulationStatus status) + throws SimulationCancelledException { + + if (isCancelled()) { + throw new SimulationCancelledException("The simulation was interrupted."); + } + + return null; + } + } +} diff --git a/src/net/sf/openrocket/gui/plot/Axis.java b/src/net/sf/openrocket/gui/plot/Axis.java new file mode 100644 index 000000000..98f8a41f1 --- /dev/null +++ b/src/net/sf/openrocket/gui/plot/Axis.java @@ -0,0 +1,52 @@ +package net.sf.openrocket.gui.plot; + +public class Axis implements Cloneable { + + private double minValue = Double.NaN; + private double maxValue = Double.NaN; + + + + public void addBound(double value) { + + if (value < minValue || Double.isNaN(minValue)) { + minValue = value; + } + if (value > maxValue || Double.isNaN(maxValue)) { + maxValue = value; + } + + } + + + public double getMinValue() { + return minValue; + } + + public double getMaxValue() { + return maxValue; + } + + public double getRangeLength() { + return maxValue - minValue; + } + + public void reset() { + minValue = Double.NaN; + maxValue = Double.NaN; + } + + + + @Override + public Axis clone() { + try { + + return (Axis) super.clone(); + + } catch (CloneNotSupportedException e) { + throw new RuntimeException("BUG! Could not clone()."); + } + } + +} diff --git a/src/net/sf/openrocket/gui/plot/PlotConfiguration.java b/src/net/sf/openrocket/gui/plot/PlotConfiguration.java new file mode 100644 index 000000000..a22daf4b1 --- /dev/null +++ b/src/net/sf/openrocket/gui/plot/PlotConfiguration.java @@ -0,0 +1,665 @@ +package net.sf.openrocket.gui.plot; + +import java.util.ArrayList; +import java.util.List; + +import net.sf.openrocket.simulation.FlightDataBranch; +import net.sf.openrocket.simulation.FlightDataBranch.Type; +import net.sf.openrocket.unit.Unit; +import net.sf.openrocket.util.MathUtil; +import net.sf.openrocket.util.Pair; + + +public class PlotConfiguration implements Cloneable { + + public static final PlotConfiguration[] DEFAULT_CONFIGURATIONS; + static { + ArrayList configs = new ArrayList(); + PlotConfiguration config; + + config = new PlotConfiguration("Vertical motion vs. time"); + config.addPlotDataType(FlightDataBranch.TYPE_ALTITUDE, 0); + config.addPlotDataType(FlightDataBranch.TYPE_VELOCITY_Z); + config.addPlotDataType(FlightDataBranch.TYPE_ACCELERATION_Z); + configs.add(config); + + config = new PlotConfiguration("Total motion vs. time"); + config.addPlotDataType(FlightDataBranch.TYPE_ALTITUDE, 0); + config.addPlotDataType(FlightDataBranch.TYPE_VELOCITY_TOTAL); + config.addPlotDataType(FlightDataBranch.TYPE_ACCELERATION_TOTAL); + configs.add(config); + + config = new PlotConfiguration("Flight side profile", FlightDataBranch.TYPE_POSITION_X); + config.addPlotDataType(FlightDataBranch.TYPE_ALTITUDE); + configs.add(config); + + config = new PlotConfiguration("Stability vs. time"); + config.addPlotDataType(FlightDataBranch.TYPE_STABILITY, 0); + config.addPlotDataType(FlightDataBranch.TYPE_CP_LOCATION, 1); + config.addPlotDataType(FlightDataBranch.TYPE_CG_LOCATION, 1); + configs.add(config); + + config = new PlotConfiguration("Drag coefficients vs. Mach number", + FlightDataBranch.TYPE_MACH_NUMBER); + config.addPlotDataType(FlightDataBranch.TYPE_DRAG_COEFF, 0); + config.addPlotDataType(FlightDataBranch.TYPE_FRICTION_DRAG_COEFF, 0); + config.addPlotDataType(FlightDataBranch.TYPE_BASE_DRAG_COEFF, 0); + config.addPlotDataType(FlightDataBranch.TYPE_PRESSURE_DRAG_COEFF, 0); + configs.add(config); + + config = new PlotConfiguration("Roll characteristics"); + config.addPlotDataType(FlightDataBranch.TYPE_ROLL_RATE, 0); + config.addPlotDataType(FlightDataBranch.TYPE_ROLL_MOMENT_COEFF, 1); + config.addPlotDataType(FlightDataBranch.TYPE_ROLL_FORCING_COEFF, 1); + config.addPlotDataType(FlightDataBranch.TYPE_ROLL_DAMPING_COEFF, 1); + configs.add(config); + + config = new PlotConfiguration("Simulation time step and computation time"); + config.addPlotDataType(FlightDataBranch.TYPE_TIME_STEP); + config.addPlotDataType(FlightDataBranch.TYPE_COMPUTATION_TIME); + configs.add(config); + + DEFAULT_CONFIGURATIONS = configs.toArray(new PlotConfiguration[0]); + } + + + + /** Bonus given for the first type being on the first axis */ + private static final double BONUS_FIRST_TYPE_ON_FIRST_AXIS = 1.0; + + /** + * Bonus given if the first axis includes zero (to prefer first axis having zero over + * the others) + */ + private static final double BONUS_FIRST_AXIS_HAS_ZERO = 2.0; + + /** Bonus given for a common zero point on left and right axes. */ + private static final double BONUS_COMMON_ZERO = 40.0; + + /** Bonus given for only using a single axis. */ + private static final double BONUS_ONLY_ONE_AXIS = 50.0; + + + private static final double INCLUDE_ZERO_DISTANCE = 0.3; // 30% of total range + + + + /** The data types to be plotted. */ + private ArrayList plotDataTypes = new ArrayList(); + + private ArrayList plotDataUnits = new ArrayList(); + + /** The corresponding Axis on which they will be plotted, or null to auto-select. */ + private ArrayList plotDataAxes = new ArrayList(); + + + /** The domain (x) axis. */ + private FlightDataBranch.Type domainAxisType = null; + private Unit domainAxisUnit = null; + + + /** All available axes. */ + private final int axesCount; + private ArrayList allAxes = new ArrayList(); + + + + private String name = null; + + + + public PlotConfiguration() { + this(null, FlightDataBranch.TYPE_TIME); + } + + public PlotConfiguration(String name) { + this(name, FlightDataBranch.TYPE_TIME); + } + + public PlotConfiguration(String name, FlightDataBranch.Type domainType) { + this.name = name; + // Two axes + allAxes.add(new Axis()); + allAxes.add(new Axis()); + axesCount = 2; + + setDomainAxisType(domainType); + } + + + + + + public FlightDataBranch.Type getDomainAxisType() { + return domainAxisType; + } + + public void setDomainAxisType(FlightDataBranch.Type type) { + boolean setUnit; + + if (domainAxisType != null && domainAxisType.getUnitGroup() == type.getUnitGroup()) + setUnit = false; + else + setUnit = true; + + domainAxisType = type; + if (setUnit) + domainAxisUnit = domainAxisType.getUnitGroup().getDefaultUnit(); + } + + public Unit getDomainAxisUnit() { + return domainAxisUnit; + } + + public void setDomainAxisUnit(Unit u) { + if (!domainAxisType.getUnitGroup().contains(u)) { + throw new IllegalArgumentException("Setting unit "+u+" to type "+domainAxisType); + } + domainAxisUnit = u; + } + + + + public void addPlotDataType(FlightDataBranch.Type type) { + plotDataTypes.add(type); + plotDataUnits.add(type.getUnitGroup().getDefaultUnit()); + plotDataAxes.add(-1); + } + + public void addPlotDataType(FlightDataBranch.Type type, int axis) { + if (axis >= axesCount) { + throw new IllegalArgumentException("Axis index too large"); + } + plotDataTypes.add(type); + plotDataUnits.add(type.getUnitGroup().getDefaultUnit()); + plotDataAxes.add(axis); + } + + + + + public void setPlotDataType(int index, FlightDataBranch.Type type) { + FlightDataBranch.Type origType = plotDataTypes.get(index); + plotDataTypes.set(index, type); + + if (origType.getUnitGroup() != type.getUnitGroup()) { + plotDataUnits.set(index, type.getUnitGroup().getDefaultUnit()); + } + } + + public void setPlotDataUnit(int index, Unit unit) { + if (!plotDataTypes.get(index).getUnitGroup().contains(unit)) { + throw new IllegalArgumentException("Attempting to set unit "+unit+" to group " + + plotDataTypes.get(index).getUnitGroup()); + } + plotDataUnits.set(index, unit); + } + + public void setPlotDataAxis(int index, int axis) { + if (axis >= axesCount) { + throw new IllegalArgumentException("Axis index too large"); + } + plotDataAxes.set(index, axis); + } + + + public void setPlotDataType(int index, FlightDataBranch.Type type, Unit unit, int axis) { + if (axis >= axesCount) { + throw new IllegalArgumentException("Axis index too large"); + } + plotDataTypes.set(index, type); + plotDataUnits.set(index, unit); + plotDataAxes.set(index, axis); + } + + public void removePlotDataType(int index) { + plotDataTypes.remove(index); + plotDataUnits.remove(index); + plotDataAxes.remove(index); + } + + + + public FlightDataBranch.Type getType (int index) { + return plotDataTypes.get(index); + } + public Unit getUnit(int index) { + return plotDataUnits.get(index); + } + public int getAxis(int index) { + return plotDataAxes.get(index); + } + + public int getTypeCount() { + return plotDataTypes.size(); + } + + + + public List getAllAxes() { + List list = new ArrayList(); + list.addAll(allAxes); + return list; + } + + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + /** + * Returns the name of this PlotConfiguration. + */ + @Override + public String toString() { + return name; + } + + + + /** + * Find the best combination of the auto-selectable axes. + * + * @return a new PlotConfiguration with the best fitting auto-selected axes and + * axes ranges selected. + */ + public PlotConfiguration fillAutoAxes(FlightDataBranch data) { + PlotConfiguration config = recursiveFillAutoAxes(data).getU(); + System.out.println("BEST FOUND, fitting"); + config.fitAxes(data); + return config; + } + + + + + /** + * Recursively search for the best combination of the auto-selectable axes. + * This is a brute-force search method. + * + * @return a new PlotConfiguration with the best fitting auto-selected axes and + * axes ranges selected, and the goodness value + */ + private Pair recursiveFillAutoAxes(FlightDataBranch data) { + + // Create copy to fill in + PlotConfiguration copy = this.clone(); + + int autoindex; + for (autoindex=0; autoindex < plotDataAxes.size(); autoindex++) { + if (plotDataAxes.get(autoindex) < 0) + break; + } + + + if (autoindex >= plotDataAxes.size()) { + // All axes have been assigned, just return since we are already the best + return new Pair(copy, copy.getGoodnessValue(data)); + } + + + // Set the auto-selected index one at a time and choose the best one + PlotConfiguration best = null; + double bestValue = Double.NEGATIVE_INFINITY; + for (int i=0; i < axesCount; i++) { + copy.plotDataAxes.set(autoindex, i); + Pair result = copy.recursiveFillAutoAxes(data); + if (result.getV() > bestValue) { + best = result.getU(); + bestValue = result.getV(); + } + } + + return new Pair(best, bestValue); + } + + + + + + /** + * Fit the axes to hold the provided data. All of the plotDataAxis elements must + * be non-negative. + *

+ * NOTE: This method assumes that only two axes are used. + */ + protected void fitAxes(FlightDataBranch data) { + + // Reset axes + for (Axis a: allAxes) { + a.reset(); + } + + // Add full range to the axes + int length = plotDataTypes.size(); + for (int i=0; i 0 || left.getMaxValue() < 0 || + right.getMinValue() > 0 || right.getMaxValue() < 0 || + Double.isNaN(left.getMinValue()) || Double.isNaN(right.getMinValue())) + return; + + + + //// Compute common zero + // TODO: MEDIUM: This algorithm may require tweaking + + double min1 = left.getMinValue(); + double max1 = left.getMaxValue(); + double min2 = right.getMinValue(); + double max2 = right.getMaxValue(); + + // Calculate and round scaling factor + double scale = Math.max(left.getRangeLength(), right.getRangeLength()) / + Math.min(left.getRangeLength(), right.getRangeLength()); + + System.out.println("Scale: "+scale); + + scale = roundScale(scale); + if (right.getRangeLength() > left.getRangeLength()) { + scale = 1/scale; + } + System.out.println("Rounded scale: " + scale); + + // Scale right axis, enlarge axes if necessary and scale back + min2 *= scale; + max2 *= scale; + min1 = Math.min(min1, min2); + min2 = min1; + max1 = Math.max(max1, max2); + max2 = max1; + min2 /= scale; + max2 /= scale; + + + + // Scale to unit length +// double scale1 = left.getRangeLength(); +// double scale2 = right.getRangeLength(); +// +// double min1 = left.getMinValue() / scale1; +// double max1 = left.getMaxValue() / scale1; +// double min2 = right.getMinValue() / scale2; +// double max2 = right.getMaxValue() / scale2; +// +// // Combine unit ranges +// min1 = MathUtil.min(min1, min2); +// min2 = min1; +// max1 = MathUtil.max(max1, max2); +// max2 = max1; +// +// // Scale up +// min1 *= scale1; +// max1 *= scale1; +// min2 *= scale2; +// max2 *= scale2; +// +// // Compute common scale +// double range1 = max1-min1; +// double range2 = max2-min2; +// +// double scale = MathUtil.max(range1, range2) / MathUtil.min(range1, range2); +// double roundScale = roundScale(scale); +// +// if (range2 < range1) { +// if (roundScale < scale) { +// min2 = min1 / roundScale; +// max2 = max1 / roundScale; +// } else { +// min1 = min2 * roundScale; +// max1 = max2 * roundScale; +// } +// } else { +// if (roundScale > scale) { +// min2 = min1 * roundScale; +// max2 = max1 * roundScale; +// } else { +// min1 = min2 / roundScale; +// max1 = max2 / roundScale; +// } +// } + + // Apply scale + left.addBound(min1); + left.addBound(max1); + right.addBound(min2); + right.addBound(max2); + + } + + + + private double roundScale(double scale) { + double mul = 1; + while (scale >= 10) { + scale /= 10; + mul *= 10; + } + while (scale < 1) { + scale *= 10; + mul /= 10; + } + + // 1 2 4 5 10 + + if (scale > 7.5) { + scale = 10; + } else if (scale > 4.5) { + scale = 5; + } else if (scale > 3) { + scale = 4; + } else if (scale > 1.5) { + scale = 2; + } else { + scale = 1; + } + return scale*mul; + } + + + + private double roundScaleUp(double scale) { + double mul = 1; + while (scale >= 10) { + scale /= 10; + mul *= 10; + } + while (scale < 1) { + scale *= 10; + mul /= 10; + } + + if (scale > 5) { + scale = 10; + } else if (scale > 4) { + scale = 5; + } else if (scale > 2) { + scale = 4; + } else if (scale > 1) { + scale = 2; + } else { + scale = 1; + } + return scale*mul; + } + + + private double roundScaleDown(double scale) { + double mul = 1; + while (scale >= 10) { + scale /= 10; + mul *= 10; + } + while (scale < 1) { + scale *= 10; + mul /= 10; + } + + if (scale > 5) { + scale = 5; + } else if (scale > 4) { + scale = 4; + } else if (scale > 2) { + scale = 2; + } else { + scale = 1; + } + return scale*mul; + } + + + + /** + * Fits the axis ranges to the data and returns the "goodness value" of this + * selection of axes. All plotDataAxis elements must be non-null. + *

+ * NOTE: This method assumes that all data can fit into the axes ranges and + * that only two axes are used. + * + * @return a "goodness value", the larger the better. + */ + protected double getGoodnessValue(FlightDataBranch data) { + double goodness = 0; + int length = plotDataTypes.size(); + + // Fit the axes ranges to the data + fitAxes(data); + + /* + * Calculate goodness of ranges. 100 points is given if the values fill the + * entire range, 0 if they fill none of it. + */ + for (int i = 0; i < length; i++) { + FlightDataBranch.Type type = plotDataTypes.get(i); + Unit unit = plotDataUnits.get(i); + int index = plotDataAxes.get(i); + if (index < 0) { + throw new IllegalStateException("getGoodnessValue called with auto-selected axis"); + } + Axis axis = allAxes.get(index); + + double min = unit.toUnit(data.getMinimum(type)); + double max = unit.toUnit(data.getMaximum(type)); + if (Double.isNaN(min) || Double.isNaN(max)) + continue; + if (MathUtil.equals(min, max)) + continue; + + double d = (max-min) / axis.getRangeLength(); + d = Math.sqrt(d); // Prioritize small ranges + goodness += d * 100.0; + } + + + /* + * Add extra points for specific things. + */ + + // A little for the first type being on the first axis + if (plotDataAxes.get(0) == 0) + goodness += BONUS_FIRST_TYPE_ON_FIRST_AXIS; + + // A little bonus if the first axis contains zero + Axis left = allAxes.get(0); + if (left.getMinValue() <= 0 && left.getMaxValue() >= 0) + goodness += BONUS_FIRST_AXIS_HAS_ZERO; + + // A boost if a common zero was used in the ranging + Axis right = allAxes.get(1); + if (left.getMinValue() <= 0 && left.getMaxValue() >= 0 && + right.getMinValue() <= 0 && right.getMaxValue() >= 0) + goodness += BONUS_COMMON_ZERO; + + // A boost if only one axis is used + if (Double.isNaN(left.getMinValue()) || Double.isNaN(right.getMinValue())) + goodness += BONUS_ONLY_ONE_AXIS; + + return goodness; + } + + + + /** + * Reset the units of this configuration to the default units. Returns this + * PlotConfiguration. + * + * @return this PlotConfiguration. + */ + public PlotConfiguration resetUnits() { + for (int i=0; i < plotDataTypes.size(); i++) { + plotDataUnits.set(i, plotDataTypes.get(i).getUnitGroup().getDefaultUnit()); + } + return this; + } + + + + + @SuppressWarnings("unchecked") + @Override + public PlotConfiguration clone() { + try { + + PlotConfiguration copy = (PlotConfiguration) super.clone(); + + // Shallow-clone all immutable lists + copy.plotDataTypes = (ArrayList) this.plotDataTypes.clone(); + copy.plotDataAxes = (ArrayList) this.plotDataAxes.clone(); + copy.plotDataUnits = (ArrayList) this.plotDataUnits.clone(); + + // Deep-clone all Axis since they are mutable + copy.allAxes = new ArrayList(); + for (Axis a: this.allAxes) { + copy.allAxes.add(a.clone()); + } + + return copy; + + + } catch (CloneNotSupportedException e) { + throw new RuntimeException("BUG! Could not clone()."); + } + } + +} diff --git a/src/net/sf/openrocket/gui/plot/PlotDialog.java b/src/net/sf/openrocket/gui/plot/PlotDialog.java new file mode 100644 index 000000000..e08525d64 --- /dev/null +++ b/src/net/sf/openrocket/gui/plot/PlotDialog.java @@ -0,0 +1,201 @@ +package net.sf.openrocket.gui.plot; + +import java.awt.Color; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.List; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JPanel; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.document.Simulation; +import net.sf.openrocket.simulation.FlightDataBranch; +import net.sf.openrocket.unit.Unit; +import net.sf.openrocket.unit.UnitGroup; +import net.sf.openrocket.util.GUIUtil; + +import org.jfree.chart.ChartFactory; +import org.jfree.chart.ChartPanel; +import org.jfree.chart.JFreeChart; +import org.jfree.chart.axis.NumberAxis; +import org.jfree.chart.plot.PlotOrientation; +import org.jfree.chart.plot.ValueMarker; +import org.jfree.chart.plot.XYPlot; +import org.jfree.chart.renderer.xy.StandardXYItemRenderer; +import org.jfree.chart.title.TextTitle; +import org.jfree.data.xy.XYSeries; +import org.jfree.data.xy.XYSeriesCollection; + +public class PlotDialog extends JDialog { + + private PlotDialog(Window parent, Simulation simulation, PlotConfiguration config) { + super(parent, "Flight data plot"); + this.setModalityType(ModalityType.DOCUMENT_MODAL); + + + // Fill the auto-selections + FlightDataBranch branch = simulation.getSimulatedData().getBranch(0); + PlotConfiguration filled = config.fillAutoAxes(branch); + List axes = filled.getAllAxes(); + + + // Create the data series for both axes + XYSeriesCollection[] data = new XYSeriesCollection[2]; + data[0] = new XYSeriesCollection(); + data[1] = new XYSeriesCollection(); + + + // Get the domain axis type + final FlightDataBranch.Type domainType = filled.getDomainAxisType(); + final Unit domainUnit = filled.getDomainAxisUnit(); + if (domainType == null) { + throw new IllegalArgumentException("Domain axis type not specified."); + } + List x = branch.get(domainType); + + + // Create the XYSeries objects from the flight data and store into the collections + int length = filled.getTypeCount(); + String[] axisLabel = new String[2]; + for (int i = 0; i < length; i++) { + // Get info + FlightDataBranch.Type type = filled.getType(i); + Unit unit = filled.getUnit(i); + int axis = filled.getAxis(i); + String name = getLabel(type, unit); + + // Store data in provided units + List y = branch.get(type); + XYSeries series = new XYSeries(name, false, true); + for (int j=0; j 0) { + // Create and set axis + double min = axes.get(i).getMinValue(); + double max = axes.get(i).getMaxValue(); + NumberAxis axis = new PresetNumberAxis(min, max); + axis.setLabel(axisLabel[i]); +// axis.setRange(axes.get(i).getMinValue(), axes.get(i).getMaxValue()); + plot.setRangeAxis(axisno, axis); + + // Add data and map to the axis + plot.setDataset(axisno, data[i]); + plot.setRenderer(axisno, new StandardXYItemRenderer()); + plot.mapDatasetToRangeAxis(axisno, axisno); + axisno++; + } + } + + plot.getDomainAxis().setLabel(getLabel(domainType,domainUnit)); + plot.addDomainMarker(new ValueMarker(0)); + plot.addRangeMarker(new ValueMarker(0)); + + + // Create the dialog + + JPanel panel = new JPanel(new MigLayout("fill")); + this.add(panel); + + ChartPanel chartPanel = new ChartPanel(chart, + false, // properties + true, // save + false, // print + true, // zoom + true); // tooltips + chartPanel.setMouseWheelEnabled(true); + chartPanel.setEnforceFileExtensions(true); + chartPanel.setInitialDelay(500); + + chartPanel.setBorder(BorderFactory.createLineBorder(Color.GRAY, 1)); + + panel.add(chartPanel, "grow, wrap 20lp"); + + JButton button = new JButton("Close"); + button.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + PlotDialog.this.dispose(); + } + }); + panel.add(button, "right"); + + this.setLocationByPlatform(true); + this.pack(); + GUIUtil.installEscapeCloseOperation(this); + GUIUtil.setDefaultButton(button); + } + + + private String getLabel(FlightDataBranch.Type type, Unit unit) { + String name = type.getName(); + if (unit != null && !UnitGroup.UNITS_NONE.contains(unit) && + !UnitGroup.UNITS_COEFFICIENT.contains(unit) && unit.getUnit().length() > 0) + name += " ("+unit.getUnit() + ")"; + return name; + } + + + + private class PresetNumberAxis extends NumberAxis { + private final double min; + private final double max; + + public PresetNumberAxis(double min, double max) { + this.min = min; + this.max = max; + autoAdjustRange(); + } + + @Override + protected void autoAdjustRange() { + this.setRange(min, max); + } + } + + + /** + * Static method that shows a plot with the specified parameters. + * + * @param parent the parent window, which will be blocked. + * @param simulation the simulation to plot. + * @param config the configuration of the plot. + */ + public static void showPlot(Window parent, Simulation simulation, PlotConfiguration config) { + new PlotDialog(parent, simulation, config).setVisible(true); + } + +} diff --git a/src/net/sf/openrocket/gui/plot/PlotPanel.java b/src/net/sf/openrocket/gui/plot/PlotPanel.java new file mode 100644 index 000000000..953ab4a0f --- /dev/null +++ b/src/net/sf/openrocket/gui/plot/PlotPanel.java @@ -0,0 +1,336 @@ +package net.sf.openrocket.gui.plot; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.util.Arrays; + +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.SwingUtilities; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.document.Simulation; +import net.sf.openrocket.gui.ResizeLabel; +import net.sf.openrocket.gui.UnitSelector; +import net.sf.openrocket.simulation.FlightDataBranch; +import net.sf.openrocket.simulation.FlightDataBranch.Type; +import net.sf.openrocket.unit.Unit; + +public class PlotPanel extends JPanel { + + // TODO: LOW: Should these be somewhere else? + public static final int AUTO = -1; + public static final int LEFT = 0; + public static final int RIGHT = 1; + + public static final String AUTO_NAME = "Auto"; + public static final String LEFT_NAME = "Left"; + public static final String RIGHT_NAME = "Right"; + + private static final String CUSTOM = "Custom"; + + /** The "Custom" configuration - not to be used for anything other than the title. */ + private static final PlotConfiguration CUSTOM_CONFIGURATION; + static { + CUSTOM_CONFIGURATION = new PlotConfiguration(CUSTOM); + } + + /** The array of presets for the combo box. */ + private static final PlotConfiguration[] PRESET_ARRAY; + static { + PRESET_ARRAY = Arrays.copyOf(PlotConfiguration.DEFAULT_CONFIGURATIONS, + PlotConfiguration.DEFAULT_CONFIGURATIONS.length + 1); + PRESET_ARRAY[PRESET_ARRAY.length-1] = CUSTOM_CONFIGURATION; + } + + + + /** The current default configuration, set each time a plot is made. */ + private static PlotConfiguration defaultConfiguration = + PlotConfiguration.DEFAULT_CONFIGURATIONS[0].resetUnits(); + + + private final Simulation simulation; + private final FlightDataBranch.Type[] types; + private PlotConfiguration configuration; + + + private JComboBox configurationSelector; + + private JComboBox domainTypeSelector; + private UnitSelector domainUnitSelector; + + private JPanel typeSelectorPanel; + + + private int modifying = 0; + + + public PlotPanel(final Simulation simulation) { + super(new MigLayout("fill")); + + this.simulation = simulation; + if (simulation.getSimulatedData() == null || + simulation.getSimulatedData().getBranchCount()==0) { + throw new IllegalArgumentException("Simulation contains no data."); + } + FlightDataBranch branch = simulation.getSimulatedData().getBranch(0); + types = branch.getTypes(); + + // TODO: LOW: Revert to custom if data type is not available. + configuration = defaultConfiguration.clone(); + + + + + // Setup the combo box + configurationSelector = new JComboBox(PRESET_ARRAY); + for (PlotConfiguration config: PRESET_ARRAY) { + if (config.getName().equals(configuration.getName())) { + configurationSelector.setSelectedItem(config); + } + } + configurationSelector.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + if (modifying > 0) + return; + PlotConfiguration conf = (PlotConfiguration)configurationSelector.getSelectedItem(); + if (conf == CUSTOM_CONFIGURATION) + return; + modifying++; + configuration = conf.clone().resetUnits(); + updatePlots(); + modifying--; + } + }); + this.add(new JLabel("Preset plot configurations: "), "spanx, split"); + this.add(configurationSelector,"growx, wrap 30lp"); + + + + this.add(new JLabel("X axis type:"), "spanx, split"); + domainTypeSelector = new JComboBox(types); + domainTypeSelector.setSelectedItem(configuration.getDomainAxisType()); + domainTypeSelector.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + if (modifying > 0) + return; + FlightDataBranch.Type type = (Type) domainTypeSelector.getSelectedItem(); + configuration.setDomainAxisType(type); + domainUnitSelector.setUnitGroup(type.getUnitGroup()); + domainUnitSelector.setSelectedUnit(configuration.getDomainAxisUnit()); + setToCustom(); + } + }); + this.add(domainTypeSelector, "gapright para"); + + + this.add(new JLabel("Unit:")); + domainUnitSelector = new UnitSelector(configuration.getDomainAxisType().getUnitGroup()); + domainUnitSelector.setSelectedUnit(configuration.getDomainAxisUnit()); + domainUnitSelector.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + if (modifying > 0) + return; + configuration.setDomainAxisUnit(domainUnitSelector.getSelectedUnit()); + } + }); + this.add(domainUnitSelector, "width 40lp, gapright para"); + + + ResizeLabel desc = new ResizeLabel("

The data will be plotted in time order " + + "even if the X axis type is not time.", -2); + this.add(desc, "width :0px:, growx, wrap para"); + + + + this.add(new JLabel("Y axis types:"), "spanx, wrap rel"); + + typeSelectorPanel = new JPanel(new MigLayout("gapy rel")); + JScrollPane scroll = new JScrollPane(typeSelectorPanel); + this.add(scroll, "spanx, height :0:, grow, wrap para"); + + + JButton button = new JButton("New Y axis plot type"); + button.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (configuration.getTypeCount() >= 15) { + JOptionPane.showMessageDialog(PlotPanel.this, + "A maximum of 15 plots is allowed.", "Cannot add plot", + JOptionPane.ERROR_MESSAGE); + return; + } + + // Select new type smartly + FlightDataBranch.Type type = null; + for (FlightDataBranch.Type t: + simulation.getSimulatedData().getBranch(0).getTypes()) { + + boolean used = false; + if (configuration.getDomainAxisType().equals(t)) { + used = true; + } else { + for (int i=0; i < configuration.getTypeCount(); i++) { + if (configuration.getType(i).equals(t)) { + used = true; + break; + } + } + } + + if (!used) { + type = t; + break; + } + } + if (type == null) { + type = simulation.getSimulatedData().getBranch(0).getTypes()[0]; + } + + // Add new type + configuration.addPlotDataType(type); + setToCustom(); + updatePlots(); + } + }); + this.add(button, "spanx, split"); + + this.add(new JPanel(), "growx"); + + button = new JButton("Plot flight"); + button.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + defaultConfiguration = configuration.clone(); + PlotDialog.showPlot(SwingUtilities.getWindowAncestor(PlotPanel.this), + simulation, configuration); + } + }); + this.add(button, "right"); + + + updatePlots(); + } + + + private void setToCustom() { + configuration.setName(CUSTOM); + configurationSelector.setSelectedItem(CUSTOM_CONFIGURATION); + } + + + private void updatePlots() { + domainTypeSelector.setSelectedItem(configuration.getDomainAxisType()); + domainUnitSelector.setUnitGroup(configuration.getDomainAxisType().getUnitGroup()); + domainUnitSelector.setSelectedUnit(configuration.getDomainAxisUnit()); + + typeSelectorPanel.removeAll(); + for (int i=0; i < configuration.getTypeCount(); i++) { + FlightDataBranch.Type type = configuration.getType(i); + Unit unit = configuration.getUnit(i); + int axis = configuration.getAxis(i); + + typeSelectorPanel.add(new PlotTypeSelector(i, type, unit, axis), "wrap"); + } + + typeSelectorPanel.repaint(); + } + + + + + /** + * A JPanel which configures a single plot of a PlotConfiguration. + */ + private class PlotTypeSelector extends JPanel { + private final String[] POSITIONS = { AUTO_NAME, LEFT_NAME, RIGHT_NAME }; + + private final int index; + private JComboBox typeSelector; + private UnitSelector unitSelector; + private JComboBox axisSelector; + + + public PlotTypeSelector(int index, FlightDataBranch.Type type) { + this (index, type, null, -1); + } + + public PlotTypeSelector(int plotIndex, FlightDataBranch.Type type, Unit unit, int position) { + super(new MigLayout("")); + + this.index = plotIndex; + + typeSelector = new JComboBox(types); + typeSelector.setSelectedItem(type); + typeSelector.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + if (modifying > 0) + return; + FlightDataBranch.Type type = (Type) typeSelector.getSelectedItem(); + configuration.setPlotDataType(index, type); + unitSelector.setUnitGroup(type.getUnitGroup()); + unitSelector.setSelectedUnit(configuration.getUnit(index)); + setToCustom(); + } + }); + this.add(typeSelector, "gapright para"); + + this.add(new JLabel("Unit:")); + unitSelector = new UnitSelector(type.getUnitGroup()); + if (unit != null) + unitSelector.setSelectedUnit(unit); + unitSelector.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + if (modifying > 0) + return; + Unit unit = (Unit) unitSelector.getSelectedUnit(); + configuration.setPlotDataUnit(index, unit); + } + }); + this.add(unitSelector, "width 40lp, gapright para"); + + this.add(new JLabel("Axis:")); + axisSelector = new JComboBox(POSITIONS); + if (position == LEFT) + axisSelector.setSelectedIndex(1); + else if (position == RIGHT) + axisSelector.setSelectedIndex(2); + else + axisSelector.setSelectedIndex(0); + axisSelector.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + if (modifying > 0) + return; + int axis = axisSelector.getSelectedIndex() - 1; + configuration.setPlotDataAxis(index, axis); + } + }); + this.add(axisSelector, "gapright para"); + + + JButton button = new JButton("Remove"); + button.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + configuration.removePlotDataType(index); + setToCustom(); + updatePlots(); + } + }); + this.add(button); + } + } +} diff --git a/src/net/sf/openrocket/gui/rocketfigure/BodyTubeShapes.java b/src/net/sf/openrocket/gui/rocketfigure/BodyTubeShapes.java new file mode 100644 index 000000000..9cef22d13 --- /dev/null +++ b/src/net/sf/openrocket/gui/rocketfigure/BodyTubeShapes.java @@ -0,0 +1,46 @@ +package net.sf.openrocket.gui.rocketfigure; + +import java.awt.Shape; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Rectangle2D; + +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.Transformation; + + +public class BodyTubeShapes extends RocketComponentShapes { + + public static Shape[] getShapesSide(net.sf.openrocket.rocketcomponent.RocketComponent component, + Transformation transformation) { + net.sf.openrocket.rocketcomponent.BodyTube tube = (net.sf.openrocket.rocketcomponent.BodyTube)component; + + double length = tube.getLength(); + double radius = tube.getRadius(); + Coordinate[] start = transformation.transform(tube.toAbsolute(new Coordinate(0,0,0))); + + Shape[] s = new Shape[start.length]; + for (int i=0; i < start.length; i++) { + s[i] = new Rectangle2D.Double(start[i].x*S,(start[i].y-radius)*S, + length*S,2*radius*S); + } + return s; + } + + + public static Shape[] getShapesBack(net.sf.openrocket.rocketcomponent.RocketComponent component, + Transformation transformation) { + net.sf.openrocket.rocketcomponent.BodyTube tube = (net.sf.openrocket.rocketcomponent.BodyTube)component; + + double or = tube.getRadius(); + + Coordinate[] start = transformation.transform(tube.toAbsolute(new Coordinate(0,0,0))); + + Shape[] s = new Shape[start.length]; + for (int i=0; i < start.length; i++) { + s[i] = new Ellipse2D.Double((start[i].z-or)*S,(start[i].y-or)*S,2*or*S,2*or*S); + } + return s; + } + + +} diff --git a/src/net/sf/openrocket/gui/rocketfigure/FinSetShapes.java b/src/net/sf/openrocket/gui/rocketfigure/FinSetShapes.java new file mode 100644 index 000000000..3d874a205 --- /dev/null +++ b/src/net/sf/openrocket/gui/rocketfigure/FinSetShapes.java @@ -0,0 +1,296 @@ +package net.sf.openrocket.gui.rocketfigure; + +import java.awt.Shape; +import java.awt.geom.Path2D; + +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.MathUtil; +import net.sf.openrocket.util.Transformation; + + +public class FinSetShapes extends RocketComponentShapes { + + // TODO: LOW: Clustering is ignored (FinSet cannot currently be clustered) + + public static Shape[] getShapesSide(net.sf.openrocket.rocketcomponent.RocketComponent component, + Transformation transformation) { + net.sf.openrocket.rocketcomponent.FinSet finset = (net.sf.openrocket.rocketcomponent.FinSet)component; + + + int fins = finset.getFinCount(); + Transformation cantRotation = finset.getCantRotation(); + Transformation baseRotation = finset.getBaseRotationTransformation(); + Transformation finRotation = finset.getFinRotationTransformation(); + + Coordinate c[] = finset.getFinPoints(); + + + // TODO: MEDIUM: sloping radius + double radius = finset.getBodyRadius(); + + // Translate & rotate the coordinates + for (int i=0; i 0; maxIndex--) { + if (points[maxIndex-1].y < points[maxIndex].y) + break; + } + + transformPoints(points,cantRotation); + transformPoints(points,new Transformation(0,radius,0)); + transformPoints(points,baseRotation); + + + sidePoints = new Coordinate[points.length]; + backPoints = new Coordinate[2*(points.length-maxIndex)]; + double sign; + if (finset.getCantAngle() > 0) { + sign = 1.0; + } else { + sign = -1.0; + } + + // Calculate points for the side panel + for (i=0; i < points.length; i++) { + sidePoints[i] = points[i].add(0,0,sign*thickness/2); + } + + // Calculate points for the back portion + i=0; + for (int j=points.length-1; j >= maxIndex; j--, i++) { + backPoints[i] = points[j].add(0,0,sign*thickness/2); + } + for (int j=maxIndex; j <= points.length-1; j++, i++) { + backPoints[i] = points[j].add(0,0,-sign*thickness/2); + } + + // Generate shapes + Shape[] s; + if (thickness > 0.0005) { + + s = new Shape[fins*2]; + for (int fin=0; fin= 0.0012) && (ir > 0)) { + // Draw outer and inner + s = new Shape[start.length*2]; + for (int i=0; i < start.length; i++) { + s[2*i] = new Rectangle2D.Double(start[i].x*S,(start[i].y-or)*S, + length*S,2*or*S); + s[2*i+1] = new Rectangle2D.Double(start[i].x*S,(start[i].y-ir)*S, + length*S,2*ir*S); + } + } else { + // Draw only outer + s = new Shape[start.length]; + for (int i=0; i < start.length; i++) { + s[i] = new Rectangle2D.Double(start[i].x*S,(start[i].y-or)*S, + length*S,2*or*S); + } + } + return s; + } + + + public static Shape[] getShapesBack(net.sf.openrocket.rocketcomponent.RocketComponent component, + Transformation transformation) { + net.sf.openrocket.rocketcomponent.RingComponent tube = (net.sf.openrocket.rocketcomponent.RingComponent)component; + Shape[] s; + + double or = tube.getOuterRadius(); + double ir = tube.getInnerRadius(); + + + Coordinate[] start = transformation.transform(tube.toAbsolute(new Coordinate(0,0,0))); + + if ((ir < or) && (ir > 0)) { + // Draw inner and outer + s = new Shape[start.length*2]; + for (int i=0; i < start.length; i++) { + s[2*i] = new Ellipse2D.Double((start[i].z-or)*S, (start[i].y-or)*S, + 2*or*S, 2*or*S); + s[2*i+1] = new Ellipse2D.Double((start[i].z-ir)*S, (start[i].y-ir)*S, + 2*ir*S, 2*ir*S); + } + } else { + // Draw only outer + s = new Shape[start.length]; + for (int i=0; i < start.length; i++) { + s[i] = new Ellipse2D.Double((start[i].z-or)*S,(start[i].y-or)*S,2*or*S,2*or*S); + } + } + return s; + } + +} diff --git a/src/net/sf/openrocket/gui/rocketfigure/RocketComponentShapes.java b/src/net/sf/openrocket/gui/rocketfigure/RocketComponentShapes.java new file mode 100644 index 000000000..8311d0ae1 --- /dev/null +++ b/src/net/sf/openrocket/gui/rocketfigure/RocketComponentShapes.java @@ -0,0 +1,32 @@ +package net.sf.openrocket.gui.rocketfigure; + + +import java.awt.Shape; + +import net.sf.openrocket.gui.scalefigure.RocketFigure; +import net.sf.openrocket.util.Transformation; + + +/** + * A catch-all, no-operation drawing component. + */ +public class RocketComponentShapes { + + protected static final double S = RocketFigure.EXTRA_SCALE; + + public static Shape[] getShapesSide(net.sf.openrocket.rocketcomponent.RocketComponent component, + Transformation t) { + // no-op + System.err.println("ERROR: RocketComponent.getShapesSide called with "+component); + return new Shape[0]; + } + + public static Shape[] getShapesBack(net.sf.openrocket.rocketcomponent.RocketComponent component, + Transformation t) { + // no-op + System.err.println("ERROR: RocketComponent.getShapesBack called with "+component); + return new Shape[0]; + } + + +} diff --git a/src/net/sf/openrocket/gui/rocketfigure/SymmetricComponentShapes.java b/src/net/sf/openrocket/gui/rocketfigure/SymmetricComponentShapes.java new file mode 100644 index 000000000..c1b9caee7 --- /dev/null +++ b/src/net/sf/openrocket/gui/rocketfigure/SymmetricComponentShapes.java @@ -0,0 +1,107 @@ +package net.sf.openrocket.gui.rocketfigure; + +import java.awt.Shape; +import java.awt.geom.Path2D; +import java.util.ArrayList; + +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.Transformation; + + +public class SymmetricComponentShapes extends RocketComponentShapes { + private static final int MINPOINTS = 91; + private static final double ACCEPTABLE_ANGLE = Math.cos(7.0*Math.PI/180.0); + + // TODO: HIGH: adaptiveness sucks, remove it. + + // TODO: LOW: Uses only first component of cluster (not currently clusterable) + + public static Shape[] getShapesSide(net.sf.openrocket.rocketcomponent.RocketComponent component, + Transformation transformation) { + net.sf.openrocket.rocketcomponent.SymmetricComponent c = (net.sf.openrocket.rocketcomponent.SymmetricComponent)component; + int i; + + final double delta = 0.0000001; + double x; + + ArrayList points = new ArrayList(); + x = delta; + points.add(new Coordinate(x,c.getRadius(x),0)); + for (i=1; i < MINPOINTS-1; i++) { + x = c.getLength()*i/(MINPOINTS-1); + points.add(new Coordinate(x,c.getRadius(x),0)); + //System.out.println("Starting with x="+x); + } + x = c.getLength() - delta; + points.add(new Coordinate(x,c.getRadius(x),0)); + + + i=0; + while (i < points.size()-2) { + if (angleAcceptable(points.get(i),points.get(i+1),points.get(i+2)) || + points.get(i+1).x - points.get(i).x < 0.001) { // 1mm + i++; + continue; + } + + // Split the longer of the areas + int n; + if (points.get(i+2).x-points.get(i+1).x > points.get(i+1).x-points.get(i).x) + n = i+1; + else + n = i; + + x = (points.get(n).x + points.get(n+1).x)/2; + points.add(n+1,new Coordinate(x,c.getRadius(x),0)); + } + + + //System.out.println("Final points: "+points.size()); + + final int len = points.size(); + + for (i=0; i < len; i++) { + points.set(i, c.toAbsolute(points.get(i))[0]); + } + + /* Show points: + Shape[] s = new Shape[len+1]; + final double d=0.001; + for (i=0; i=0; i--) { + path.lineTo(points.get(i).x*S, points.get(i).y*S); + } + for (i=0; i ACCEPTABLE_ANGLE); + } + /* + * cosAngle = v1.v2 / |v1|*|v2| = v1.v2 / sqrt(v1.v1*v2.v2) + */ + private static double cosAngle(Coordinate v1, Coordinate v2, Coordinate v3) { + double cos; + double len; + cos = Coordinate.dot(v1.sub(v2), v2.sub(v3)); + len = Math.sqrt(v1.sub(v2).length2() * v2.sub(v3).length2()); + return cos/len; + } +} diff --git a/src/net/sf/openrocket/gui/rocketfigure/TransitionShapes.java b/src/net/sf/openrocket/gui/rocketfigure/TransitionShapes.java new file mode 100644 index 000000000..205cd502a --- /dev/null +++ b/src/net/sf/openrocket/gui/rocketfigure/TransitionShapes.java @@ -0,0 +1,96 @@ +package net.sf.openrocket.gui.rocketfigure; + +import java.awt.Shape; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Path2D; +import java.awt.geom.Rectangle2D; + +import net.sf.openrocket.rocketcomponent.Transition; +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.Transformation; + + +public class TransitionShapes extends RocketComponentShapes { + + // TODO: LOW: Uses only first component of cluster (not currently clusterable). + + public static Shape[] getShapesSide(net.sf.openrocket.rocketcomponent.RocketComponent component, + Transformation transformation) { + net.sf.openrocket.rocketcomponent.Transition transition = (net.sf.openrocket.rocketcomponent.Transition)component; + + Shape[] mainShapes; + + // Simpler shape for conical transition, others use the method from SymmetricComponent + if (transition.getType() == Transition.Shape.CONICAL) { + double length = transition.getLength(); + double r1 = transition.getForeRadius(); + double r2 = transition.getAftRadius(); + Coordinate start = transformation.transform(transition. + toAbsolute(Coordinate.NUL)[0]); + + Path2D.Float path = new Path2D.Float(); + path.moveTo(start.x*S, r1*S); + path.lineTo((start.x+length)*S, r2*S); + path.lineTo((start.x+length)*S, -r2*S); + path.lineTo(start.x*S, -r1*S); + path.closePath(); + + mainShapes = new Shape[] { path }; + } else { + mainShapes = SymmetricComponentShapes.getShapesSide(component, transformation); + } + + Rectangle2D.Double shoulder1=null, shoulder2=null; + int arrayLength = mainShapes.length; + + if (transition.getForeShoulderLength() > 0.0005) { + Coordinate start = transformation.transform(transition. + toAbsolute(Coordinate.NUL)[0]); + double r = transition.getForeShoulderRadius(); + double l = transition.getForeShoulderLength(); + shoulder1 = new Rectangle2D.Double((start.x-l)*S, -r*S, l*S, 2*r*S); + arrayLength++; + } + if (transition.getAftShoulderLength() > 0.0005) { + Coordinate start = transformation.transform(transition. + toAbsolute(new Coordinate(transition.getLength()))[0]); + double r = transition.getAftShoulderRadius(); + double l = transition.getAftShoulderLength(); + shoulder2 = new Rectangle2D.Double(start.x*S, -r*S, l*S, 2*r*S); + arrayLength++; + } + if (shoulder1==null && shoulder2==null) + return mainShapes; + + Shape[] shapes = new Shape[arrayLength]; + int i; + + for (i=0; i < mainShapes.length; i++) { + shapes[i] = mainShapes[i]; + } + if (shoulder1 != null) { + shapes[i] = shoulder1; + i++; + } + if (shoulder2 != null) { + shapes[i] = shoulder2; + } + return shapes; + } + + + public static Shape[] getShapesBack(net.sf.openrocket.rocketcomponent.RocketComponent component, + Transformation transformation) { + net.sf.openrocket.rocketcomponent.Transition transition = (net.sf.openrocket.rocketcomponent.Transition)component; + + double r1 = transition.getForeRadius(); + double r2 = transition.getAftRadius(); + + Shape[] s = new Shape[2]; + s[0] = new Ellipse2D.Double(-r1*S,-r1*S,2*r1*S,2*r1*S); + s[1] = new Ellipse2D.Double(-r2*S,-r2*S,2*r2*S,2*r2*S); + return s; + } + + +} diff --git a/src/net/sf/openrocket/gui/scalefigure/AbstractScaleFigure.java b/src/net/sf/openrocket/gui/scalefigure/AbstractScaleFigure.java new file mode 100644 index 000000000..5a515ba76 --- /dev/null +++ b/src/net/sf/openrocket/gui/scalefigure/AbstractScaleFigure.java @@ -0,0 +1,112 @@ +package net.sf.openrocket.gui.scalefigure; + +import java.awt.Color; +import java.awt.Dimension; +import java.util.LinkedList; +import java.util.List; + +import javax.swing.JPanel; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import net.sf.openrocket.util.Prefs; + + +public abstract class AbstractScaleFigure extends JPanel implements ScaleFigure { + + // Number of pixels to leave at edges when fitting figure + public static final int BORDER_PIXELS_WIDTH=30; + public static final int BORDER_PIXELS_HEIGHT=20; + + + protected final double dpi; + + protected double scale = 1.0; + protected double scaling = 1.0; + + protected final List listeners = new LinkedList(); + + + public AbstractScaleFigure() { + this.dpi = Prefs.getDPI(); + this.scaling = 1.0; + this.scale = dpi/0.0254*scaling; + + setBackground(Color.WHITE); + setOpaque(true); + } + + + + public abstract void updateFigure(); + public abstract double getFigureWidth(); + public abstract double getFigureHeight(); + + + @Override + public double getScaling() { + return scaling; + } + + @Override + public double getAbsoluteScale() { + return scale; + } + + @Override + public void setScaling(double scaling) { + if (Double.isInfinite(scaling) || Double.isNaN(scaling)) + scaling = 1.0; + if (scaling < 0.001) + scaling = 0.001; + if (scaling > 1000) + scaling = 1000; + if (Math.abs(this.scaling - scaling) < 0.01) + return; + this.scaling = scaling; + this.scale = dpi/0.0254*scaling; + updateFigure(); + } + + @Override + public void setScaling(Dimension bounds) { + double zh = 1, zv = 1; + int w = bounds.width - 2*BORDER_PIXELS_WIDTH -20; + int h = bounds.height - 2*BORDER_PIXELS_HEIGHT -20; + + if (w < 10) + w = 10; + if (h < 10) + h = 10; + + zh = ((double)w) / getFigureWidth(); + zv = ((double)h) / getFigureHeight(); + + double s = Math.min(zh, zv)/dpi*0.0254 - 0.001; + + setScaling(s); + } + + + + @Override + public void addChangeListener(ChangeListener listener) { + listeners.add(0,listener); + } + + @Override + public void removeChangeListener(ChangeListener listener) { + listeners.remove(listener); + } + + private ChangeEvent changeEvent = null; + protected void fireChangeEvent() { + ChangeListener[] list = listeners.toArray(new ChangeListener[0]); + for (ChangeListener l: list) { + if (changeEvent == null) + changeEvent = new ChangeEvent(this); + l.stateChanged(changeEvent); + } + } + +} diff --git a/src/net/sf/openrocket/gui/scalefigure/FinPointFigure.java b/src/net/sf/openrocket/gui/scalefigure/FinPointFigure.java new file mode 100644 index 000000000..29e2112c4 --- /dev/null +++ b/src/net/sf/openrocket/gui/scalefigure/FinPointFigure.java @@ -0,0 +1,344 @@ +package net.sf.openrocket.gui.scalefigure; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.geom.AffineTransform; +import java.awt.geom.Line2D; +import java.awt.geom.NoninvertibleTransformException; +import java.awt.geom.Path2D; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; + +import net.sf.openrocket.rocketcomponent.FreeformFinSet; +import net.sf.openrocket.unit.Tick; +import net.sf.openrocket.unit.Unit; +import net.sf.openrocket.unit.UnitGroup; +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.MathUtil; + + +// TODO: MEDIUM: the figure jumps and bugs when using automatic fitting + +public class FinPointFigure extends AbstractScaleFigure { + + private static final int BOX_SIZE = 4; + + private final FreeformFinSet finset; + private int modID = -1; + + private double minX, maxX, maxY; + private double figureWidth = 0; + private double figureHeight = 0; + private double translateX = 0; + private double translateY = 0; + + private AffineTransform transform; + private Rectangle2D.Double[] handles = null; + + + public FinPointFigure(FreeformFinSet finset) { + this.finset = finset; + } + + + @Override + public void paintComponent(Graphics g) { + super.paintComponent(g); + Graphics2D g2 = (Graphics2D) g; + + if (modID != finset.getRocket().getAerodynamicModID()) { + modID = finset.getRocket().getAerodynamicModID(); + calculateDimensions(); + } + + + double tx, ty; + // Calculate translation for figure centering + if (figureWidth*scale + 2*BORDER_PIXELS_WIDTH < getWidth()) { + + // Figure fits in the viewport + tx = (getWidth()-figureWidth*scale)/2 - minX*scale; + + } else { + + // Figure does not fit in viewport + tx = BORDER_PIXELS_WIDTH - minX*scale; + + } + + + if (figureHeight*scale + 2*BORDER_PIXELS_HEIGHT < getHeight()) { + ty = getHeight() - BORDER_PIXELS_HEIGHT; + } else { + ty = BORDER_PIXELS_HEIGHT + figureHeight*scale; + } + + if (Math.abs(translateX - tx)>1 || Math.abs(translateY - ty)>1) { + // Origin has changed, fire event + translateX = tx; + translateY = ty; + fireChangeEvent(); + } + + + if (Math.abs(translateX - tx)>1 || Math.abs(translateY - ty)>1) { + // Origin has changed, fire event + translateX = tx; + translateY = ty; + fireChangeEvent(); + } + + + // Calculate and store the transformation used + transform = new AffineTransform(); + transform.translate(translateX, translateY); + transform.scale(scale/EXTRA_SCALE, -scale/EXTRA_SCALE); + + // TODO: HIGH: border Y-scale upwards + + g2.transform(transform); + + // Set rendering hints appropriately + g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, + RenderingHints.VALUE_STROKE_NORMALIZE); + g2.setRenderingHint(RenderingHints.KEY_RENDERING, + RenderingHints.VALUE_RENDER_QUALITY); + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + + + + Rectangle visible = g2.getClipBounds(); + double x0 = ((double)visible.x-3)/EXTRA_SCALE; + double x1 = ((double)visible.x+visible.width+4)/EXTRA_SCALE; + double y0 = ((double)visible.y-3)/EXTRA_SCALE; + double y1 = ((double)visible.y+visible.height+4)/EXTRA_SCALE; + + + // Background grid + + g2.setStroke(new BasicStroke((float)(1.0*EXTRA_SCALE/scale), + BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL)); + g2.setColor(new Color(0,0,255,30)); + + Unit unit; + if (this.getParent() != null && + this.getParent().getParent() instanceof ScaleScrollPane) { + unit = ((ScaleScrollPane)this.getParent().getParent()).getCurrentUnit(); + } else { + unit = UnitGroup.UNITS_LENGTH.getDefaultUnit(); + } + + // vertical + Tick[] ticks = unit.getTicks(x0, x1, + ScaleScrollPane.MINOR_TICKS/scale, + ScaleScrollPane.MAJOR_TICKS/scale); + Line2D.Double line = new Line2D.Double(); + for (Tick t: ticks) { + if (t.major) { + line.setLine(t.value*EXTRA_SCALE, y0*EXTRA_SCALE, + t.value*EXTRA_SCALE, y1*EXTRA_SCALE); + g2.draw(line); + } + } + + // horizontal + ticks = unit.getTicks(y0, y1, + ScaleScrollPane.MINOR_TICKS/scale, + ScaleScrollPane.MAJOR_TICKS/scale); + for (Tick t: ticks) { + if (t.major) { + line.setLine(x0*EXTRA_SCALE, t.value*EXTRA_SCALE, + x1*EXTRA_SCALE, t.value*EXTRA_SCALE); + g2.draw(line); + } + } + + + + + + // Base rocket line + g2.setStroke(new BasicStroke((float)(3.0*EXTRA_SCALE/scale), + BasicStroke.CAP_BUTT,BasicStroke.JOIN_BEVEL)); + g2.setColor(Color.GRAY); + + g2.drawLine((int)(x0*EXTRA_SCALE), 0, (int)(x1*EXTRA_SCALE), 0); + + + // Fin shape + Coordinate[] points = finset.getFinPoints(); + Path2D.Double shape = new Path2D.Double(); + shape.moveTo(0, 0); + for (int i=1; i < points.length; i++) { + shape.lineTo(points[i].x*EXTRA_SCALE, points[i].y*EXTRA_SCALE); + } + + g2.setStroke(new BasicStroke((float)(1.0*EXTRA_SCALE/scale), + BasicStroke.CAP_BUTT,BasicStroke.JOIN_BEVEL)); + g2.setColor(Color.BLACK); + g2.draw(shape); + + + // Fin point boxes + g2.setColor(new Color(150,0,0)); + double s = BOX_SIZE*EXTRA_SCALE/scale; + handles = new Rectangle2D.Double[points.length]; + for (int i=0; i < points.length; i++) { + Coordinate c = points[i]; + handles[i] = new Rectangle2D.Double(c.x*EXTRA_SCALE-s, c.y*EXTRA_SCALE-s, 2*s, 2*s); + g2.draw(handles[i]); + } + + } + + + + public int getIndexByPoint(double x, double y) { + if (handles == null) + return -1; + + // Calculate point in shapes' coordinates + Point2D.Double p = new Point2D.Double(x,y); + try { + transform.inverseTransform(p,p); + } catch (NoninvertibleTransformException e) { + return -1; + } + + for (int i=0; i < handles.length; i++) { + if (handles[i].contains(p)) + return i; + } + return -1; + } + + + public int getSegmentByPoint(double x, double y) { + if (handles == null) + return -1; + + // Calculate point in shapes' coordinates + Point2D.Double p = new Point2D.Double(x,y); + try { + transform.inverseTransform(p,p); + } catch (NoninvertibleTransformException e) { + return -1; + } + + double x0 = p.x / EXTRA_SCALE; + double y0 = p.y / EXTRA_SCALE; + double delta = BOX_SIZE / scale; + + System.out.println("Point: "+x0+","+y0); + System.out.println("delta: "+(BOX_SIZE/scale)); + + Coordinate[] points = finset.getFinPoints(); + for (int i=1; i < points.length; i++) { + double x1 = points[i-1].x; + double y1 = points[i-1].y; + double x2 = points[i].x; + double y2 = points[i].y; + +// System.out.println("point1:"+x1+","+y1+" point2:"+x2+","+y2); + + double u = Math.abs((x2-x1)*(y1-y0) - (x1-x0)*(y2-y1)) / + MathUtil.hypot(x2-x1, y2-y1); + System.out.println("Distance of segment "+i+" is "+u); + if (u < delta) + return i; + } + + return -1; + } + + + public Point2D.Double convertPoint(double x, double y) { + Point2D.Double p = new Point2D.Double(x,y); + try { + transform.inverseTransform(p,p); + } catch (NoninvertibleTransformException e) { + assert(false): "Should not occur"; + return new Point2D.Double(0,0); + } + + p.setLocation(p.x / EXTRA_SCALE, p.y / EXTRA_SCALE); + return p; + } + + + + @Override + public Dimension getOrigin() { + if (modID != finset.getRocket().getAerodynamicModID()) { + modID = finset.getRocket().getAerodynamicModID(); + calculateDimensions(); + } + return new Dimension((int)translateX, (int)translateY); + } + + @Override + public double getFigureWidth() { + if (modID != finset.getRocket().getAerodynamicModID()) { + modID = finset.getRocket().getAerodynamicModID(); + calculateDimensions(); + } + return figureWidth; + } + + @Override + public double getFigureHeight() { + if (modID != finset.getRocket().getAerodynamicModID()) { + modID = finset.getRocket().getAerodynamicModID(); + calculateDimensions(); + } + return figureHeight; + } + + + private void calculateDimensions() { + minX = 0; + maxX = 0; + maxY = 0; + + for (Coordinate c: finset.getFinPoints()) { + if (c.x < minX) + minX = c.x; + if (c.x > maxX) + maxX = c.x; + if (c.y > maxY) + maxY = c.y; + } + + if (maxX < 0.01) + maxX = 0.01; + + figureWidth = maxX - minX; + figureHeight = maxY; + + + Dimension d = new Dimension((int)(figureWidth*scale+2*BORDER_PIXELS_WIDTH), + (int)(figureHeight*scale+2*BORDER_PIXELS_HEIGHT)); + + if (!d.equals(getPreferredSize()) || !d.equals(getMinimumSize())) { + setPreferredSize(d); + setMinimumSize(d); + revalidate(); + } + } + + + + @Override + public void updateFigure() { + repaint(); + } + + + +} diff --git a/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java b/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java new file mode 100644 index 000000000..d48010b8b --- /dev/null +++ b/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java @@ -0,0 +1,542 @@ +package net.sf.openrocket.gui.scalefigure; + + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.Shape; +import java.awt.geom.AffineTransform; +import java.awt.geom.Ellipse2D; +import java.awt.geom.NoninvertibleTransformException; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashSet; + +import net.sf.openrocket.gui.figureelements.FigureElement; +import net.sf.openrocket.rocketcomponent.Configuration; +import net.sf.openrocket.rocketcomponent.Motor; +import net.sf.openrocket.rocketcomponent.MotorMount; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.LineStyle; +import net.sf.openrocket.util.MathUtil; +import net.sf.openrocket.util.Prefs; +import net.sf.openrocket.util.Reflection; +import net.sf.openrocket.util.Transformation; + +/** + * A ScaleFigure that draws a complete rocket. Extra information can + * be added to the figure by the methods {@link #addRelativeExtra(FigureElement)}, + * {@link #clearRelativeExtra()}. + * + * @author Sampo Niskanen + */ + +public class RocketFigure extends AbstractScaleFigure { + private static final long serialVersionUID = 1L; + + private static final String ROCKET_FIGURE_PACKAGE = "net.sf.openrocket.gui.rocketfigure"; + private static final String ROCKET_FIGURE_SUFFIX = "Shapes"; + + public static final int TYPE_SIDE = 1; + public static final int TYPE_BACK = 2; + + // Width for drawing normal and selected components + public static final double NORMAL_WIDTH = 1.0; + public static final double SELECTED_WIDTH = 2.0; + + + private final Configuration configuration; + private RocketComponent[] selection = new RocketComponent[0]; + + private int type = TYPE_SIDE; + + private double rotation; + private Transformation transformation; + + private double translateX, translateY; + + + + /* + * figureComponents contains the corresponding RocketComponents of the figureShapes + */ + private final ArrayList figureShapes = new ArrayList(); + private final ArrayList figureComponents = + new ArrayList(); + + private double minX=0, maxX=0, maxR=0; + // Figure width and height in SI-units and pixels + private double figureWidth=0, figureHeight=0; + private int figureWidthPx=0, figureHeightPx=0; + + private AffineTransform g2transformation = null; + + private final ArrayList relativeExtra = new ArrayList(); + private final ArrayList absoluteExtra = new ArrayList(); + + + /** + * Creates a new rocket figure. + */ + public RocketFigure(Configuration configuration) { + super(); + + this.configuration = configuration; + + this.rotation = 0.0; + this.transformation = Transformation.rotate_x(0.0); + + calculateSize(); + updateFigure(); + } + + + + public Dimension getOrigin() { + return new Dimension((int)translateX, (int)translateY); + } + + @Override + public double getFigureHeight() { + return figureHeight; + } + + @Override + public double getFigureWidth() { + return figureWidth; + } + + + public RocketComponent[] getSelection() { + return selection; + } + + public void setSelection(RocketComponent[] selection) { + if (selection == null) { + selection = new RocketComponent[0]; + } else { + this.selection = selection; + } + updateFigure(); + } + + + public double getRotation() { + return rotation; + } + + public Transformation getRotateTransformation() { + return transformation; + } + + public void setRotation(double rot) { + if (MathUtil.equals(rotation, rot)) + return; + this.rotation = rot; + this.transformation = Transformation.rotate_x(rotation); + updateFigure(); + } + + + public int getType() { + return type; + } + + public void setType(int type) { + if (type != TYPE_BACK && type != TYPE_SIDE) { + throw new IllegalArgumentException("Illegal type: "+type); + } + if (this.type == type) + return; + this.type = type; + updateFigure(); + } + + + + + + + /** + * Updates the figure shapes and figure size. + */ + @Override + public void updateFigure() { + figureShapes.clear(); + figureComponents.clear(); + + calculateSize(); + + // Get shapes for all active components + for (RocketComponent c: configuration) { + Shape[] s = getShapes(c); + for (int i=0; i < s.length; i++) { + figureShapes.add(s[i]); + figureComponents.add(c); + } + } + + repaint(); + fireChangeEvent(); + } + + + public void addRelativeExtra(FigureElement p) { + relativeExtra.add(p); + } + + public void removeRelativeExtra(FigureElement p) { + relativeExtra.remove(p); + } + + public void clearRelativeExtra() { + relativeExtra.clear(); + } + + + public void addAbsoluteExtra(FigureElement p) { + absoluteExtra.add(p); + } + + public void removeAbsoluteExtra(FigureElement p) { + absoluteExtra.remove(p); + } + + public void clearAbsoluteExtra() { + absoluteExtra.clear(); + } + + + /** + * Paints the rocket on to the Graphics element. + *

+ * Warning: If paintComponent is used outside the normal Swing usage, some Swing + * dependent parameters may be left wrong (mainly transformation). If it is used, + * the RocketFigure should be repainted immediately afterwards. + */ + @Override + public void paintComponent(Graphics g) { + super.paintComponent(g); + Graphics2D g2 = (Graphics2D)g; + + + AffineTransform baseTransform = g2.getTransform(); + + // Update figure shapes if necessary + if (figureShapes == null) + updateFigure(); + + + double tx, ty; + // Calculate translation for figure centering + if (figureWidthPx + 2*BORDER_PIXELS_WIDTH < getWidth()) { + + // Figure fits in the viewport + if (type == TYPE_BACK) + tx = getWidth()/2; + else + tx = (getWidth()-figureWidthPx)/2 - minX*scale; + + } else { + + // Figure does not fit in viewport + if (type == TYPE_BACK) + tx = BORDER_PIXELS_WIDTH + figureWidthPx/2; + else + tx = BORDER_PIXELS_WIDTH - minX*scale; + + } + + if (figureHeightPx + 2*BORDER_PIXELS_HEIGHT < getHeight()) { + ty = getHeight()/2; + } else { + ty = BORDER_PIXELS_HEIGHT + figureHeightPx/2; + } + + if (Math.abs(translateX - tx)>1 || Math.abs(translateY - ty)>1) { + // Origin has changed, fire event + translateX = tx; + translateY = ty; + fireChangeEvent(); + } + + + // Calculate and store the transformation used + // (inverse is used in detecting clicks on objects) + g2transformation = new AffineTransform(); + g2transformation.translate(translateX, translateY); + // Mirror position Y-axis upwards + g2transformation.scale(scale/EXTRA_SCALE, -scale/EXTRA_SCALE); + + g2.transform(g2transformation); + + // Set rendering hints appropriately + g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, + RenderingHints.VALUE_STROKE_NORMALIZE); + g2.setRenderingHint(RenderingHints.KEY_RENDERING, + RenderingHints.VALUE_RENDER_QUALITY); + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + + + // Draw all shapes + + for (int i=0; i < figureShapes.size(); i++) { + RocketComponent c = figureComponents.get(i); + Shape s = figureShapes.get(i); + boolean selected = false; + + // Check if component is in the selection + for (int j=0; j < selection.length; j++) { + if (c == selection[j]) { + selected = true; + break; + } + } + + // Set component color and line style + Color color = c.getColor(); + if (color == null) { + color = Prefs.getDefaultColor(c.getClass()); + } + g2.setColor(color); + + LineStyle style = c.getLineStyle(); + if (style == null) + style = Prefs.getDefaultLineStyle(c.getClass()); + + float[] dashes = style.getDashes(); + for (int j=0; j iterator = configuration.motorIterator(); + while (iterator.hasNext()) { + MotorMount mount = iterator.next(); + Motor motor = mount.getMotor(motorID); + double length = motor.getLength(); + double radius = motor.getDiameter() / 2; + + Coordinate[] position = ((RocketComponent)mount).toAbsolute( + new Coordinate(((RocketComponent)mount).getLength() + + mount.getMotorOverhang() - length)); + + for (int i=0; i < position.length; i++) { + position[i] = transformation.transform(position[i]); + } + + for (Coordinate coord: position) { + Shape s; + if (type == TYPE_SIDE) { + s = new Rectangle2D.Double(EXTRA_SCALE*coord.x, + EXTRA_SCALE*(coord.y - radius), EXTRA_SCALE*length, + EXTRA_SCALE*2*radius); + } else { + s = new Ellipse2D.Double(EXTRA_SCALE*(coord.z-radius), + EXTRA_SCALE*(coord.y-radius), EXTRA_SCALE*2*radius, + EXTRA_SCALE*2*radius); + } + g2.setColor(fillColor); + g2.fill(s); + g2.setColor(borderColor); + g2.draw(s); + } + } + + + + // Draw relative extras + for (FigureElement e: relativeExtra) { + e.paint(g2, scale/EXTRA_SCALE); + } + + // Draw absolute extras + g2.setTransform(baseTransform); + Rectangle rect = this.getVisibleRect(); + + for (FigureElement e: absoluteExtra) { + e.paint(g2, 1.0, rect); + } + + } + + + public RocketComponent[] getComponentsByPoint(double x, double y) { + // Calculate point in shapes' coordinates + Point2D.Double p = new Point2D.Double(x,y); + try { + g2transformation.inverseTransform(p,p); + } catch (NoninvertibleTransformException e) { + return new RocketComponent[0]; + } + + LinkedHashSet l = new LinkedHashSet(); + + for (int i=0; i bounds = configuration.getBounds(); + + if (bounds.isEmpty()) { + minX = 0; + maxX = 0; + maxR = 0; + return; + } + + minX = Double.MAX_VALUE; + maxX = Double.MIN_VALUE; + maxR = 0; + for (Coordinate c: bounds) { + double x = c.x, r = MathUtil.hypot(c.y, c.z); + if (x < minX) + minX = x; + if (x > maxX) + maxX = x; + if (r > maxR) + maxR = r; + } + } + + + public double getBestZoom(Rectangle2D bounds) { + double zh=1, zv=1; + if (bounds.getWidth() > 0.0001) + zh = (getWidth()-2*BORDER_PIXELS_WIDTH)/bounds.getWidth(); + if (bounds.getHeight() > 0.0001) + zv = (getHeight()-2*BORDER_PIXELS_HEIGHT)/bounds.getHeight(); + return Math.min(zh, zv); + } + + + /** + * Calculates the necessary size of the figure and set the PreferredSize + * property accordingly. + */ + private void calculateSize() { + calculateFigureBounds(); + + switch (type) { + case TYPE_SIDE: + figureWidth = maxX-minX; + figureHeight = 2*maxR; + break; + + case TYPE_BACK: + figureWidth = 2*maxR; + figureHeight = 2*maxR; + break; + + default: + assert(false): "Should not occur, type="+type; + figureWidth = 0; + figureHeight = 0; + } + + figureWidthPx = (int)(figureWidth * scale); + figureHeightPx = (int)(figureHeight * scale); + + Dimension d = new Dimension(figureWidthPx+2*BORDER_PIXELS_WIDTH, + figureHeightPx+2*BORDER_PIXELS_HEIGHT); + + if (!d.equals(getPreferredSize()) || !d.equals(getMinimumSize())) { + setPreferredSize(d); + setMinimumSize(d); + revalidate(); + } + } + + public Rectangle2D getDimensions() { + switch (type) { + case TYPE_SIDE: + return new Rectangle2D.Double(minX,-maxR,maxX-minX,2*maxR); + + case TYPE_BACK: + return new Rectangle2D.Double(-maxR,-maxR,2*maxR,2*maxR); + + default: + throw new RuntimeException("Illegal figure type = "+type); + } + } + +} diff --git a/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java b/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java new file mode 100644 index 000000000..94bf1017d --- /dev/null +++ b/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java @@ -0,0 +1,693 @@ +package net.sf.openrocket.gui.scalefigure; + + +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Point; +import java.awt.event.ActionEvent; +import java.awt.event.InputEvent; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSlider; +import javax.swing.JToggleButton; +import javax.swing.JViewport; +import javax.swing.SwingUtilities; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.tree.TreePath; +import javax.swing.tree.TreeSelectionModel; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.aerodynamics.AerodynamicCalculator; +import net.sf.openrocket.aerodynamics.BarrowmanCalculator; +import net.sf.openrocket.aerodynamics.FlightConditions; +import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.document.OpenRocketDocument; +import net.sf.openrocket.document.Simulation; +import net.sf.openrocket.gui.BasicSlider; +import net.sf.openrocket.gui.StageSelector; +import net.sf.openrocket.gui.UnitSelector; +import net.sf.openrocket.gui.adaptors.DoubleModel; +import net.sf.openrocket.gui.adaptors.MotorConfigurationModel; +import net.sf.openrocket.gui.configdialog.ComponentConfigDialog; +import net.sf.openrocket.gui.figureelements.CGCaret; +import net.sf.openrocket.gui.figureelements.CPCaret; +import net.sf.openrocket.gui.figureelements.Caret; +import net.sf.openrocket.gui.figureelements.RocketInfo; +import net.sf.openrocket.gui.main.ComponentTreeModel; +import net.sf.openrocket.gui.main.SimulationWorker; +import net.sf.openrocket.rocketcomponent.Configuration; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.rocketcomponent.SymmetricComponent; +import net.sf.openrocket.simulation.FlightData; +import net.sf.openrocket.simulation.SimulationListener; +import net.sf.openrocket.simulation.listeners.ApogeeEndListener; +import net.sf.openrocket.simulation.listeners.InterruptListener; +import net.sf.openrocket.unit.UnitGroup; +import net.sf.openrocket.util.ChangeSource; +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.MathUtil; +import net.sf.openrocket.util.Prefs; + +/** + * A JPanel that contains a RocketFigure and buttons to manipulate the figure. + * + * @author Sampo Niskanen + */ +public class RocketPanel extends JPanel implements TreeSelectionListener, ChangeSource { + + private final RocketFigure figure; + private final ScaleScrollPane scrollPane; + + private JLabel infoMessage; + + private TreeSelectionModel selectionModel = null; + + + /* Calculation of CP and CG */ + private AerodynamicCalculator calculator; + + + private final OpenRocketDocument document; + private final Configuration configuration; + + private Caret extraCP = null; + private Caret extraCG = null; + private RocketInfo extraText = null; + + + private double cpAOA = Double.NaN; + private double cpTheta = Double.NaN; + private double cpMach = Double.NaN; + private double cpRoll = Double.NaN; + + // The functional ID of the rocket that was simulated + private int flightDataFunctionalID = -1; + private String flightDataMotorID = null; + + + private SimulationWorker backgroundSimulationWorker = null; + + + private List listeners = new ArrayList(); + + + /** + * The executor service used for running the background simulations. + * This uses a fixed-sized thread pool for all background simulations + * with all threads in daemon mode and with minimum priority. + */ + private static final Executor backgroundSimulationExecutor; + static { + backgroundSimulationExecutor = Executors.newFixedThreadPool(Prefs.getMaxThreadCount(), + new ThreadFactory() { + private ThreadFactory factory = Executors.defaultThreadFactory(); + @Override + public Thread newThread(Runnable r) { + Thread t = factory.newThread(r); + t.setDaemon(true); + t.setPriority(Thread.MIN_PRIORITY); + return t; + } + }); + } + + + public RocketPanel(OpenRocketDocument document) { + + this.document = document; + configuration = document.getDefaultConfiguration(); + + // TODO: FUTURE: calculator selection + calculator = new BarrowmanCalculator(configuration); + + // Create figure and custom scroll pane + figure = new RocketFigure(configuration); + + scrollPane = new ScaleScrollPane(figure) { + @Override + public void mouseClicked(MouseEvent event) { + handleMouseClick(event); + } + }; + scrollPane.getViewport().setScrollMode(JViewport.SIMPLE_SCROLL_MODE); + scrollPane.setFitting(true); + + createPanel(); + + configuration.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + System.out.println("Configuration changed, calling updateFigure"); + updateExtras(); + figure.updateFigure(); + } + }); + } + + + /** + * Creates the layout and components of the panel. + */ + private void createPanel() { + setLayout(new MigLayout("","[shrink][grow]","[shrink][shrink][grow][shrink]")); + + setPreferredSize(new Dimension(800,300)); + + + //// Create toolbar + + // Side/back buttons + FigureTypeAction action = new FigureTypeAction(RocketFigure.TYPE_SIDE); + action.putValue(Action.NAME, "Side view"); + action.putValue(Action.SHORT_DESCRIPTION, "Side view"); + JToggleButton toggle = new JToggleButton(action); + add(toggle,"spanx, split"); + + action = new FigureTypeAction(RocketFigure.TYPE_BACK); + action.putValue(Action.NAME, "Back view"); + action.putValue(Action.SHORT_DESCRIPTION, "Rear view"); + toggle = new JToggleButton(action); + add(toggle,"gap rel"); + + + // Zoom level selector + ScaleSelector scaleSelector = new ScaleSelector(scrollPane); + add(scaleSelector); + + + + // Stage selector + StageSelector stageSelector = new StageSelector(configuration); + add(stageSelector,""); + + + + // Motor configuration selector + + JLabel label = new JLabel("Motor configuration:"); + label.setHorizontalAlignment(JLabel.RIGHT); + add(label,"growx, right"); + add(new JComboBox(new MotorConfigurationModel(configuration)),"wrap"); + + + + + + // Create slider and scroll pane + + DoubleModel theta = new DoubleModel(figure,"Rotation", + UnitGroup.UNITS_ANGLE,0,2*Math.PI); + UnitSelector us = new UnitSelector(theta,true); + us.setHorizontalAlignment(JLabel.CENTER); + add(us,"alignx 50%, growx"); + + // Add the rocket figure + add(scrollPane,"grow, spany 2, wmin 300lp, hmin 100lp, wrap"); + + + // Add rotation slider + // Minimum size to fit "360deg" + JLabel l = new JLabel("360\u00b0"); + Dimension d = l.getPreferredSize(); + + add(new BasicSlider(theta.getSliderModel(0,2*Math.PI),JSlider.VERTICAL,true), + "ax 50%, wrap, width "+(d.width+6)+"px:null:null, growy"); + + + infoMessage = new JLabel("" + + "Click to select    " + + "Shift+click to select other    " + + "Double-click to edit    " + + "Click+drag to move"); + infoMessage.setFont(new Font("Sans Serif", Font.PLAIN, 9)); + add(infoMessage,"skip, span, gapleft 25, wrap"); + + addExtras(); + } + + + + public RocketFigure getFigure() { + return figure; + } + + public AerodynamicCalculator getCalculator() { + return calculator; + } + + public Configuration getConfiguration() { + return configuration; + } + + public void setSelectionModel(TreeSelectionModel m) { + if (selectionModel != null) { + selectionModel.removeTreeSelectionListener(this); + } + selectionModel = m; + selectionModel.addTreeSelectionListener(this); + valueChanged((TreeSelectionEvent)null); // updates FigureParameters + } + + + + /** + * Return the angle of attack used in CP calculation. NaN signifies the default value + * of zero. + * @return the angle of attack used, or NaN. + */ + public double getCPAOA() { + return cpAOA; + } + + /** + * Set the angle of attack to be used in CP calculation. A value of NaN signifies that + * the default AOA (zero) should be used. + * @param aoa the angle of attack to use, or NaN + */ + public void setCPAOA(double aoa) { + if (MathUtil.equals(aoa, cpAOA) || + (Double.isNaN(aoa) && Double.isNaN(cpAOA))) + return; + cpAOA = aoa; + updateExtras(); + figure.updateFigure(); + fireChangeEvent(); + } + + public double getCPTheta() { + return cpTheta; + } + + public void setCPTheta(double theta) { + if (MathUtil.equals(theta, cpTheta) || + (Double.isNaN(theta) && Double.isNaN(cpTheta))) + return; + cpTheta = theta; + if (!Double.isNaN(theta)) + figure.setRotation(theta); + updateExtras(); + figure.updateFigure(); + fireChangeEvent(); + } + + public double getCPMach() { + return cpMach; + } + + public void setCPMach(double mach) { + if (MathUtil.equals(mach, cpMach) || + (Double.isNaN(mach) && Double.isNaN(cpMach))) + return; + cpMach = mach; + updateExtras(); + figure.updateFigure(); + fireChangeEvent(); + } + + public double getCPRoll() { + return cpRoll; + } + + public void setCPRoll(double roll) { + if (MathUtil.equals(roll, cpRoll) || + (Double.isNaN(roll) && Double.isNaN(cpRoll))) + return; + cpRoll = roll; + updateExtras(); + figure.updateFigure(); + fireChangeEvent(); + } + + + + @Override + public void addChangeListener(ChangeListener listener) { + listeners.add(0,listener); + } + @Override + public void removeChangeListener(ChangeListener listener) { + listeners.remove(listener); + } + + protected void fireChangeEvent() { + ChangeEvent e = new ChangeEvent(this); + ChangeListener[] list = listeners.toArray(new ChangeListener[0]); + for (ChangeListener l: list) { + l.stateChanged(e); + } + } + + + + + /** + * Handle clicking on figure shapes. The functioning is the following: + * + * Get the components clicked. + * If no component is clicked, do nothing. + * If the primary currently selected component is in the set, keep it, + * unless the selector specified is pressed. If it is pressed, cycle to + * the next component. Otherwise select the first component in the list. + */ + public static final int CYCLE_SELECTION_MODIFIER = InputEvent.SHIFT_DOWN_MASK; + + private void handleMouseClick(MouseEvent event) { + if (event.getButton() != MouseEvent.BUTTON1) + return; + Point p0 = event.getPoint(); + Point p1 = scrollPane.getViewport().getViewPosition(); + int x = p0.x + p1.x; + int y = p0.y + p1.y; + + RocketComponent[] clicked = figure.getComponentsByPoint(x, y); + + // If no component is clicked, do nothing + if (clicked.length == 0) + return; + + // Check whether the currently selected component is in the clicked components. + TreePath path = selectionModel.getSelectionPath(); + if (path != null) { + RocketComponent current = (RocketComponent)path.getLastPathComponent(); + path = null; + for (int i=0; i 0.000001) + cpx = cp.x; + else + cpx = Double.NaN; + + if (cg.weight > 0.000001) + cgx = cg.x; + else + cgx = Double.NaN; + + // Length bound is assumed to be tight + double length = 0, diameter = 0; + Collection bounds = configuration.getBounds(); + if (!bounds.isEmpty()) { + double minX = Double.POSITIVE_INFINITY, maxX = Double.NEGATIVE_INFINITY; + for (Coordinate c: bounds) { + if (c.x < minX) + minX = c.x; + if (c.x > maxX) + maxX = c.x; + } + length = maxX - minX; + } + + for (RocketComponent c: configuration) { + if (c instanceof SymmetricComponent) { + double d1 = ((SymmetricComponent)c).getForeRadius() * 2; + double d2 = ((SymmetricComponent)c).getAftRadius() * 2; + diameter = MathUtil.max(diameter, d1, d2); + } + } + + extraText.setCG(cgx); + extraText.setCP(cpx); + extraText.setLength(length); + extraText.setDiameter(diameter); + extraText.setMass(cg.weight); + extraText.setWarnings(warnings); + + + if (figure.getType() == RocketFigure.TYPE_SIDE && length > 0) { + + // TODO: LOW: Y-coordinate and rotation + extraCP.setPosition(cpx * RocketFigure.EXTRA_SCALE, 0); + extraCG.setPosition(cgx * RocketFigure.EXTRA_SCALE, 0); + + } else { + + extraCP.setPosition(Double.NaN, Double.NaN); + extraCG.setPosition(Double.NaN, Double.NaN); + + } + + + //////// Flight simulation in background + + // Check whether to compute or not + if (!Prefs.computeFlightInBackground()) { + extraText.setFlightData(null); + extraText.setCalculatingData(false); + stopBackgroundSimulation(); + return; + } + + // Check whether data is already up to date + if (flightDataFunctionalID == configuration.getRocket().getFunctionalModID() && + flightDataMotorID == configuration.getMotorConfigurationID()) { + return; + } + + flightDataFunctionalID = configuration.getRocket().getFunctionalModID(); + flightDataMotorID = configuration.getMotorConfigurationID(); + + // Stop previous computation (if any) + stopBackgroundSimulation(); + + // Check that configuration has motors + if (!configuration.hasMotors()) { + extraText.setFlightData(FlightData.NaN_DATA); + extraText.setCalculatingData(false); + return; + } + + // Start calculation process + extraText.setCalculatingData(true); + + Rocket duplicate = configuration.getRocket().copy(); + Simulation simulation = Prefs.getBackgroundSimulation(duplicate); + simulation.getConditions().setMotorConfigurationID( + configuration.getMotorConfigurationID()); + + backgroundSimulationWorker = new BackgroundSimulationWorker(simulation); + backgroundSimulationExecutor.execute(backgroundSimulationWorker); + } + + /** + * Cancels the current background simulation worker, if any. + */ + private void stopBackgroundSimulation() { + if (backgroundSimulationWorker != null) { + backgroundSimulationWorker.cancel(true); + backgroundSimulationWorker = null; + } + } + + + /** + * A SimulationWorker that simulates the rocket flight in the background and + * sets the results to the extra text when finished. The worker can be cancelled + * if necessary. + */ + private class BackgroundSimulationWorker extends SimulationWorker { + + public BackgroundSimulationWorker(Simulation sim) { + super(sim); + } + + @Override + protected FlightData doInBackground() { + + // Pause a little while to allow faster UI reaction + try { + Thread.sleep(300); + } catch (InterruptedException ignore) { } + if (isCancelled() || backgroundSimulationWorker != this) + return null; + + return super.doInBackground(); + } + + @Override + protected void simulationDone() { + // Do nothing if cancelled + if (isCancelled() || backgroundSimulationWorker != this) // Double-check + return; + + backgroundSimulationWorker = null; + extraText.setFlightData(simulation.getSimulatedData()); + extraText.setCalculatingData(false); + figure.repaint(); + } + + @Override + protected SimulationListener[] getExtraListeners() { + return new SimulationListener[] { + InterruptListener.INSTANCE, + ApogeeEndListener.INSTANCE + }; + } + + @Override + protected void simulationInterrupted(Throwable t) { + // Do nothing on cancel, set N/A data otherwise + if (isCancelled() || backgroundSimulationWorker != this) // Double-check + return; + + backgroundSimulationWorker = null; + extraText.setFlightData(FlightData.NaN_DATA); + extraText.setCalculatingData(false); + figure.repaint(); + } + } + + + + /** + * Adds the extra data to the figure. Currently this includes the CP and CG carets. + */ + private void addExtras() { + figure.clearRelativeExtra(); + extraCG = new CGCaret(0,0); + extraCP = new CPCaret(0,0); + extraText = new RocketInfo(configuration); + updateExtras(); + figure.addRelativeExtra(extraCP); + figure.addRelativeExtra(extraCG); + figure.addAbsoluteExtra(extraText); + } + + + /** + * Updates the selection in the FigureParameters and repaints the figure. + * Ignores the event itself. + */ + public void valueChanged(TreeSelectionEvent e) { + TreePath[] paths = selectionModel.getSelectionPaths(); + if (paths==null) { + figure.setSelection(null); + return; + } + + RocketComponent[] components = new RocketComponent[paths.length]; + for (int i=0; iAction that shows whether the figure type is the type + * given in the constructor. + * + * @author Sampo Niskanen + */ + private class FigureTypeAction extends AbstractAction implements ChangeListener { + private final int type; + + public FigureTypeAction(int type) { + this.type = type; + stateChanged(null); + figure.addChangeListener(this); + } + + public void actionPerformed(ActionEvent e) { + boolean state = (Boolean)getValue(Action.SELECTED_KEY); + if (state == true) { + // This view has been selected + figure.setType(type); + updateExtras(); + } + stateChanged(null); + } + + public void stateChanged(ChangeEvent e) { + putValue(Action.SELECTED_KEY,figure.getType() == type); + } + } + +} diff --git a/src/net/sf/openrocket/gui/scalefigure/ScaleFigure.java b/src/net/sf/openrocket/gui/scalefigure/ScaleFigure.java new file mode 100644 index 000000000..161c8353b --- /dev/null +++ b/src/net/sf/openrocket/gui/scalefigure/ScaleFigure.java @@ -0,0 +1,67 @@ +package net.sf.openrocket.gui.scalefigure; + +import java.awt.Dimension; + +import net.sf.openrocket.util.ChangeSource; + + +public interface ScaleFigure extends ChangeSource { + + /** + * Extra scaling applied to the figure. The f***ing Java JRE doesn't know + * how to draw shapes when using very large scaling factors, so this must + * be manually applied to every single shape used. + *

+ * The scaling factor used is divided by this value, and every coordinate used + * in the figures must be multiplied by this factor. + */ + public static final double EXTRA_SCALE = 1000; + + /** + * Shorthand for {@link #EXTRA_SCALE}. + */ + public static final double S = EXTRA_SCALE; + + + /** + * Set the scale level of the figure. A scale value of 1.0 indicates an original + * size when using the current DPI level. + * + * @param scale the scale level. + */ + public void setScaling(double scale); + + + /** + * Set the scale level so that the figure fits into the given bounds. + * + * @param bounds the bounds of the figure. + */ + public void setScaling(Dimension bounds); + + + /** + * Return the scale level of the figure. A scale value of 1.0 indicates an original + * size when using the current DPI level. + * + * @return the current scale level. + */ + public double getScaling(); + + + /** + * Return the scale of the figure on px/m. + * + * @return the current scale value. + */ + public double getAbsoluteScale(); + + + /** + * Return the pixel coordinates of the figure origin. + * + * @return the pixel coordinates of the figure origin. + */ + public Dimension getOrigin(); + +} diff --git a/src/net/sf/openrocket/gui/scalefigure/ScaleScrollPane.java b/src/net/sf/openrocket/gui/scalefigure/ScaleScrollPane.java new file mode 100644 index 000000000..e1d08b6e7 --- /dev/null +++ b/src/net/sf/openrocket/gui/scalefigure/ScaleScrollPane.java @@ -0,0 +1,374 @@ +package net.sf.openrocket.gui.scalefigure; + + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; + +import javax.swing.BorderFactory; +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JViewport; +import javax.swing.ScrollPaneConstants; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import net.sf.openrocket.gui.UnitSelector; +import net.sf.openrocket.gui.adaptors.DoubleModel; +import net.sf.openrocket.unit.Tick; +import net.sf.openrocket.unit.Unit; +import net.sf.openrocket.unit.UnitGroup; + + + +/** + * A scroll pane that holds a {@link ScaleFigure} and includes rulers that show + * natural units. The figure can be moved by dragging on the figure. + *

+ * This class implements both MouseListener and + * MouseMotionListener. If subclasses require extra functionality + * (e.g. checking for clicks) then these methods may be overridden, and only unhandled + * events passed to this class. + * + * @author Sampo Niskanen + */ +public class ScaleScrollPane extends JScrollPane + implements MouseListener, MouseMotionListener { + + public static final int RULER_SIZE = 20; + public static final int MINOR_TICKS = 3; + public static final int MAJOR_TICKS = 30; + + + private JComponent component; + private ScaleFigure figure; + private JViewport viewport; + + private DoubleModel rulerUnit; + private Ruler horizontalRuler; + private Ruler verticalRuler; + + private final boolean allowFit; + + private boolean fit = false; + + + public ScaleScrollPane(JComponent component) { + this(component, true); + } + + public ScaleScrollPane(JComponent component, boolean allowFit) { + super(component); +// super(component, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, +// JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); + + if (!(component instanceof ScaleFigure)) { + throw new IllegalArgumentException("component must implement ScaleFigure"); + } + + this.component = component; + this.figure = (ScaleFigure)component; + this.allowFit = allowFit; + + + rulerUnit = new DoubleModel(0.0,UnitGroup.UNITS_LENGTH); + rulerUnit.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + ScaleScrollPane.this.component.repaint(); + } + }); + horizontalRuler = new Ruler(Ruler.HORIZONTAL); + verticalRuler = new Ruler(Ruler.VERTICAL); + this.setColumnHeaderView(horizontalRuler); + this.setRowHeaderView(verticalRuler); + + UnitSelector selector = new UnitSelector(rulerUnit); + selector.setFont(new Font("SansSerif", Font.PLAIN, 8)); + this.setCorner(JScrollPane.UPPER_LEFT_CORNER, selector); + this.setCorner(JScrollPane.UPPER_RIGHT_CORNER, new JPanel()); + this.setCorner(JScrollPane.LOWER_LEFT_CORNER, new JPanel()); + this.setCorner(JScrollPane.LOWER_RIGHT_CORNER, new JPanel()); + + this.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY)); + + + viewport = this.getViewport(); + viewport.addMouseListener(this); + viewport.addMouseMotionListener(this); + + figure.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + horizontalRuler.updateSize(); + verticalRuler.updateSize(); + if (fit) { + setFitting(true); + } + } + }); + + viewport.addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent e) { + if (fit) { + setFitting(true); + } + } + }); + + } + + public ScaleFigure getFigure() { + return figure; + } + + + public boolean isFittingAllowed() { + return allowFit; + } + + public boolean isFitting() { + return fit; + } + + public void setFitting(boolean fit) { + if (fit && !allowFit) { + throw new RuntimeException("Attempting to fit figure not allowing fit."); + } + this.fit = fit; + if (fit) { + setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER); + validate(); + Dimension view = viewport.getExtentSize(); + figure.setScaling(view); + } else { + setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); + setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); + } + } + + + + public double getScaling() { + return figure.getScaling(); + } + + public double getScale() { + return figure.getAbsoluteScale(); + } + + public void setScaling(double scale) { + if (fit) { + setFitting(false); + } + figure.setScaling(scale); + horizontalRuler.repaint(); + verticalRuler.repaint(); + } + + + public Unit getCurrentUnit() { + return rulerUnit.getCurrentUnit(); + } + + + //////////////// Mouse handlers //////////////// + + + private int dragStartX=0; + private int dragStartY=0; + private Rectangle dragRectangle = null; + + @Override + public void mousePressed(MouseEvent e) { + dragStartX = e.getX(); + dragStartY = e.getY(); + dragRectangle = viewport.getViewRect(); + } + + @Override + public void mouseReleased(MouseEvent e) { + dragRectangle = null; + } + + @Override + public void mouseDragged(MouseEvent e) { + if (dragRectangle==null) { + return; + } + + dragRectangle.setLocation(dragStartX-e.getX(),dragStartY-e.getY()); + + dragStartX = e.getX(); + dragStartY = e.getY(); + + viewport.scrollRectToVisible(dragRectangle); + } + + @Override + public void mouseClicked(MouseEvent e) { + } + + @Override + public void mouseEntered(MouseEvent e) { + } + + @Override + public void mouseExited(MouseEvent e) { + } + + @Override + public void mouseMoved(MouseEvent e) { + } + + + + //////////////// The view port rulers //////////////// + + + private class Ruler extends JComponent { + public static final int HORIZONTAL = 0; + public static final int VERTICAL = 1; + + private final int orientation; + + public Ruler(int orientation) { + this.orientation = orientation; + updateSize(); + + rulerUnit.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + Ruler.this.repaint(); + } + }); + } + + + public void updateSize() { + Dimension d = component.getPreferredSize(); + if (orientation == HORIZONTAL) { + setPreferredSize(new Dimension(d.width+10,RULER_SIZE)); + } else { + setPreferredSize(new Dimension(RULER_SIZE,d.height+10)); + } + revalidate(); + repaint(); + } + + private double fromPx(int px) { + Dimension origin = figure.getOrigin(); + if (orientation == HORIZONTAL) { + px -= origin.width; + } else { +// px = -(px - origin.height); + px -= origin.height; + } + return px/figure.getAbsoluteScale(); + } + + private int toPx(double l) { + Dimension origin = figure.getOrigin(); + int px = (int)(l * figure.getAbsoluteScale() + 0.5); + if (orientation == HORIZONTAL) { + px += origin.width; + } else { + px = px + origin.height; +// px += origin.height; + } + return px; + } + + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + Graphics2D g2 = (Graphics2D)g; + + Rectangle area = g2.getClipBounds(); + + // Fill area with background color + g2.setColor(getBackground()); + g2.fillRect(area.x, area.y, area.width, area.height+100); + + + int startpx,endpx; + if (orientation == HORIZONTAL) { + startpx = area.x; + endpx = area.x+area.width; + } else { + startpx = area.y; + endpx = area.y+area.height; + } + + Unit unit = rulerUnit.getCurrentUnit(); + double start,end,minor,major; + start = fromPx(startpx); + end = fromPx(endpx); + minor = MINOR_TICKS/figure.getAbsoluteScale(); + major = MAJOR_TICKS/figure.getAbsoluteScale(); + + Tick[] ticks = unit.getTicks(start, end, minor, major); + + + // Set color & hints + g2.setColor(Color.BLACK); + g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, + RenderingHints.VALUE_STROKE_NORMALIZE); + g2.setRenderingHint(RenderingHints.KEY_RENDERING, + RenderingHints.VALUE_RENDER_QUALITY); + g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, + RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + + for (Tick t: ticks) { + int position = toPx(t.value); + drawTick(g2,position,t); + } + } + + private void drawTick(Graphics g, int position, Tick t) { + int length; + String str = null; + if (t.major) { + length = RULER_SIZE/2; + } else { + if (t.notable) + length = RULER_SIZE/3; + else + length = RULER_SIZE/6; + } + + // Set font + if (t.major) { + str = rulerUnit.getCurrentUnit().toString(t.value); + if (t.notable) + g.setFont(new Font("SansSerif", Font.BOLD, 9)); + else + g.setFont(new Font("SansSerif", Font.PLAIN, 9)); + } + + // Draw tick & text + if (orientation == HORIZONTAL) { + g.drawLine(position, RULER_SIZE-length, position, RULER_SIZE); + if (str != null) + g.drawString(str, position, RULER_SIZE-length-1); + } else { + g.drawLine(RULER_SIZE-length, position, RULER_SIZE, position); + if (str != null) + g.drawString(str, 1, position-1); + } + } + } +} diff --git a/src/net/sf/openrocket/gui/scalefigure/ScaleSelector.java b/src/net/sf/openrocket/gui/scalefigure/ScaleSelector.java new file mode 100644 index 000000000..d4452d6c9 --- /dev/null +++ b/src/net/sf/openrocket/gui/scalefigure/ScaleSelector.java @@ -0,0 +1,155 @@ +package net.sf.openrocket.gui.scalefigure; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.text.DecimalFormat; +import java.util.Arrays; + +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JPanel; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.util.Icons; + +public class ScaleSelector extends JPanel { + + // Ready zoom settings + private static final DecimalFormat PERCENT_FORMAT = new DecimalFormat("0.#%"); + + private static final double[] ZOOM_LEVELS = { 0.15, 0.25, 0.5, 0.75, 1.0, 1.5, 2.0 }; + private static final String ZOOM_FIT = "Fit"; + private static final String[] ZOOM_SETTINGS; + static { + ZOOM_SETTINGS = new String[ZOOM_LEVELS.length+1]; + for (int i=0; i ZOOM_LEVELS[i]+0.05 && scale < ZOOM_LEVELS[i+1]+0.05) + return ZOOM_LEVELS[i]; + } + if (scale > ZOOM_LEVELS[ZOOM_LEVELS.length/2]) { + // scale is large, drop to next lowest full 100% + scale = Math.ceil(scale-1.05); + return Math.max(scale, ZOOM_LEVELS[i]); + } + // scale is small + return scale/1.5; + } + + + private double getNextScale(double scale) { + int i; + for (i=0; i ZOOM_LEVELS[i]-0.05 && scale < ZOOM_LEVELS[i+1]-0.05) + return ZOOM_LEVELS[i+1]; + } + if (scale > ZOOM_LEVELS[ZOOM_LEVELS.length/2]) { + // scale is large, give next full 100% + scale = Math.floor(scale+1.05); + return scale; + } + return scale*1.5; + } + +} diff --git a/src/net/sf/openrocket/material/Material.java b/src/net/sf/openrocket/material/Material.java new file mode 100644 index 000000000..f59885da9 --- /dev/null +++ b/src/net/sf/openrocket/material/Material.java @@ -0,0 +1,211 @@ +package net.sf.openrocket.material; + +import net.sf.openrocket.unit.Unit; +import net.sf.openrocket.unit.UnitGroup; +import net.sf.openrocket.util.MathUtil; + +/** + * A class for different material types. Each material has a name and density. + * The interpretation of the density depends on the material type. For + * {@link Type#BULK} it is kg/m^3, for {@link Type#SURFACE} km/m^2. + *

+ * Objects of this type are immutable. + * + * @author Sampo Niskanen + */ + +public abstract class Material implements Comparable { + + public enum Type { + LINE, + SURFACE, + BULK + } + + public static class Line extends Material { + public Line(String name, double density) { + super(name, density); + } + + @Override + public UnitGroup getUnitGroup() { + return UnitGroup.UNITS_DENSITY_LINE; + } + + @Override + public Type getType() { + return Type.LINE; + } + } + + public static class Surface extends Material { + + public Surface(String name, double density) { + super(name, density); + } + + @Override + public UnitGroup getUnitGroup() { + return UnitGroup.UNITS_DENSITY_SURFACE; + } + + @Override + public Type getType() { + return Type.SURFACE; + } + + @Override + public String toStorableString() { + return super.toStorableString(); + } + } + + public static class Bulk extends Material { + public Bulk(String name, double density) { + super(name, density); + } + + @Override + public UnitGroup getUnitGroup() { + return UnitGroup.UNITS_DENSITY_BULK; + } + + @Override + public Type getType() { + return Type.BULK; + } + } + + + + private final String name; + private final double density; + + + public Material(String name, double density) { + this.name = name; + this.density = density; + } + + + + public double getDensity() { + return density; + } + + public String getName() { + return name; + } + + public String getName(Unit u) { + return name + " (" + u.toStringUnit(density) + ")"; + } + + public abstract UnitGroup getUnitGroup(); + public abstract Type getType(); + + @Override + public String toString() { + return getName(getUnitGroup().getDefaultUnit()); + } + + + /** + * Compares this object to another object. Material objects are equal if and only if + * their types, names and densities are identical. + */ + @Override + public boolean equals(Object o) { + if (o == null) + return false; + if (this.getClass() != o.getClass()) + return false; + Material m = (Material)o; + return ((m.name.equals(this.name)) && + MathUtil.equals(m.density, this.density)); + } + + + /** + * A hashCode() method giving a hash code compatible with the equals() method. + */ + @Override + public int hashCode() { + return name.hashCode() + (int)(density*1000); + } + + + /** + * Order the materials according to their name, secondarily according to density. + */ + public int compareTo(Material o) { + int c = this.name.compareTo(o.name); + if (c != 0) { + return c; + } else { + return (int)((this.density - o.density)*1000); + } + } + + + + public static Material newMaterial(Type type, String name, double density) { + switch (type) { + case LINE: + return new Material.Line(name, density); + + case SURFACE: + return new Material.Surface(name, density); + + case BULK: + return new Material.Bulk(name, density); + + default: + throw new IllegalArgumentException("Unknown material type: "+type); + } + } + + + public String toStorableString() { + return getType().name() + "|" + name.replace('|', ' ') + '|' + density; + } + + public static Material fromStorableString(String str) { + String[] split = str.split("\\|",3); + if (split.length < 3) + throw new IllegalArgumentException("Illegal material string: "+str); + + Type type = null; + String name; + double density; + + try { + type = Type.valueOf(split[0]); + } catch (Exception e) { + throw new IllegalArgumentException("Illegal material string: "+str, e); + } + + name = split[1]; + + try { + density = Double.parseDouble(split[2]); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Illegal material string: "+str, e); + } + + switch (type) { + case BULK: + return new Material.Bulk(name, density); + + case SURFACE: + return new Material.Surface(name, density); + + case LINE: + return new Material.Line(name, density); + + default: + throw new IllegalArgumentException("Illegal material string: "+str); + } + } + +} diff --git a/src/net/sf/openrocket/rocketcomponent/BodyComponent.java b/src/net/sf/openrocket/rocketcomponent/BodyComponent.java new file mode 100644 index 000000000..f17bd3d9f --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/BodyComponent.java @@ -0,0 +1,79 @@ +package net.sf.openrocket.rocketcomponent; + + +/** + * Class to represent a body object. The object can be described as a function of + * the cylindrical coordinates x and angle theta as r = f(x,theta). The component + * need not be symmetrical in any way (e.g. square tube, slanted cone etc). + * + * It defines the methods getRadius(x,theta) and getInnerRadius(x,theta), as well + * as get/setLength(). + * + * @author Sampo Niskanen + */ + +public abstract class BodyComponent extends ExternalComponent { + + /** + * Default constructor. Sets the relative position to POSITION_RELATIVE_AFTER, + * i.e. body components come after one another. + */ + public BodyComponent() { + super(RocketComponent.Position.AFTER); + } + + + + /** + * Get the outer radius of the component at cylindrical coordinate (x,theta). + * + * Note that the return value may be negative for a slanted object. + * + * @param x Distance in x direction + * @param theta Angle about the x-axis + * @return Distance to the outer edge of the object + */ + public abstract double getRadius(double x, double theta); + + + /** + * Get the inner radius of the component at cylindrical coordinate (x,theta). + * + * Note that the return value may be negative for a slanted object. + * + * @param x Distance in x direction + * @param theta Angle about the x-axis + * @return Distance to the inner edge of the object + */ + public abstract double getInnerRadius(double x, double theta); + + + + /** + * Sets the length of the body component. + */ + public void setLength(double length) { + if (this.length == length) + return; + this.length = Math.max(length,0); + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + } + + + /** + * Check whether the given type can be added to this component. BodyComponents allow any + * InternalComponents or ExternalComponents, excluding BodyComponents, to be added. + * + * @param type The RocketComponent class type to add. + * @return Whether such a component can be added. + */ + @Override + public boolean isCompatible(Class type) { + if (InternalComponent.class.isAssignableFrom(type)) + return true; + if (ExternalComponent.class.isAssignableFrom(type) && + !BodyComponent.class.isAssignableFrom(type)) + return true; + return false; + } +} diff --git a/src/net/sf/openrocket/rocketcomponent/BodyTube.java b/src/net/sf/openrocket/rocketcomponent/BodyTube.java new file mode 100644 index 000000000..d22ab67d3 --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/BodyTube.java @@ -0,0 +1,388 @@ +package net.sf.openrocket.rocketcomponent; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; + +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.MathUtil; + + +/** + * Rocket body tube component. Has only two parameters, a radius and length. + * + * @author Sampo Niskanen + */ + +public class BodyTube extends SymmetricComponent implements MotorMount { + + private double radius=0; + private boolean autoRadius = false; // Radius chosen automatically based on parent component + + // When changing the inner radius, thickness is modified + + private boolean motorMount = false; + private HashMap ejectionDelays = new HashMap(); + private HashMap motors = new HashMap(); + private IgnitionEvent ignitionEvent = IgnitionEvent.AUTOMATIC; + private double ignitionDelay = 0; + private double overhang = 0; + + + + public BodyTube() { + super(); + this.length = 8*DEFAULT_RADIUS; + this.radius = DEFAULT_RADIUS; + this.autoRadius = true; + } + + public BodyTube(double length, double radius) { + super(); + this.radius = Math.max(radius,0); + this.length = Math.max(length,0); + } + + + public BodyTube(double length, double radius, boolean filled) { + this(length,radius); + this.filled = filled; + } + + public BodyTube(double length, double radius, double thickness) { + this(length,radius); + this.filled = false; + this.thickness = thickness; + } + + + /************ Get/set component parameter methods ************/ + + /** + * Return the outer radius of the body tube. + */ + public double getRadius() { + if (autoRadius) { + // Return auto radius from front or rear + double r = -1; + SymmetricComponent c = this.getPreviousSymmetricComponent(); + if (c != null) { + r = c.getFrontAutoRadius(); + } + if (r < 0) { + c = this.getNextSymmetricComponent(); + if (c != null) { + r = c.getRearAutoRadius(); + } + } + if (r < 0) + r = DEFAULT_RADIUS; + return r; + } + return radius; + } + + + /** + * Set the outer radius of the body tube. If the radius is less than the wall thickness, + * the wall thickness is decreased accordingly of the value of the radius. + * This method sets the automatic radius off. + */ + public void setRadius(double radius) { + if ((this.radius == radius) && (autoRadius==false)) + return; + + this.autoRadius = false; + this.radius = Math.max(radius,0); + + if (this.thickness > this.radius) + this.thickness = this.radius; + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + } + + + /** + * Returns whether the radius is selected automatically or not. + * Returns false also in case automatic radius selection is not possible. + */ + public boolean isRadiusAutomatic() { + return autoRadius; + } + + /** + * Sets whether the radius is selected automatically or not. + */ + public void setRadiusAutomatic(boolean auto) { + if (autoRadius == auto) + return; + + autoRadius = auto; + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + } + + + @Override + public double getAftRadius() { return getRadius(); } + @Override + public double getForeRadius() { return getRadius(); } + @Override + public boolean isAftRadiusAutomatic() { return isRadiusAutomatic(); } + @Override + public boolean isForeRadiusAutomatic() { return isRadiusAutomatic(); } + + + + @Override + protected double getFrontAutoRadius() { + if (isRadiusAutomatic()) { + // Search for previous SymmetricComponent + SymmetricComponent c = this.getPreviousSymmetricComponent(); + if (c != null) { + return c.getFrontAutoRadius(); + } else { + return -1; + } + } + return getRadius(); + } + + @Override + protected double getRearAutoRadius() { + if (isRadiusAutomatic()) { + // Search for next SymmetricComponent + SymmetricComponent c = this.getNextSymmetricComponent(); + if (c != null) { + return c.getRearAutoRadius(); + } else { + return -1; + } + } + return getRadius(); + } + + + + + + + + public double getInnerRadius() { + if (filled) + return 0; + return Math.max(getRadius()-thickness, 0); + } + + public void setInnerRadius(double r) { + setThickness(getRadius()-r); + } + + + + + /** + * Return the component name. + */ + @Override + public String getComponentName() { + return "Body tube"; + } + + + /************ Component calculations ***********/ + + // From SymmetricComponent + /** + * Returns the outer radius at the position x. This returns the same value as getRadius(). + */ + @Override + public double getRadius(double x) { + return getRadius(); + } + + /** + * Returns the inner radius at the position x. If the tube is filled, returns always zero. + */ + @Override + public double getInnerRadius(double x) { + if (filled) + return 0.0; + else + return Math.max(getRadius()-thickness,0); + } + + + /** + * Returns the body tube's center of gravity. + */ + @Override + public Coordinate getComponentCG() { + return new Coordinate(length/2,0,0,getComponentMass()); + } + + /** + * Returns the body tube's volume. + */ + @Override + public double getComponentVolume() { + double r = getRadius(); + if (filled) + return getFilledVolume(r,length); + else + return getFilledVolume(r,length) - getFilledVolume(getInnerRadius(0),length); + } + + + @Override + public double getLongitudalUnitInertia() { + // 1/12 * (3 * (r1^2 + r2^2) + h^2) + return (3 * (MathUtil.pow2(getInnerRadius())) + MathUtil.pow2(getRadius()) + + MathUtil.pow2(getLength())) / 12; + } + + @Override + public double getRotationalUnitInertia() { + // 1/2 * (r1^2 + r2^2) + return (MathUtil.pow2(getInnerRadius()) + MathUtil.pow2(getRadius()))/2; + } + + + + + /** + * Helper function for cylinder volume. + */ + private static double getFilledVolume(double r, double l) { + return Math.PI * r*r * l; + } + + + /** + * Adds bounding coordinates to the given set. The body tube will fit within the + * convex hull of the points. + * + * Currently the points are simply a rectangular box around the body tube. + */ + @Override + public Collection getComponentBounds() { + Collection bounds = new ArrayList(8); + double r = getRadius(); + addBound(bounds,0,r); + addBound(bounds,length,r); + return bounds; + } + + + //////////////// Motor mount ///////////////// + + @Override + public boolean isMotorMount() { + return motorMount; + } + + @Override + public void setMotorMount(boolean mount) { + if (motorMount == mount) + return; + motorMount = mount; + fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE); + } + + @Override + public Motor getMotor(String id) { + return motors.get(id); + } + + @Override + public void setMotor(String id, Motor motor) { + Motor current = motors.get(id); + if ((motor == null && current == null) || + (motor != null && motor.equals(current))) + return; + motors.put(id, motor); + fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE); + } + + @Override + public double getMotorDelay(String id) { + Double delay = ejectionDelays.get(id); + if (delay == null) + return Motor.PLUGGED; + return delay; + } + + @Override + public void setMotorDelay(String id, double delay) { + ejectionDelays.put(id, delay); + fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE); + } + + @Override + public int getMotorCount() { + return 1; + } + + @Override + public double getMotorMountDiameter() { + return getInnerRadius()*2; + } + + @Override + public IgnitionEvent getIgnitionEvent() { + return ignitionEvent; + } + + @Override + public void setIgnitionEvent(IgnitionEvent event) { + if (ignitionEvent == event) + return; + ignitionEvent = event; + fireComponentChangeEvent(ComponentChangeEvent.EVENT_CHANGE); + } + + + @Override + public double getIgnitionDelay() { + return ignitionDelay; + } + + @Override + public void setIgnitionDelay(double delay) { + if (MathUtil.equals(delay, ignitionDelay)) + return; + ignitionDelay = delay; + fireComponentChangeEvent(ComponentChangeEvent.EVENT_CHANGE); + } + + + @Override + public double getMotorOverhang() { + return overhang; + } + + @Override + public void setMotorOverhang(double overhang) { + if (MathUtil.equals(this.overhang, overhang)) + return; + this.overhang = overhang; + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + } + + + + + /* + * (non-Javadoc) + * Copy the motor and ejection delay HashMaps. + * + * @see rocketcomponent.RocketComponent#copy() + */ + @SuppressWarnings("unchecked") + @Override + public RocketComponent copy() { + RocketComponent c = super.copy(); + ((BodyTube)c).motors = (HashMap) motors.clone(); + ((BodyTube)c).ejectionDelays = (HashMap) ejectionDelays.clone(); + return c; + } + +} diff --git a/src/net/sf/openrocket/rocketcomponent/Bulkhead.java b/src/net/sf/openrocket/rocketcomponent/Bulkhead.java new file mode 100644 index 000000000..c82aa97ca --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/Bulkhead.java @@ -0,0 +1,36 @@ +package net.sf.openrocket.rocketcomponent; + + +public class Bulkhead extends RadiusRingComponent { + + public Bulkhead() { + setOuterRadiusAutomatic(true); + setLength(0.002); + } + + @Override + public double getInnerRadius() { + return 0; + } + + @Override + public void setInnerRadius(double r) { + // No-op + } + + @Override + public void setOuterRadiusAutomatic(boolean auto) { + super.setOuterRadiusAutomatic(auto); + } + + @Override + public String getComponentName() { + return "Bulkhead"; + } + + @Override + public boolean isCompatible(Class type) { + return false; + } + +} diff --git a/src/net/sf/openrocket/rocketcomponent/CenteringRing.java b/src/net/sf/openrocket/rocketcomponent/CenteringRing.java new file mode 100644 index 000000000..515667a5d --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/CenteringRing.java @@ -0,0 +1,58 @@ +package net.sf.openrocket.rocketcomponent; + +import net.sf.openrocket.util.Coordinate; + + +public class CenteringRing extends RadiusRingComponent { + + public CenteringRing() { + setOuterRadiusAutomatic(true); + setInnerRadiusAutomatic(true); + setLength(0.002); + } + + + @Override + public double getInnerRadius() { + // Implement sibling inner radius automation + if (isInnerRadiusAutomatic()) { + innerRadius = 0; + for (RocketComponent sibling: this.getParent().getChildren()) { + if (!(sibling instanceof RadialParent)) // Excludes itself + continue; + + double pos1 = this.toRelative(Coordinate.NUL, sibling)[0].x; + double pos2 = this.toRelative(new Coordinate(getLength()), sibling)[0].x; + if (pos2 < 0 || pos1 > sibling.getLength()) + continue; + + innerRadius = Math.max(innerRadius, ((InnerTube)sibling).getOuterRadius()); + } + innerRadius = Math.min(innerRadius, getOuterRadius()); + } + + return super.getInnerRadius(); + } + + + @Override + public void setOuterRadiusAutomatic(boolean auto) { + super.setOuterRadiusAutomatic(auto); + } + + @Override + public void setInnerRadiusAutomatic(boolean auto) { + super.setInnerRadiusAutomatic(auto); + } + + @Override + public String getComponentName() { + return "Centering ring"; + } + + @Override + public boolean isCompatible(Class type) { + return false; + } + +} diff --git a/src/net/sf/openrocket/rocketcomponent/ClusterConfiguration.java b/src/net/sf/openrocket/rocketcomponent/ClusterConfiguration.java new file mode 100644 index 000000000..5019fccc9 --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/ClusterConfiguration.java @@ -0,0 +1,103 @@ +package net.sf.openrocket.rocketcomponent; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + + +/** + * Class that defines different cluster configurations available for the InnerTube. + * The class is immutable, and all the constructors are private. Therefore the only + * available cluster configurations are those available in the static fields. + * + * @author Sampo Niskanen + */ +public class ClusterConfiguration { + // Helper vars + private static final double R5 = 1.0/(2*Math.sin(2*Math.PI/10)); + private static final double SQRT2 = Math.sqrt(2); + private static final double SQRT3 = Math.sqrt(3); + + /** A single motor */ + public static final ClusterConfiguration SINGLE = new ClusterConfiguration("single", 0,0); + + /** Definitions of cluster configurations. Do not modify array. */ + public static final ClusterConfiguration[] CONFIGURATIONS = { + // Single row + SINGLE, + new ClusterConfiguration("double", -0.5,0, 0.5,0), + new ClusterConfiguration("3-row", -1.0,0, 0.0,0, 1.0,0), + new ClusterConfiguration("4-row", -1.5,0, -0.5,0, 0.5,0, 1.5,0), + + // Ring of tubes + new ClusterConfiguration("3-ring", -0.5,-1.0/(2*SQRT3), + 0.5,-1.0/(2*SQRT3), + 0, 1.0/SQRT3), + new ClusterConfiguration("4-ring", -0.5,0.5, 0.5,0.5, 0.5,-0.5, -0.5,-0.5), + new ClusterConfiguration("5-ring", 0,R5, + R5*Math.sin(2*Math.PI/5),R5*Math.cos(2*Math.PI/5), + R5*Math.sin(2*Math.PI*2/5),R5*Math.cos(2*Math.PI*2/5), + R5*Math.sin(2*Math.PI*3/5),R5*Math.cos(2*Math.PI*3/5), + R5*Math.sin(2*Math.PI*4/5),R5*Math.cos(2*Math.PI*4/5)), + new ClusterConfiguration("6-ring", 0,1, SQRT3/2,0.5, SQRT3/2,-0.5, + 0,-1, -SQRT3/2,-0.5, -SQRT3/2,0.5), + + // Centered with ring + new ClusterConfiguration("3-star", 0,0, 0,1, SQRT3/2,-0.5, -SQRT3/2,-0.5), + new ClusterConfiguration("4-star", 0,0, -1/SQRT2,1/SQRT2, 1/SQRT2,1/SQRT2, + 1/SQRT2,-1/SQRT2, -1/SQRT2,-1/SQRT2), + new ClusterConfiguration("5-star", 0,0, 0,1, + Math.sin(2*Math.PI/5),Math.cos(2*Math.PI/5), + Math.sin(2*Math.PI*2/5),Math.cos(2*Math.PI*2/5), + Math.sin(2*Math.PI*3/5),Math.cos(2*Math.PI*3/5), + Math.sin(2*Math.PI*4/5),Math.cos(2*Math.PI*4/5)), + new ClusterConfiguration("6-star", 0,0, 0,1, SQRT3/2,0.5, SQRT3/2,-0.5, + 0,-1, -SQRT3/2,-0.5, -SQRT3/2,0.5) + }; + + + private final List points; + private final String xmlName; + + private ClusterConfiguration(String xmlName, double... points) { + this.xmlName = xmlName; + if (points.length == 0 || points.length%2 == 1) { + throw new IllegalArgumentException("Illegal number of points specified: "+ + points.length); + } + List l = new ArrayList(points.length); + for (double d: points) + l.add(d); + + this.points = Collections.unmodifiableList(l); + } + + public String getXMLName() { + return xmlName; + } + + public int getClusterCount() { + return points.size()/2; + } + + public List getPoints() { + return points; // Unmodifiable + } + + /** + * Return the points rotated by rotation radians. + * @param rotation Rotation amount. + */ + public List getPoints(double rotation) { + double cos = Math.cos(rotation); + double sin = Math.sin(rotation); + List ret = new ArrayList(points.size()); + for (int i=0; i + * Note that the mass and CG overrides of the ComponentAssembly class + * overrides all sibling mass/CG as well as its own. + * + * @author Sampo Niskanen + */ +public abstract class ComponentAssembly extends RocketComponent { + + /** + * Sets the position of the components to POSITION_RELATIVE_AFTER. + * (Should have no effect.) + */ + public ComponentAssembly() { + super(RocketComponent.Position.AFTER); + } + + /** + * Null method (ComponentAssembly has no bounds of itself). + */ + @Override + public Collection getComponentBounds() { + return Collections.emptyList(); + } + + /** + * Null method (ComponentAssembly has no mass of itself). + */ + @Override + public Coordinate getComponentCG() { + return Coordinate.NUL; + } + + /** + * Null method (ComponentAssembly has no mass of itself). + */ + @Override + public double getComponentMass() { + return 0; + } + + /** + * Null method (ComponentAssembly has no mass of itself). + */ + @Override + public double getLongitudalUnitInertia() { + return 0; + } + + /** + * Null method (ComponentAssembly has no mass of itself). + */ + @Override + public double getRotationalUnitInertia() { + return 0; + } + + /** + * Components have no aerodynamic effect, so return false. + */ + @Override + public boolean isAerodynamic() { + return false; + } + + /** + * Component have no effect on mass, so return false (even though the override values + * may have an effect). + */ + @Override + public boolean isMassive() { + return false; + } + + @Override + public boolean getOverrideSubcomponents() { + return true; + } + + @Override + public void setOverrideSubcomponents(boolean override) { + // No-op + } + + @Override + public boolean isOverrideSubcomponentsEnabled() { + return false; + } + +} diff --git a/src/net/sf/openrocket/rocketcomponent/ComponentChangeEvent.java b/src/net/sf/openrocket/rocketcomponent/ComponentChangeEvent.java new file mode 100644 index 000000000..5e7fea076 --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/ComponentChangeEvent.java @@ -0,0 +1,94 @@ +package net.sf.openrocket.rocketcomponent; + +import javax.swing.event.ChangeEvent; + +public class ComponentChangeEvent extends ChangeEvent { + private static final long serialVersionUID = 1L; + + + /** A change that does not affect simulation results in any way (name, color, etc.) */ + public static final int NONFUNCTIONAL_CHANGE = 1; + /** A change that affects the mass properties of the rocket */ + public static final int MASS_CHANGE = 2; + /** A change that affects the aerodynamic properties of the rocket */ + public static final int AERODYNAMIC_CHANGE = 4; + /** A change that affects the mass and aerodynamic properties of the rocket */ + public static final int BOTH_CHANGE = MASS_CHANGE|AERODYNAMIC_CHANGE; // Mass & Aerodynamic + + /** A change that affects the rocket tree structure */ + public static final int TREE_CHANGE = 8; + /** A change caused by undo/redo. */ + public static final int UNDO_CHANGE = 16; + /** A change in the motor configurations or names */ + public static final int MOTOR_CHANGE = 32; + /** A change in the events occurring during flight. */ + public static final int EVENT_CHANGE = 64; + + /** A bit-field that contains all possible change types. */ + public static final int ALL_CHANGE = 0xFFFFFFFF; + + private final int type; + + + public ComponentChangeEvent(RocketComponent component, int type) { + super(component); + if (type == 0) { + throw new IllegalArgumentException("no event type provided"); + } + this.type = type; + } + + + public boolean isAerodynamicChange() { + return (type & AERODYNAMIC_CHANGE) != 0; + } + + public boolean isMassChange() { + return (type & MASS_CHANGE) != 0; + } + + public boolean isOtherChange() { + return (type & BOTH_CHANGE) == 0; + } + + public boolean isTreeChange() { + return (type & TREE_CHANGE) != 0; + } + + public boolean isUndoChange() { + return (type & UNDO_CHANGE) != 0; + } + + public boolean isMotorChange() { + return (type & MOTOR_CHANGE) != 0; + } + + public int getType() { + return type; + } + + @Override + public String toString() { + String s = ""; + + if ((type & NONFUNCTIONAL_CHANGE) != 0) + s += ",nonfunc"; + if (isMassChange()) + s += ",mass"; + if (isAerodynamicChange()) + s += ",aero"; + if (isTreeChange()) + s += ",tree"; + if (isUndoChange()) + s += ",undo"; + if (isMotorChange()) + s += ",motor"; + if ((type & EVENT_CHANGE) != 0) + s += ",event"; + + if (s.length() > 0) + s = s.substring(1); + + return "ComponentChangeEvent[" + s + "]"; + } +} diff --git a/src/net/sf/openrocket/rocketcomponent/ComponentChangeListener.java b/src/net/sf/openrocket/rocketcomponent/ComponentChangeListener.java new file mode 100644 index 000000000..dba150aeb --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/ComponentChangeListener.java @@ -0,0 +1,9 @@ +package net.sf.openrocket.rocketcomponent; + +import java.util.EventListener; + +public interface ComponentChangeListener extends EventListener { + + public void componentChanged(ComponentChangeEvent e); + +} diff --git a/src/net/sf/openrocket/rocketcomponent/Configuration.java b/src/net/sf/openrocket/rocketcomponent/Configuration.java new file mode 100644 index 000000000..c13382318 --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/Configuration.java @@ -0,0 +1,509 @@ +package net.sf.openrocket.rocketcomponent; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; + +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.EventListenerList; + +import net.sf.openrocket.util.ChangeSource; +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.MathUtil; + + +public class Configuration implements Cloneable, ChangeSource, ComponentChangeListener, + Iterable { + + public static final double DEFAULT_IGNITION_TIME = Double.MAX_VALUE; + + + private Rocket rocket; + private BitSet stages = new BitSet(); + + private String motorConfiguration = null; + + private EventListenerList listenerList = new EventListenerList(); + + private final HashMap ignitionTimes = + new HashMap(); + + + /* Cached data */ + private int boundsModID = -1; + private ArrayList cachedBounds = new ArrayList(); + private double cachedLength = -1; + + private int refLengthModID = -1; + private double cachedRefLength = -1; + + + + /** + * Create a new configuration with the specified Rocket with + * null motor configuration. + * + * @param rocket the rocket + */ + public Configuration(Rocket rocket) { + this.rocket = rocket; + setAllStages(); + rocket.addComponentChangeListener(this); + } + + + /** + * Create a new configuration with the specified Rocket and motor + * configuration. + * + * @param rocket the rocket. + * @param motorID the motor configuration ID to use. + */ + public Configuration(Rocket rocket, String motorID) { + this.rocket = rocket; + this.motorConfiguration = motorID; + setAllStages(); + rocket.addComponentChangeListener(this); + } + + + + + public Rocket getRocket() { + return rocket; + } + + + public void setAllStages() { + stages.clear(); + stages.set(0,rocket.getStageCount()); + fireChangeEvent(); + } + + + /** + * Set all stages up to and including the given stage number. For example, + * setToStage(0) will set only the first stage active. + * + * @param stage the stage number. + */ + public void setToStage(int stage) { + stages.clear(); + stages.set(0, stage+1, true); +// stages.set(stage+1, rocket.getStageCount(), false); + fireChangeEvent(); + } + + + /** + * Check whether the up-most stage of the rocket is in this configuration. + * + * @return true if the first stage is active in this configuration. + */ + public boolean isHead() { + return isStageActive(0); + } + + public boolean isStageActive(RocketComponent stage) { + if (!(stage instanceof Stage)) { + throw new IllegalArgumentException("called with component "+stage); + } + return stages.get(stage.getParent().getChildPosition(stage)); + } + + + public boolean isStageActive(int stage) { + if (stage >= rocket.getStageCount()) + return false; + return stages.get(stage); + } + + public int getStageCount() { + return rocket.getStageCount(); + } + + public int getActiveStageCount() { + int count = 0; + int s = rocket.getStageCount(); + + for (int i=0; i < s; i++) { + if (stages.get(i)) + count++; + } + return count; + } + + public int[] getActiveStages() { + int stageCount = rocket.getStageCount(); + List active = new ArrayList(); + int[] ret; + + for (int i=0; i < stageCount; i++) { + if (stages.get(i)) { + active.add(i); + } + } + + ret = new int[active.size()]; + for (int i=0; i < ret.length; i++) { + ret[i] = active.get(i); + } + + return ret; + } + + + /** + * Return the reference length associated with the current configuration. The + * reference length type is retrieved from the Rocket. + * + * @return the reference length for this configuration. + */ + public double getReferenceLength() { + if (rocket.getModID() != refLengthModID) { + refLengthModID = rocket.getModID(); + cachedRefLength = rocket.getReferenceType().getReferenceLength(this); + } + return cachedRefLength; + } + + + public double getReferenceArea() { + return Math.PI * MathUtil.pow2(getReferenceLength()/2); + } + + + public String getMotorConfigurationID() { + return motorConfiguration; + } + + public void setMotorConfigurationID(String id) { + if ((motorConfiguration == null && id == null) || + (id != null && id.equals(motorConfiguration))) + return; + + motorConfiguration = id; + fireChangeEvent(); + } + + public String getMotorConfigurationDescription() { + return rocket.getMotorConfigurationDescription(motorConfiguration); + } + + + + /** + * Clear all motor ignition times. All values are reset to their default of + * {@link #DEFAULT_IGNITION_TIME}. + */ + public void resetIgnitionTimes() { + ignitionTimes.clear(); + } + + /** + * Set the ignition time of the motor in the specified motor mount. Negative or NaN + * time values will cause an IllegalArgumentException. + * + * @param mount the motor mount to specify. + * @param time the time at which to ignite the motors. + * @throws IllegalArgumentException if time is negative of NaN. + */ + public void setIgnitionTime(MotorMount mount, double time) { + if (time < 0) { + throw new IllegalArgumentException("time is negative: "+time); + } + ignitionTimes.put(mount, time); + // TODO: MEDIUM: Should this fire events? + } + + /** + * Return the ignition time of the motor in the specified motor mount. If no time + * has been specified, returns {@link #DEFAULT_IGNITION_TIME} as the default. + * + * @param mount the motor mount. + * @return ignition time of the motors in the mount. + */ + public double getIgnitionTime(MotorMount mount) { + Double d = ignitionTimes.get(mount); + if (d == null) + return DEFAULT_IGNITION_TIME; + return d; + } + + + + + /** + * Removes the listener connection to the rocket and listeners of this object. + * This configuration may not be used after a call to this method! + */ + public void release() { + rocket.removeComponentChangeListener(this); + listenerList = null; + rocket = null; + } + + + //////////////// Listeners //////////////// + + @Override + public void addChangeListener(ChangeListener listener) { + listenerList.add(ChangeListener.class, listener); + } + + @Override + public void removeChangeListener(ChangeListener listener) { + listenerList.remove(ChangeListener.class, listener); + } + + protected void fireChangeEvent() { + Object[] listeners = listenerList.getListenerList(); + ChangeEvent e = new ChangeEvent(this); + + boundsModID = -1; + refLengthModID = -1; + + for (int i = listeners.length-2; i>=0; i-=2) { + if (listeners[i] == ChangeListener.class) { + ((ChangeListener) listeners[i+1]).stateChanged(e); + } + } + } + + + @Override + public void componentChanged(ComponentChangeEvent e) { + fireChangeEvent(); + } + + + /////////////// Helper methods /////////////// + + /** + * Return whether this configuration has any motors defined to it. + * + * @return true if this configuration has active motor mounts with motors defined to them. + */ + public boolean hasMotors() { + for (RocketComponent c: this) { + if (c instanceof MotorMount) { + MotorMount mount = (MotorMount) c; + if (!mount.isMotorMount()) + continue; + if (mount.getMotor(this.motorConfiguration) != null) { + return true; + } + } + } + return false; + } + + + /** + * Return the bounds of the current configuration. The bounds are cached. + * + * @return a Collection containing coordinates bouding the rocket. + */ + @SuppressWarnings("unchecked") + public Collection getBounds() { + if (rocket.getModID() != boundsModID) { + boundsModID = rocket.getModID(); + cachedBounds.clear(); + + double minX = Double.POSITIVE_INFINITY, maxX = Double.NEGATIVE_INFINITY; + for (RocketComponent component: this) { + for (Coordinate c: component.getComponentBounds()) { + for (Coordinate coord: component.toAbsolute(c)) { + cachedBounds.add(coord); + if (coord.x < minX) + minX = coord.x; + if (coord.x > maxX) + maxX = coord.x; + } + } + } + + if (Double.isInfinite(minX) || Double.isInfinite(maxX)) { + cachedLength = 0; + } else { + cachedLength = maxX - minX; + } + } + return (ArrayList) cachedBounds.clone(); + } + + + /** + * Returns the length of the rocket configuration, from the foremost bound X-coordinate + * to the aft-most X-coordinate. The value is cached. + * + * @return the length of the rocket in the X-direction. + */ + public double getLength() { + if (rocket.getModID() != boundsModID) + getBounds(); // Calculates the length + + return cachedLength; + } + + + + + /** + * Return an iterator that iterates over the currently active components. + * The Rocket and Stage components are not returned, + * but instead all components that are within currently active stages. + */ + @Override + public Iterator iterator() { + return new ConfigurationIterator(); + } + + + /** + * Return an iterator that iterates over all MotorMounts within the + * current configuration that have an active motor. + * + * @return an iterator over active motor mounts. + */ + public Iterator motorIterator() { + return new MotorIterator(); + } + + + /** + * Perform a deep-clone. The object references are also cloned and no + * listeners are listening on the cloned object. + */ + @Override + public Configuration clone() { + try { + Configuration config = (Configuration) super.clone(); + config.listenerList = new EventListenerList(); + config.stages = (BitSet) this.stages.clone(); + config.cachedBounds = new ArrayList(); + config.boundsModID = -1; + config.refLengthModID = -1; + rocket.addComponentChangeListener(config); + return config; + } catch (CloneNotSupportedException e) { + throw new RuntimeException("BUG: clone not supported!",e); + } + } + + + + /** + * A class that iterates over all currently active components. + * + * @author Sampo Niskanen + */ + private class ConfigurationIterator implements Iterator { + Iterator> iterators; + Iterator current = null; + + public ConfigurationIterator() { + List> list = new ArrayList>(); + + for (RocketComponent stage: rocket.getChildren()) { + if (isStageActive(stage)) { + list.add(stage.deepIterator()); + } + } + + // Get iterators and initialize current + iterators = list.iterator(); + if (iterators.hasNext()) { + current = iterators.next(); + } else { + List l = Collections.emptyList(); + current = l.iterator(); + } + } + + + @Override + public boolean hasNext() { + if (!current.hasNext()) + getNextIterator(); + + return current.hasNext(); + } + + @Override + public RocketComponent next() { + if (!current.hasNext()) + getNextIterator(); + + return current.next(); + } + + /** + * Get the next iterator that has items. If such an iterator does + * not exist, current is left to an empty iterator. + */ + private void getNextIterator() { + while ((!current.hasNext()) && iterators.hasNext()) { + current = iterators.next(); + } + } + + @Override + public void remove() { + throw new UnsupportedOperationException("remove unsupported"); + } + } + + private class MotorIterator implements Iterator { + private final Iterator iterator; + private MotorMount next = null; + + public MotorIterator() { + this.iterator = iterator(); + } + + @Override + public boolean hasNext() { + getNext(); + return (next != null); + } + + @Override + public MotorMount next() { + getNext(); + if (next == null) { + throw new NoSuchElementException("iterator called for too long"); + } + + MotorMount ret = next; + next = null; + return ret; + } + + @Override + public void remove() { + throw new UnsupportedOperationException("remove unsupported"); + } + + private void getNext() { + if (next != null) + return; + while (iterator.hasNext()) { + RocketComponent c = iterator.next(); + if (c instanceof MotorMount) { + MotorMount mount = (MotorMount) c; + if (mount.isMotorMount() && mount.getMotor(motorConfiguration) != null) { + next = mount; + return; + } + } + } + } + } +} diff --git a/src/net/sf/openrocket/rocketcomponent/EllipticalFinSet.java b/src/net/sf/openrocket/rocketcomponent/EllipticalFinSet.java new file mode 100644 index 000000000..d4ecac831 --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/EllipticalFinSet.java @@ -0,0 +1,70 @@ +package net.sf.openrocket.rocketcomponent; + +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.MathUtil; + +public class EllipticalFinSet extends FinSet { + public static final int POINTS = 21; + + private static final double[] POINT_X = new double[POINTS]; + private static final double[] POINT_Y = new double[POINTS]; + static { + for (int i=0; i < POINTS; i++) { + double a = Math.PI * (POINTS-1-i)/(POINTS-1); + POINT_X[i] = (Math.cos(a)+1)/2; + POINT_Y[i] = Math.sin(a); + } + POINT_X[0] = 0; + POINT_Y[0] = 0; + POINT_X[POINTS-1] = 1; + POINT_Y[POINTS-1] = 0; + } + + + private double height = 0.05; + + public EllipticalFinSet() { + this.length = 0.05; + } + + + @Override + public Coordinate[] getFinPoints() { + Coordinate[] points = new Coordinate[POINTS]; + for (int i=0; i < POINTS; i++) { + points[i] = new Coordinate(POINT_X[i]*length, POINT_Y[i]*height); + } + return points; + } + + @Override + public double getSpan() { + return height; + } + + @Override + public String getComponentName() { + return "Elliptical fin set"; + } + + + public double getHeight() { + return height; + } + + public void setHeight(double height) { + if (MathUtil.equals(this.height, height)) + return; + this.height = height; + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + } + + + public void setLength(double length) { + if (MathUtil.equals(this.length, length)) + return; + this.length = length; + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + } + +} diff --git a/src/net/sf/openrocket/rocketcomponent/EngineBlock.java b/src/net/sf/openrocket/rocketcomponent/EngineBlock.java new file mode 100644 index 000000000..e6c06ae5b --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/EngineBlock.java @@ -0,0 +1,28 @@ +package net.sf.openrocket.rocketcomponent; + + +public class EngineBlock extends ThicknessRingComponent { + + public EngineBlock() { + super(); + setOuterRadiusAutomatic(true); + setThickness(0.005); + setLength(0.005); + } + + @Override + public void setOuterRadiusAutomatic(boolean auto) { + super.setOuterRadiusAutomatic(auto); + } + + @Override + public String getComponentName() { + return "Engine block"; + } + + @Override + public boolean isCompatible(Class type) { + return false; + } + +} diff --git a/src/net/sf/openrocket/rocketcomponent/ExternalComponent.java b/src/net/sf/openrocket/rocketcomponent/ExternalComponent.java new file mode 100644 index 000000000..0f6ece5a7 --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/ExternalComponent.java @@ -0,0 +1,129 @@ +package net.sf.openrocket.rocketcomponent; + +import net.sf.openrocket.material.Material; +import net.sf.openrocket.unit.UnitGroup; +import net.sf.openrocket.util.Prefs; + +/** + * Class of components with well-defined physical appearance and which have an effect on + * aerodynamic simulation. They have material defined for them, and the mass of the component + * is calculated using the component's volume. + * + * @author Sampo Niskanen + */ + +public abstract class ExternalComponent extends RocketComponent { + + public enum Finish { + ROUGH("Rough", 500e-6), + UNFINISHED("Unfinished", 150e-6), + NORMAL("Regular paint", 60e-6), + SMOOTH("Smooth paint", 20e-6), + POLISHED("Polished", 2e-6); + + private final String name; + private final double roughnessSize; + + Finish(String name, double roughness) { + this.name = name; + this.roughnessSize = roughness; + } + + public double getRoughnessSize() { + return roughnessSize; + } + + @Override + public String toString() { + return name + " (" + UnitGroup.UNITS_ROUGHNESS.toStringUnit(roughnessSize) + ")"; + } + } + + + /** + * The material of the component. + */ + protected Material material=null; + + protected Finish finish = Finish.NORMAL; + + + + /** + * Constructor that sets the relative position of the component. + */ + public ExternalComponent(RocketComponent.Position relativePosition) { + super(relativePosition); + this.material = Prefs.getDefaultComponentMaterial(this.getClass(), Material.Type.BULK); + } + + /** + * Returns the volume of the component. This value is used in calculating the mass + * of the object. + */ + public abstract double getComponentVolume(); + + /** + * Calculates the mass of the component as the product of the volume and interior density. + */ + @Override + public double getComponentMass() { + return material.getDensity() * getComponentVolume(); + } + + /** + * ExternalComponent has aerodynamic effect, so return true. + */ + @Override + public boolean isAerodynamic() { + return true; + } + + /** + * ExternalComponent has effect on the mass, so return true. + */ + @Override + public boolean isMassive() { + return true; + } + + + public Material getMaterial() { + return material; + } + + public void setMaterial(Material mat) { + if (mat.getType() != Material.Type.BULK) { + throw new IllegalArgumentException("ExternalComponent requires a bulk material" + + " type="+mat.getType()); + } + + if (material.equals(mat)) + return; + material = mat; + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + } + + public Finish getFinish() { + return finish; + } + + public void setFinish(Finish finish) { + if (this.finish == finish) + return; + this.finish = finish; + fireComponentChangeEvent(ComponentChangeEvent.AERODYNAMIC_CHANGE); + } + + + + @Override + protected void copyFrom(RocketComponent c) { + super.copyFrom(c); + + ExternalComponent src = (ExternalComponent)c; + this.material = src.material; + this.finish = src.finish; + } + +} diff --git a/src/net/sf/openrocket/rocketcomponent/FinSet.java b/src/net/sf/openrocket/rocketcomponent/FinSet.java new file mode 100644 index 000000000..fc18062c8 --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/FinSet.java @@ -0,0 +1,512 @@ +package net.sf.openrocket.rocketcomponent; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.MathUtil; +import net.sf.openrocket.util.Transformation; + + +public abstract class FinSet extends ExternalComponent { + + /** + * Maximum allowed cant of fins. + */ + public static final double MAX_CANT = (15.0 * Math.PI / 180); + + + public enum CrossSection { + SQUARE("Square", 1.00), + ROUNDED("Rounded", 0.99), + AIRFOIL("Airfoil", 0.85); + + private final String name; + private final double volume; + CrossSection(String name, double volume) { + this.name = name; + this.volume = volume; + } + + public double getRelativeVolume() { + return volume; + } + @Override + public String toString() { + return name; + } + } + + /** + * Number of fins. + */ + protected int fins = 3; + + /** + * Rotation about the x-axis by 2*PI/fins. + */ + protected Transformation finRotation = Transformation.rotate_x(2*Math.PI/fins); + + /** + * Rotation angle of the first fin. Zero corresponds to the positive y-axis. + */ + protected double rotation = 0; + + /** + * Rotation about the x-axis by angle this.rotation. + */ + protected Transformation baseRotation = Transformation.rotate_x(rotation); + + + /** + * Cant angle of fins. + */ + protected double cantAngle = 0; + + /* Cached value: */ + private Transformation cantRotation = null; + + + /** + * Thickness of the fins. + */ + protected double thickness = 0; + + + /** + * The cross-section shape of the fins. + */ + protected CrossSection crossSection = CrossSection.SQUARE; + + + // Cached fin area & CG. Validity of both must be checked using finArea! + private double finArea = -1; + private double finCGx = -1; + private double finCGy = -1; + + + /** + * New FinSet with given number of fins and given base rotation angle. + * Sets the component relative position to POSITION_RELATIVE_BOTTOM, + * i.e. fins are positioned at the bottom of the parent component. + */ + public FinSet() { + super(RocketComponent.Position.BOTTOM); + } + + + + /** + * Return the number of fins in the set. + * @return The number of fins. + */ + public int getFinCount() { + return fins; + } + + /** + * Sets the number of fins in the set. + * @param n The number of fins, greater of equal to one. + */ + public void setFinCount(int n) { + if (fins == n) + return; + if (n < 1) + n = 1; + if (n > 8) + n = 8; + fins = n; + finRotation = Transformation.rotate_x(2*Math.PI/fins); + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + } + + public Transformation getFinRotationTransformation() { + return finRotation; + } + + /** + * Gets the base rotation amount of the first fin. + * @return The base rotation amount. + */ + public double getBaseRotation() { + return rotation; + } + + /** + * Sets the base rotation amount of the first fin. + * @param r The base rotation amount. + */ + public void setBaseRotation(double r) { + r = MathUtil.reduce180(r); + if (MathUtil.equals(r, rotation)) + return; + rotation = r; + baseRotation = Transformation.rotate_x(rotation); + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + } + + public Transformation getBaseRotationTransformation() { + return baseRotation; + } + + + + public double getCantAngle() { + return cantAngle; + } + + public void setCantAngle(double cant) { + cant = MathUtil.clamp(cant, -MAX_CANT, MAX_CANT); + if (MathUtil.equals(cant, cantAngle)) + return; + this.cantAngle = cant; + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + } + + + public Transformation getCantRotation() { + if (cantRotation == null) { + if (MathUtil.equals(cantAngle,0)) { + cantRotation = Transformation.IDENTITY; + } else { + Transformation t = new Transformation(-length/2,0,0); + t = Transformation.rotate_y(cantAngle).applyTransformation(t); + t = new Transformation(length/2,0,0).applyTransformation(t); + cantRotation = t; + } + } + return cantRotation; + } + + + + public double getThickness() { + return thickness; + } + + public void setThickness(double r) { + if (thickness == r) + return; + thickness = Math.max(r,0); + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + } + + + public CrossSection getCrossSection() { + return crossSection; + } + + public void setCrossSection(CrossSection cs) { + if (crossSection == cs) + return; + crossSection = cs; + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + } + + + + + + @Override + public void setRelativePosition(RocketComponent.Position position) { + super.setRelativePosition(position); + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + } + + + @Override + public void setPositionValue(double value) { + super.setPositionValue(value); + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + } + + + + + + + /////////// Calculation methods /////////// + + /** + * Return the area of one side of one fin. + * + * @return the area of one side of one fin. + */ + public double getFinArea() { + if (finArea < 0) + calculateAreaCG(); + + return finArea; + } + + /** + * Return the unweighted CG of a single fin. The X-coordinate is relative to + * the root chord trailing edge and the Y-coordinate to the fin root chord. + * + * @return the unweighted CG coordinate of a single fin. + */ + public Coordinate getFinCG() { + if (finArea < 0) + calculateAreaCG(); + + return new Coordinate(finCGx,finCGy,0); + } + + + + @Override + public double getComponentVolume() { + return fins * getFinArea() * thickness * crossSection.getRelativeVolume(); + } + + + @Override + public Coordinate getComponentCG() { + if (finArea < 0) + calculateAreaCG(); + + double mass = getComponentMass(); // safe + + if (fins == 1) { + return baseRotation.transform( + new Coordinate(finCGx,finCGy + getBodyRadius(), 0, mass)); + } else { + return new Coordinate(finCGx, 0, 0, mass); + } + } + + + private void calculateAreaCG() { + Coordinate[] points = this.getFinPoints(); + finArea = 0; + finCGx = 0; + finCGy = 0; + + for (int i=0; i < points.length-1; i++) { + final double x0 = points[i].x; + final double x1 = points[i+1].x; + final double y0 = points[i].y; + final double y1 = points[i+1].y; + + double da = (y0+y1)*(x1-x0) / 2; + finArea += da; + if (Math.abs(y0-y1) < 0.00001) { + finCGx += (x0+x1)/2 * da; + finCGy += y0/2 * da; + } else { + finCGx += (x0*(2*y0 + y1) + x1*(y0 + 2*y1)) / (3*(y0 + y1)) * da; + finCGy += (y1 + y0*y0/(y0 + y1))/3 * da; + } + } + + if (finArea < 0) + finArea = 0; + + if (finArea > 0) { + finCGx /= finArea; + finCGy /= finArea; + } else { + finCGx = (points[0].x + points[points.length-1].x)/2; + finCGy = 0; + } + } + + + /* + * Return an approximation of the longitudal unitary inertia of the fin set. + * The process is the following: + * + * 1. Approximate the fin with a rectangular fin + * + * 2. The inertia of one fin is taken as the average of the moments of inertia + * through its center perpendicular to the plane, and the inertia through + * its center parallel to the plane + * + * 3. If there are multiple fins, the inertia is shifted to the center of the fin + * set and multiplied by the number of fins. + */ + @Override + public double getLongitudalUnitInertia() { + double area = getFinArea(); + if (MathUtil.equals(area, 0)) + return 0; + + // Approximate fin with a rectangular fin + // w2 and h2 are squares of the fin width and height + double w = getLength(); + double h = getSpan(); + double w2,h2; + + if (MathUtil.equals(w*h,0)) { + w2 = area; + h2 = area; + } else { + w2 = w*area/h; + h2 = h*area/w; + } + + double inertia = (h2 + 2*w2)/24; + + if (fins == 1) + return inertia; + + double radius = getBodyRadius(); + + return fins * (inertia + MathUtil.pow2(Math.sqrt(h2) + radius)); + } + + + /* + * Return an approximation of the rotational unitary inertia of the fin set. + * The process is the following: + * + * 1. Approximate the fin with a rectangular fin and calculate the inertia of the + * rectangular approximate + * + * 2. If there are multiple fins, shift the inertia center to the fin set center + * and multiply with the number of fins. + */ + @Override + public double getRotationalUnitInertia() { + double area = getFinArea(); + if (MathUtil.equals(area, 0)) + return 0; + + // Approximate fin with a rectangular fin + double w = getLength(); + double h = getSpan(); + + if (MathUtil.equals(w*h,0)) { + h = Math.sqrt(area); + } else { + h = Math.sqrt(h*area/w); + } + + if (fins == 1) + return h*h / 12; + + double radius = getBodyRadius(); + + return fins * (h*h/12 + MathUtil.pow2(h/2 + radius)); + } + + + /** + * Adds the fin set's bounds to the collection. + */ + @Override + public Collection getComponentBounds() { + List bounds = new ArrayList(); + double r = getBodyRadius(); + + for (Coordinate point: getFinPoints()) { + addFinBound(bounds, point.x, point.y + r); + } + + return bounds; + } + + + /** + * Adds the 2d-coordinate bound (x,y) to the collection for both z-components and for + * all fin rotations. + */ + private void addFinBound(Collection set, double x, double y) { + Coordinate c; + int i; + + c = new Coordinate(x,y,thickness/2); + c = baseRotation.transform(c); + set.add(c); + for (i=1; ifalse + */ + @Override + public boolean isCompatible(Class type) { + return false; + } + + + + + /** + * Return a list of coordinates defining the geometry of a single fin. + * The coordinates are the XY-coordinates of points defining the shape of a single fin, + * where the origin is the leading root edge. Therefore, the first point must be (0,0,0). + * All Z-coordinates must be zero, and the last coordinate must have Y=0. + * + * @return List of XY-coordinates. + */ + public abstract Coordinate[] getFinPoints(); + + /** + * Get the span of a single fin. That is, the length from the root to the tip of the fin. + * @return Span of a single fin. + */ + public abstract double getSpan(); + + + @Override + protected void copyFrom(RocketComponent c) { + super.copyFrom(c); + + FinSet src = (FinSet)c; + this.fins = src.fins; + this.finRotation = src.finRotation; + this.rotation = src.rotation; + this.baseRotation = src.baseRotation; + this.cantAngle = src.cantAngle; + this.cantRotation = src.cantRotation; + this.thickness = src.thickness; + this.crossSection = src.crossSection; + } +} diff --git a/src/net/sf/openrocket/rocketcomponent/FreeformFinSet.java b/src/net/sf/openrocket/rocketcomponent/FreeformFinSet.java new file mode 100644 index 000000000..1ea92caee --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/FreeformFinSet.java @@ -0,0 +1,229 @@ +package net.sf.openrocket.rocketcomponent; + +import java.util.ArrayList; +import java.util.List; + +import net.sf.openrocket.util.Coordinate; + + +public class FreeformFinSet extends FinSet { + + private final List points = new ArrayList(); + + public FreeformFinSet() { + points.add(Coordinate.NUL); + points.add(new Coordinate(0.025,0.05)); + points.add(new Coordinate(0.075,0.05)); + points.add(new Coordinate(0.05,0)); + + this.length = 0.05; + } + + + public FreeformFinSet(FinSet finset) { + Coordinate[] finpoints = finset.getFinPoints(); + this.copyFrom(finset); + + points.clear(); + for (Coordinate c: finpoints) { + points.add(c); + } + this.length = points.get(points.size()-1).x - points.get(0).x; + } + + + + /** + * Add a fin point between indices index-1 and index. + * The point is placed at the midpoint of the current segment. + * + * @param index the fin point before which to add the new point. + */ + public void addPoint(int index) { + double x0, y0, x1, y1; + + x0 = points.get(index-1).x; + y0 = points.get(index-1).y; + x1 = points.get(index).x; + y1 = points.get(index).y; + + points.add(index, new Coordinate((x0+x1)/2, (y0+y1)/2)); + // adding a point within the segment affects neither mass nor aerodynamics + fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE); + } + + + /** + * Remove the fin point with the given index. The first and last fin points + * cannot be removed, and will cause an IllegalArgumentException + * if attempted. + * + * @param index the fin point index to remove + */ + public void removePoint(int index) { + if (index == 0 || index == points.size()-1) { + throw new IllegalArgumentException("cannot remove first or last point"); + } + points.remove(index); + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + } + + + public int getPointCount() { + return points.size(); + } + + public void setPoints(Coordinate[] p) { + if (p[0].x != 0 || p[0].y != 0 || p[p.length-1].y != 0) { + throw new IllegalArgumentException("Start or end point illegal."); + } + for (int i=0; i < p.length-1; i++) { + for (int j=i+2; j < p.length-1; j++) { + if (intersects(p[i].x, p[i].y, p[i+1].x, p[i+1].y, + p[j].x, p[j].y, p[j+1].x, p[j+1].y)) { + throw new IllegalArgumentException("segments intersect"); + } + } + if (p[i].z != 0) { + throw new IllegalArgumentException("z-coordinate not zero"); + } + } + + points.clear(); + for (Coordinate c: p) { + points.add(c); + } + this.length = p[p.length-1].x; + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + } + + + /** + * Set the point at position i to coordinates (x,y). + *

+ * Note that this method enforces basic fin shape restrictions (non-negative y, + * first and last point locations) silently, but throws an + * IllegalArgumentException if the point causes fin segments to + * intersect. The calling method should always catch this exception. + *

+ * Moving of the first point in the X-axis is allowed, but this actually moves + * all of the other points the corresponding distance back. + * + * @param index the point index to modify. + * @param x the x-coordinate. + * @param y the y-coordinate. + */ + public void setPoint(int index, double x, double y) { + if (y < 0) + y = 0; + + double x0,y0,x1,y1; + + if (index == 0) { + + // Restrict point + x = Math.min(x, points.get(points.size()-1).x); + y = 0; + x0 = Double.NaN; + y0 = Double.NaN; + x1 = points.get(1).x; + y1 = points.get(1).y; + + } else if (index == points.size()-1) { + + // Restrict point + x = Math.max(x, 0); + y = 0; + x0 = points.get(index-1).x; + y0 = points.get(index-1).y; + x1 = Double.NaN; + y1 = Double.NaN; + + } else { + + x0 = points.get(index-1).x; + y0 = points.get(index-1).y; + x1 = points.get(index+1).x; + y1 = points.get(index+1).y; + + } + + + + // Check for intersecting + double px0, py0, px1, py1; + px0 = 0; + py0 = 0; + for (int i=1; i < points.size(); i++) { + px1 = points.get(i).x; + py1 = points.get(i).y; + + if (i != index-1 && i != index && i != index+1) { + if (intersects(x0,y0,x,y,px0,py0,px1,py1)) { + throw new IllegalArgumentException("segments intersect"); + } + } + if (i != index && i != index+1 && i != index+2) { + if (intersects(x,y,x1,y1,px0,py0,px1,py1)) { + throw new IllegalArgumentException("segments intersect"); + } + } + + px0 = px1; + py0 = py1; + } + + if (index == 0) { + + System.out.println("Set point zero to x:"+x); + for (int i=1; i < points.size(); i++) { + Coordinate c = points.get(i); + points.set(i, c.setX(c.x - x)); + } + + } else { + + points.set(index,new Coordinate(x,y)); + + } + if (index == 0 || index == points.size()-1) { + this.length = points.get(points.size()-1).x; + } + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + } + + + + private boolean intersects(double ax0, double ay0, double ax1, double ay1, + double bx0, double by0, double bx1, double by1) { + + double d = ((by1-by0)*(ax1-ax0) - (bx1-bx0)*(ay1-ay0)); + + double ua = ((bx1-bx0)*(ay0-by0) - (by1-by0)*(ax0-bx0)) / d; + double ub = ((ax1-ax0)*(ay0-by0) - (ay1-ay0)*(ax0-bx0)) / d; + + return (ua >= 0) && (ua <= 1) && (ub >= 0) && (ub <= 1); + } + + + @Override + public Coordinate[] getFinPoints() { + return points.toArray(new Coordinate[0]); + } + + @Override + public double getSpan() { + double max = 0; + for (Coordinate c: points) { + if (c.y > max) + max = c.y; + } + return max; + } + + @Override + public String getComponentName() { + return "Freeform fin set"; + } + +} diff --git a/src/net/sf/openrocket/rocketcomponent/InnerTube.java b/src/net/sf/openrocket/rocketcomponent/InnerTube.java new file mode 100644 index 000000000..fe485e277 --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/InnerTube.java @@ -0,0 +1,276 @@ +package net.sf.openrocket.rocketcomponent; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.MathUtil; + + +/** + * This class defines an inner tube that can be used as a motor mount. The component + * may also be clustered. + * + * @author Sampo Niskanen + */ +public class InnerTube extends ThicknessRingComponent +implements Clusterable, RadialParent, MotorMount { + + private ClusterConfiguration cluster = ClusterConfiguration.SINGLE; + private double clusterScale = 1.0; + private double clusterRotation = 0.0; + + + private boolean motorMount = false; + private HashMap ejectionDelays = new HashMap(); + private HashMap motors = new HashMap(); + private IgnitionEvent ignitionEvent = IgnitionEvent.AUTOMATIC; + private double ignitionDelay = 0; + private double overhang = 0; + + + /** + * Main constructor. + */ + public InnerTube() { + // A-C motor size: + this.setOuterRadius(0.019/2); + this.setInnerRadius(0.018/2); + this.setLength(0.070); + } + + + @Override + public double getInnerRadius(double x) { + return getInnerRadius(); + } + + + @Override + public double getOuterRadius(double x) { + return getOuterRadius(); + } + + + @Override + public String getComponentName() { + return "Inner Tube"; + } + + /** + * Allow all InternalComponents to be added to this component. + */ + @Override + public boolean isCompatible(Class type) { + return InternalComponent.class.isAssignableFrom(type); + } + + + + ///////////// Cluster methods ////////////// + + /** + * Get the current cluster configuration. + * @return The current cluster configuration. + */ + public ClusterConfiguration getClusterConfiguration() { + return cluster; + } + + /** + * Set the current cluster configuration. + * @param cluster The cluster configuration. + */ + public void setClusterConfiguration(ClusterConfiguration cluster) { + this.cluster = cluster; + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + } + + /** + * Return the number of tubes in the cluster. + * @return Number of tubes in the current cluster. + */ + @Override + public int getClusterCount() { + return cluster.getClusterCount(); + } + + /** + * Get the cluster scaling. A value of 1.0 indicates that the tubes are packed + * touching each other, larger values separate the tubes and smaller values + * pack inside each other. + */ + public double getClusterScale() { + return clusterScale; + } + + /** + * Set the cluster scaling. + * @see #getClusterScale() + */ + public void setClusterScale(double scale) { + scale = Math.max(scale,0); + if (MathUtil.equals(clusterScale, scale)) + return; + clusterScale = scale; + fireComponentChangeEvent(new ComponentChangeEvent(this,ComponentChangeEvent.MASS_CHANGE)); + } + + + + /** + * @return the clusterRotation + */ + public double getClusterRotation() { + return clusterRotation; + } + + + /** + * @param rotation the clusterRotation to set + */ + public void setClusterRotation(double rotation) { + rotation = MathUtil.reduce180(rotation); + if (clusterRotation == rotation) + return; + this.clusterRotation = rotation; + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + } + + + @Override + public double getClusterSeparation() { + return 2*getOuterRadius()*clusterScale; + } + + + public List getClusterPoints() { + List list = new ArrayList(getClusterCount()); + List points = cluster.getPoints(clusterRotation - getRadialDirection()); + double separation = getClusterSeparation(); + for (int i=0; i < points.size()/2; i++) { + list.add(new Coordinate(0,points.get(2*i)*separation,points.get(2*i+1)*separation)); + } + return list; + } + + + @Override + public Coordinate[] shiftCoordinates(Coordinate[] array) { + array = super.shiftCoordinates(array); + + int count = getClusterCount(); + if (count == 1) + return array; + + List points = getClusterPoints(); + assert(points.size() == count); + Coordinate[] newArray = new Coordinate[array.length * count]; + for (int i=0; i < array.length; i++) { + for (int j=0; j < count; j++) { + newArray[i*count + j] = array[i].add(points.get(j)); + } + } + + return newArray; + } + + + + + //////////////// Motor mount ///////////////// + + @Override + public boolean isMotorMount() { + return motorMount; + } + + @Override + public void setMotorMount(boolean mount) { + if (motorMount == mount) + return; + motorMount = mount; + fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE); + } + + @Override + public Motor getMotor(String id) { + return motors.get(id); + } + + @Override + public void setMotor(String id, Motor motor) { + Motor current = motors.get(id); + if ((motor == null && current == null) || + (motor != null && motor.equals(current))) + return; + motors.put(id, motor); + fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE); + } + + @Override + public double getMotorDelay(String id) { + Double delay = ejectionDelays.get(id); + if (delay == null) + return Motor.PLUGGED; + return delay; + } + + @Override + public void setMotorDelay(String id, double delay) { + ejectionDelays.put(id, delay); + fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE); + } + + @Override + public int getMotorCount() { + return getClusterCount(); + } + + @Override + public double getMotorMountDiameter() { + return getInnerRadius()*2; + } + + @Override + public IgnitionEvent getIgnitionEvent() { + return ignitionEvent; + } + + @Override + public void setIgnitionEvent(IgnitionEvent event) { + if (ignitionEvent == event) + return; + ignitionEvent = event; + fireComponentChangeEvent(ComponentChangeEvent.EVENT_CHANGE); + } + + + @Override + public double getIgnitionDelay() { + return ignitionDelay; + } + + @Override + public void setIgnitionDelay(double delay) { + if (MathUtil.equals(delay, ignitionDelay)) + return; + ignitionDelay = delay; + fireComponentChangeEvent(ComponentChangeEvent.EVENT_CHANGE); + } + + + @Override + public double getMotorOverhang() { + return overhang; + } + + @Override + public void setMotorOverhang(double overhang) { + if (MathUtil.equals(this.overhang, overhang)) + return; + this.overhang = overhang; + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + } +} \ No newline at end of file diff --git a/src/net/sf/openrocket/rocketcomponent/InternalComponent.java b/src/net/sf/openrocket/rocketcomponent/InternalComponent.java new file mode 100644 index 000000000..1f2dba40c --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/InternalComponent.java @@ -0,0 +1,51 @@ +package net.sf.openrocket.rocketcomponent; + + +/** + * A component internal to the rocket. Internal components have no effect on the + * the aerodynamics of a rocket, only its mass properties (though the location of the + * components is not enforced to be within external components). Internal components + * are always attached relative to the parent component, which can be internal or + * external, or absolutely. + * + * @author Sampo Niskanen + */ +public abstract class InternalComponent extends RocketComponent { + + public InternalComponent() { + super(RocketComponent.Position.BOTTOM); + } + + + @Override + public final void setRelativePosition(RocketComponent.Position position) { + super.setRelativePosition(position); + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + } + + + @Override + public final void setPositionValue(double value) { + super.setPositionValue(value); + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + } + + + /** + * Non-aerodynamic components. + * @return false + */ + @Override + public final boolean isAerodynamic() { + return false; + } + + /** + * Is massive. + * @return true + */ + @Override + public final boolean isMassive() { + return true; + } +} diff --git a/src/net/sf/openrocket/rocketcomponent/LaunchLug.java b/src/net/sf/openrocket/rocketcomponent/LaunchLug.java new file mode 100644 index 000000000..9fbfcae48 --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/LaunchLug.java @@ -0,0 +1,195 @@ +package net.sf.openrocket.rocketcomponent; + +import java.util.ArrayList; +import java.util.Collection; + +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.MathUtil; + + +public class LaunchLug extends ExternalComponent { + + private double radius; + private double thickness; + + private double radialDirection = 0; + + /* These are calculated when the component is first attached to any Rocket */ + private double shiftY, shiftZ; + + + + public LaunchLug() { + super(Position.MIDDLE); + radius = 0.01/2; + thickness = 0.001; + length = 0.03; + } + + + public double getRadius() { + return radius; + } + + public void setRadius(double radius) { + if (MathUtil.equals(this.radius, radius)) + return; + this.radius = radius; + this.thickness = Math.min(this.thickness, this.radius); + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + } + + public double getInnerRadius() { + return radius-thickness; + } + + public void setInnerRadius(double innerRadius) { + setRadius(innerRadius + thickness); + } + + public double getThickness() { + return thickness; + } + + public void setThickness(double thickness) { + if (MathUtil.equals(this.thickness, thickness)) + return; + this.thickness = MathUtil.clamp(thickness, 0, radius); + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + } + + + public double getRadialDirection() { + return radialDirection; + } + + public void setRadialDirection(double direction) { + direction = MathUtil.reduce180(direction); + if (MathUtil.equals(this.radialDirection, direction)) + return; + this.radialDirection = direction; + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + } + + + + public void setLength(double length) { + if (MathUtil.equals(this.length, length)) + return; + this.length = length; + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + } + + + + + + + @Override + public void setRelativePosition(RocketComponent.Position position) { + super.setRelativePosition(position); + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + } + + + @Override + public void setPositionValue(double value) { + super.setPositionValue(value); + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + } + + + + @Override + public Coordinate[] shiftCoordinates(Coordinate[] array) { + array = super.shiftCoordinates(array); + + for (int i=0; i < array.length; i++) { + array[i] = array[i].add(0, shiftY, shiftZ); + } + + return array; + } + + + @Override + public void componentChanged(ComponentChangeEvent e) { + super.componentChanged(e); + + /* + * shiftY and shiftZ must be computed here since calculating them + * in shiftCoordinates() would cause an infinite loop due to .toRelative + */ + RocketComponent body; + double parentRadius; + + for (body = this.getParent(); body != null; body = body.getParent()) { + if (body instanceof SymmetricComponent) + break; + } + + if (body == null) { + parentRadius = 0; + } else { + SymmetricComponent s = (SymmetricComponent)body; + double x1, x2; + x1 = this.toRelative(Coordinate.NUL, body)[0].x; + x2 = this.toRelative(new Coordinate(length,0,0), body)[0].x; + x1 = MathUtil.clamp(x1, 0, body.getLength()); + x2 = MathUtil.clamp(x2, 0, body.getLength()); + parentRadius = Math.max(s.getRadius(x1), s.getRadius(x2)); + } + + shiftY = Math.cos(radialDirection) * (parentRadius + radius); + shiftZ = Math.sin(radialDirection) * (parentRadius + radius); + +// System.out.println("Computed shift: y="+shiftY+" z="+shiftZ); +} + + + + + @Override + public double getComponentVolume() { + return length * Math.PI * (MathUtil.pow2(radius) - MathUtil.pow2(radius-thickness)); + } + + @Override + public Collection getComponentBounds() { + ArrayList set = new ArrayList(); + addBound(set, 0, radius); + addBound(set, length, radius); + return set; + } + + @Override + public Coordinate getComponentCG() { + return new Coordinate(length/2, 0, 0, getComponentMass()); + } + + @Override + public String getComponentName() { + return "Launch lug"; + } + + @Override + public double getLongitudalUnitInertia() { + // 1/12 * (3 * (r1^2 + r2^2) + h^2) + return (3 * (MathUtil.pow2(getInnerRadius())) + MathUtil.pow2(getRadius()) + + MathUtil.pow2(getLength())) / 12; + } + + @Override + public double getRotationalUnitInertia() { + // 1/2 * (r1^2 + r2^2) + return (MathUtil.pow2(getInnerRadius()) + MathUtil.pow2(getRadius()))/2; + } + + + @Override + public boolean isCompatible(Class type) { + // Allow nothing to be attached to a LaunchLug + return false; + } + +} diff --git a/src/net/sf/openrocket/rocketcomponent/MassComponent.java b/src/net/sf/openrocket/rocketcomponent/MassComponent.java new file mode 100644 index 000000000..951d4dc3f --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/MassComponent.java @@ -0,0 +1,44 @@ +package net.sf.openrocket.rocketcomponent; + +import net.sf.openrocket.util.MathUtil; + +public class MassComponent extends MassObject { + private double mass = 0; + + + public MassComponent() { + super(); + } + + public MassComponent(double length, double radius, double mass) { + super(length, radius); + this.mass = mass; + } + + + @Override + public double getComponentMass() { + return mass; + } + + public void setComponentMass(double mass) { + mass = Math.max(mass, 0); + if (MathUtil.equals(this.mass, mass)) + return; + this.mass = mass; + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + } + + + @Override + public String getComponentName() { + return "Mass component"; + } + + + @Override + public boolean isCompatible(Class type) { + // Allow no components to be attached to a MassComponent + return false; + } +} diff --git a/src/net/sf/openrocket/rocketcomponent/MassObject.java b/src/net/sf/openrocket/rocketcomponent/MassObject.java new file mode 100644 index 000000000..cadb3b0ca --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/MassObject.java @@ -0,0 +1,141 @@ +package net.sf.openrocket.rocketcomponent; + +import static net.sf.openrocket.util.MathUtil.pow2; + +import java.util.ArrayList; +import java.util.Collection; + +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.MathUtil; + + + +/** + * A MassObject is an internal component that can a specific weight, but not + * necessarily a strictly bound shape. It is represented as a homogeneous + * cylinder and drawn in the rocket figure with rounded corners. + *

+ * Subclasses of this class need only implement the {@link #getComponentMass()}, + * {@link #getComponentName()} and {@link #isCompatible(RocketComponent)} + * methods. + * + * @author Sampo Niskanen + */ +public abstract class MassObject extends InternalComponent { + + private double radius; + + private double radialPosition; + private double radialDirection; + + private double shiftY = 0; + private double shiftZ = 0; + + + public MassObject() { + this(0.03, 0.015); + } + + public MassObject(double length, double radius) { + super(); + + this.length = length; + this.radius = radius; + + this.setRelativePosition(Position.TOP); + this.setPositionValue(0.0); + } + + + + + public void setLength(double length) { + length = Math.max(length, 0); + if (MathUtil.equals(this.length, length)) + return; + this.length = length; + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + } + + + + public final double getRadius() { + return radius; + } + + + public final void setRadius(double radius) { + radius = Math.max(radius, 0); + if (MathUtil.equals(this.radius, radius)) + return; + this.radius = radius; + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + } + + + + public final double getRadialPosition() { + return radialPosition; + } + + public final void setRadialPosition(double radialPosition) { + radialPosition = Math.max(radialPosition, 0); + if (MathUtil.equals(this.radialPosition, radialPosition)) + return; + this.radialPosition = radialPosition; + shiftY = radialPosition * Math.cos(radialDirection); + shiftZ = radialPosition * Math.sin(radialDirection); + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + } + + public final double getRadialDirection() { + return radialDirection; + } + + public final void setRadialDirection(double radialDirection) { + radialDirection = MathUtil.reduce180(radialDirection); + if (MathUtil.equals(this.radialDirection, radialDirection)) + return; + this.radialDirection = radialDirection; + shiftY = radialPosition * Math.cos(radialDirection); + shiftZ = radialPosition * Math.sin(radialDirection); + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + } + + + + + /** + * Shift the coordinates according to the radial position and direction. + */ + @Override + public final Coordinate[] shiftCoordinates(Coordinate[] array) { + for (int i=0; i < array.length; i++) { + array[i] = array[i].add(0, shiftY, shiftZ); + } + return array; + } + + @Override + public final Coordinate getComponentCG() { + return new Coordinate(length/2, shiftY, shiftZ, getComponentMass()); + } + + @Override + public final double getLongitudalUnitInertia() { + return (3*pow2(radius) + pow2(length)) / 12; + } + + @Override + public final double getRotationalUnitInertia() { + return pow2(radius) / 2; + } + + @Override + public final Collection getComponentBounds() { + Collection c = new ArrayList(); + addBound(c, 0, radius); + addBound(c, length, radius); + return c; + } +} diff --git a/src/net/sf/openrocket/rocketcomponent/Motor.java b/src/net/sf/openrocket/rocketcomponent/Motor.java new file mode 100644 index 000000000..9f84518b0 --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/Motor.java @@ -0,0 +1,623 @@ +package net.sf.openrocket.rocketcomponent; + +import java.text.Collator; +import java.util.Comparator; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.MathUtil; + + + +/** + * Abstract base class for motors. The methods that must be implemented are + * {@link #getTotalTime()}, {@link #getThrust(double)} and {@link #getCG(double)}. + * Additionally the method {@link #getMaxThrust()} may be overridden for efficiency. + *

+ * + * NOTE: The current implementation of {@link #getAverageTime()} and + * {@link #getAverageThrust()} assume that the class is immutable! + * + * @author Sampo Niskanen + */ +public abstract class Motor implements Comparable { + + /** + * Enum of rocket motor types. + * + * @author Sampo Niskanen + */ + public enum Type { + SINGLE("Single-use", "Single-use solid propellant motor"), + RELOAD("Reloadable", "Reloadable solid propellant motor"), + HYBRID("Hybrid", "Hybrid rocket motor engine"), + UNKNOWN("Unknown", "Unknown motor type"); + + private final String name; + private final String description; + + Type(String name, String description) { + this.name = name; + this.description = description; + } + + /** + * Return a short name of this motor type. + * @return a short name of the motor type. + */ + public String getName() { + return name; + } + + /** + * Return a long description of this motor type. + * @return a description of the motor type. + */ + public String getDescription() { + return description; + } + + @Override + public String toString() { + return name; + } + } + + + /** + * Ejection charge delay value signifying a "plugged" motor with no ejection charge. + * The value is that of Double.POSITIVE_INFINITY. + */ + public static final double PLUGGED = Double.POSITIVE_INFINITY; + + + /** + * Below what portion of maximum thrust is the motor chosen to be off when + * calculating average thrust and burn time.double + */ + public static final double AVERAGE_MARGINAL = 0.05; + + /* All data is cached, so divisions can be very tight. */ + private static final int DIVISIONS = 1000; + + + // Comparators: + private static final Collator COLLATOR = Collator.getInstance(Locale.US); + static { + COLLATOR.setStrength(Collator.PRIMARY); + } + private static DesignationComparator DESIGNATION_COMPARATOR = new DesignationComparator(); + + + + + private final String manufacturer; + private final String designation; + private final String description; + private final Type motorType; + + private final double[] delays; + + private final double diameter; + private final double length; + + /* Cached data */ + private double maxThrust = -1; + private double avgTime = -1; + private double avgThrust = -1; + private double totalImpulse = -1; + + + + /** + * Sole constructor. None of the parameters may be null. + * + * @param manufacturer the manufacturer of the motor. + * @param designation the motor designation. + * @param description further description, including any comments on the origin + * of the thrust curve. + * @param delays an array of the standard ejection charge delays. A plugged + * motor (no ejection charge) is specified by a delay of + * {@link #PLUGGED} (Double.POSITIVE_INFINITY). + * @param diameter maximum diameter of the motor + * @param length length of the motor + */ + protected Motor(String manufacturer, String designation, String description, + Type type, double[] delays, double diameter, double length) { + + if (manufacturer == null || designation == null || description == null || + type == null || delays == null) { + throw new IllegalArgumentException("Parameters cannot be null."); + } + + this.manufacturer = manufacturer; + this.designation = designation; + this.description = description.trim(); + this.motorType = type; + this.delays = delays.clone(); + this.diameter = diameter; + this.length = length; + } + + + + /** + * Return the total burn time of the motor. The method {@link #getThrust(double)} + * must return zero for time values greater than the return value. + * + * @return the total burn time of the motor. + */ + public abstract double getTotalTime(); + + + /** + * Return the thrust of the motor at the specified time. + * + * @param time time since the ignition of the motor. + * @return the thrust at the specified time. + */ + public abstract double getThrust(double time); + + + /** + * Return the average thrust of the motor between times t1 and t2. + * + * @param t1 starting time since the ignition of the motor. + * @param t2 end time since the ignition of the motor. + * @return the average thrust during the time period. + */ + /* TODO: MEDIUM: Implement better method in subclass */ + public double getThrust(double t1, double t2) { + double f = 0; + f += getThrust(t1); + f += getThrust(0.8*t1 + 0.2*t2); + f += getThrust(0.6*t1 + 0.4*t2); + f += getThrust(0.4*t1 + 0.6*t2); + f += getThrust(0.2*t1 + 0.8*t2); + f += getThrust(t2); + return f/6; + } + + + /** + * Return the mass and CG of the motor at the specified time. + * + * @param time time since the ignition of the motor. + * @return the mass and CG of the motor. + */ + public abstract Coordinate getCG(double time); + + + + /** + * Return the mass of the motor at the specified time. The original mass + * of the motor can be queried by getMass(0) and the burnt mass + * by getMass(Double.MAX_VALUE). + * + * @param time time since the ignition of the motor. + * @return the mass of the motor. + */ + public double getMass(double time) { + return getCG(time).weight; + } + + + /** + * Return the longitudal moment of inertia of the motor at the specified time. + * This default method assumes that the mass of the motor is evenly distributed + * in a cylinder with the diameter and length of the motor. + * + * @param time time since the ignition of the motor. + * @return the longitudal moment of inertia of the motor. + */ + public double getLongitudalInertia(double time) { + return getMass(time) * (3.0*MathUtil.pow2(diameter/2) + MathUtil.pow2(length))/12; + } + + + + /** + * Return the rotational moment of inertia of the motor at the specified time. + * This default method assumes that the mass of the motor is evenly distributed + * in a cylinder with the diameter and length of the motor. + * + * @param time time since the ignition of the motor. + * @return the rotational moment of inertia of the motor. + */ + public double getRotationalInertia(double time) { + return getMass(time) * MathUtil.pow2(diameter) / 8; + } + + + + + /** + * Return the maximum thrust. This implementation slices through the thrust curve + * searching for the maximum thrust. Subclasses may wish to override this with a + * more efficient method. + * + * @return the maximum thrust of the motor + */ + public double getMaxThrust() { + if (maxThrust < 0) { + double time = getTotalTime(); + maxThrust = 0; + + for (int i=0; i < DIVISIONS; i++) { + double t = time * i / DIVISIONS; + double thrust = getThrust(t); + + if (thrust > maxThrust) + maxThrust = thrust; + } + } + return maxThrust; + } + + + /** + * Return the time used in calculating the average thrust. The time is the + * length of time from motor ignition until the thrust has dropped below + * {@link #AVERAGE_MARGINAL} times the maximum thrust. + * + * @return the nominal burn time. + */ + public double getAverageTime() { + // Compute average time lazily + if (avgTime < 0) { + double max = getMaxThrust(); + double time = getTotalTime(); + + for (int i=DIVISIONS; i >= 0; i--) { + avgTime = time * i / DIVISIONS; + if (getThrust(avgTime) > max*AVERAGE_MARGINAL) + break; + } + } + return avgTime; + } + + + /** + * Return the calculated average thrust during time from ignition to + * {@link #getAverageTime()}. + * + * @return the nominal average thrust. + */ + public double getAverageThrust() { + // Compute average thrust lazily + if (avgThrust < 0) { + double time = getAverageTime(); + + avgThrust = 0; + for (int i=0; i < DIVISIONS; i++) { + double t = time * i / DIVISIONS; + avgThrust += getThrust(t); + } + avgThrust /= DIVISIONS; + } + return avgThrust; + } + + + /** + * Return the total impulse of the motor. This is calculated from the entire + * burn time, and therefore may differ from the value of {@link #getAverageTime()} + * and {@link #getAverageThrust()} multiplied together. + * + * @return the total impulse of the motor. + */ + public double getTotalImpulse() { + // Compute total impulse lazily + if (totalImpulse < 0) { + double time = getTotalTime(); + double f0, t0; + + totalImpulse = 0; + t0 = 0; + f0 = getThrust(0); + for (int i=1; i < DIVISIONS; i++) { + double t1 = time * i / DIVISIONS; + double f1 = getThrust(t1); + totalImpulse += 0.5*(f0+f1)*(t1-t0); + t0 = t1; + f0 = f1; + } + } + return totalImpulse; + } + + + /** + * Return the manufacturer of the motor. + * + * @return the manufacturer + */ + public String getManufacturer() { + return manufacturer; + } + + /** + * Return the designation of the motor. + * + * @return the designation + */ + public String getDesignation() { + return designation; + } + + /** + * Return the designation of the motor, including a delay. + * + * @param delay the delay of the motor. + * @return designation with delay. + */ + public String getDesignation(double delay) { + return getDesignation() + "-" + getDelayString(delay); + } + + + /** + * Return extra description for the motor. This may include for example + * comments on the source of the thrust curve. The returned String + * may include new-lines. + * + * @return the description + */ + public String getDescription() { + return description; + } + + + /** + * Return the motor type. + * + * @return the motorType + */ + public Type getMotorType() { + return motorType; + } + + + + /** + * Return the standard ejection charge delays for the motor. "Plugged" motors + * with no ejection charge are signified by the value {@link #PLUGGED} + * (Double.POSITIVE_INFINITY). + * + * @return the list of standard ejection charge delays, which may be empty. + */ + public double[] getStandardDelays() { + return delays.clone(); + } + + /** + * Return the maximum diameter of the motor. + * + * @return the diameter + */ + public double getDiameter() { + return diameter; + } + + /** + * Return the length of the motor. This should be a "characteristic" length, + * and the exact definition may depend on the motor type. Typically this should + * be the length from the bottom of the motor to the end of the maximum diameter + * portion, ignoring any smaller ejection charge compartments. + * + * @return the length + */ + public double getLength() { + return length; + } + + + /** + * Compares two Motor objects. The motors are considered equal + * if they have identical manufacturers, designations and types, near-identical + * dimensions, burn times and delays and near-identical thrust curves + * (sampled at 10 equidistant points). + *

+ * The comment field is ignored when comparing equality. + */ + @Override + public boolean equals(Object o) { + if (!(o instanceof Motor)) + return false; + + Motor other = (Motor) o; + + // Tests manufacturer, designation, diameter and length + if (this.compareTo(other) != 0) + return false; + + if (Math.abs(this.getTotalTime() - other.getTotalTime()) > 0.5 || + this.motorType != other.motorType || + this.delays.length != other.delays.length) { + + return false; + } + + for (int i=0; i < delays.length; i++) { + // INF - INF == NaN, which produces false when compared + if (Math.abs(this.delays[i] - other.delays[i]) > 0.5) { + return false; + } + } + + double time = getTotalTime(); + for (int i=0; i < 10; i++) { + double t = time * i/10; + if (Math.abs(this.getThrust(t) - other.getThrust(t)) > 1) { + return false; + } + } + return true; + } + + /** + * A hashCode method compatible with the equals + * method. + */ + @Override + public int hashCode() { + return (manufacturer.hashCode() + designation.hashCode() + + ((int)(length*1000)) + ((int)(diameter*1000))); + } + + + + @Override + public String toString() { + return manufacturer + " " + designation; + } + + + ////////// Static methods + + + /** + * Return a String representation of a delay time. If the delay is {@link #PLUGGED}, + * returns "P". + * + * @param delay the delay time. + * @return the String representation. + */ + public static String getDelayString(double delay) { + return getDelayString(delay,"P"); + } + + /** + * Return a String representation of a delay time. If the delay is {@link #PLUGGED}, + * plugged is returned. + * + * @param delay the delay time. + * @param plugged the return value if there is no ejection charge. + * @return the String representation. + */ + public static String getDelayString(double delay, String plugged) { + if (delay == PLUGGED) + return plugged; + delay = Math.rint(delay*10)/10; + if (MathUtil.equals(delay, Math.rint(delay))) + return "" + ((int)delay); + return "" + delay; + } + + + + + //////////// Comparation + + + + @Override + public int compareTo(Motor other) { + int value; + + if (COLLATOR == null) { + } + + // 1. Manufacturer + value = COLLATOR.compare(this.manufacturer, other.manufacturer); + if (value != 0) + return value; + + // 2. Designation + value = DESIGNATION_COMPARATOR.compare(this.designation, other.designation); + if (value != 0) + return value; + + // 3. Diameter + value = (int)((this.diameter - other.diameter)*1000000); + if (value != 0) + return value; + + // 4. Length + value = (int)((this.length - other.length)*1000000); + if (value != 0) + return value; + + // 5. Total impulse + value = (int)((this.getTotalImpulse() - other.getTotalImpulse())*1000); + return value; + } + + + + public static Comparator getDesignationComparator() { + return DESIGNATION_COMPARATOR; + } + + + /** + * Compares two motors by their designations. The motors are ordered first + * by their motor class, second by their average thrust and lastly by any + * extra modifiers at the end of the designation. + * + * @author Sampo Niskanen + */ + private static class DesignationComparator implements Comparator { + private Pattern pattern = + Pattern.compile("^([0-9][0-9]+|1/([1-8]))?([a-zA-Z])([0-9]+)(.*?)$"); + + @Override + public int compare(String o1, String o2) { + int value; + Matcher m1, m2; + + m1 = pattern.matcher(o1); + m2 = pattern.matcher(o2); + + if (m1.find() && m2.find()) { + + String o1Class = m1.group(3); + int o1Thrust = Integer.parseInt(m1.group(4)); + String o1Extra = m1.group(5); + + String o2Class = m2.group(3); + int o2Thrust = Integer.parseInt(m2.group(4)); + String o2Extra = m2.group(5); + + // 1. Motor class + if (o1Class.equalsIgnoreCase("A") && o2Class.equalsIgnoreCase("A")) { + // 1/2A and 1/4A comparison + String sub1 = m1.group(2); + String sub2 = m2.group(2); + + if (sub1 != null || sub2 != null) { + if (sub1 == null) + sub1 = "1"; + if (sub2 == null) + sub2 = "1"; + value = -COLLATOR.compare(sub1,sub2); + if (value != 0) + return value; + } + } + value = COLLATOR.compare(o1Class,o2Class); + if (value != 0) + return value; + + // 2. Average thrust + if (o1Thrust != o2Thrust) + return o1Thrust - o2Thrust; + + // 3. Extra modifier + return COLLATOR.compare(o1Extra, o2Extra); + + } else { + + System.out.println("Falling back"); + System.out.println("o1:"+o1 + " o2:"+o2); + + // Not understandable designation, simply compare strings + return COLLATOR.compare(o1, o2); + } + } + } + +} diff --git a/src/net/sf/openrocket/rocketcomponent/MotorMount.java b/src/net/sf/openrocket/rocketcomponent/MotorMount.java new file mode 100644 index 000000000..b6dbb5fba --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/MotorMount.java @@ -0,0 +1,189 @@ +package net.sf.openrocket.rocketcomponent; + +import net.sf.openrocket.simulation.FlightEvent; +import net.sf.openrocket.util.ChangeSource; + +public interface MotorMount extends ChangeSource { + + public static enum IgnitionEvent { + AUTOMATIC("Automatic (launch or ejection charge)") { + @Override + public boolean isActivationEvent(FlightEvent e, RocketComponent source) { + int count = source.getRocket().getStageCount(); + int stage = source.getStageNumber(); + + if (stage == count-1) { + return LAUNCH.isActivationEvent(e, source); + } else { + return EJECTION_CHARGE.isActivationEvent(e, source); + } + } + }, + LAUNCH("Launch") { + @Override + public boolean isActivationEvent(FlightEvent e, RocketComponent source) { + return (e.getType() == FlightEvent.Type.LAUNCH); + } + }, + EJECTION_CHARGE("First ejection charge of previous stage") { + @Override + public boolean isActivationEvent(FlightEvent e, RocketComponent source) { + if (e.getType() != FlightEvent.Type.EJECTION_CHARGE) + return false; + + int charge = e.getSource().getStageNumber(); + int mount = source.getStageNumber(); + return (mount+1 == charge); + } + }, + BURNOUT("First burnout of previous stage") { + @Override + public boolean isActivationEvent(FlightEvent e, RocketComponent source) { + if (e.getType() != FlightEvent.Type.BURNOUT) + return false; + + int charge = e.getSource().getStageNumber(); + int mount = source.getStageNumber(); + return (mount+1 == charge); + } + }, + NEVER("Never") { + @Override + public boolean isActivationEvent(FlightEvent e, RocketComponent source) { + return false; + } + }, + ; + + + private final String description; + + IgnitionEvent(String description) { + this.description = description; + } + + public abstract boolean isActivationEvent(FlightEvent e, RocketComponent source); + + @Override + public String toString() { + return description; + } + }; + + + /** + * Is the component currently a motor mount. + * + * @return whether the component holds a motor. + */ + public boolean isMotorMount(); + + /** + * Set whether the component is currently a motor mount. + */ + public void setMotorMount(boolean mount); + + + /** + * Return the motor for the motor configuration. May return null + * if no motor has been set. This method must return null if ID + * is null. + * + * @param id the motor configuration ID + * @return the motor, or null if not set. + */ + public Motor getMotor(String id); + + /** + * Set the motor for the motor configuration. May be set to null + * to remove the motor. + * + * @param id the motor configuration ID + * @param motor the motor, or null. + */ + public void setMotor(String id, Motor motor); + + /** + * Get the number of similar motors clustered. + * + * @return the number of motors. + */ + public int getMotorCount(); + + + + /** + * Return the ejection charge delay of given motor configuration. + * A "plugged" motor without an ejection charge is given by + * {@link Motor#PLUGGED} (Double.POSITIVE_INFINITY). + * + * @param id the motor configuration ID + * @return the ejection charge delay. + */ + public double getMotorDelay(String id); + + /** + * Set the ejection change delay of the given motor configuration. + * The ejection charge is disable (a "plugged" motor) is set by + * {@link Motor#PLUGGED} (Double.POSITIVE_INFINITY). + * + * @param id the motor configuration ID + * @param delay the ejection charge delay. + */ + public void setMotorDelay(String id, double delay); + + + /** + * Return the event that ignites this motor. + * + * @return the {@link IgnitionEvent} that ignites this motor. + */ + public IgnitionEvent getIgnitionEvent(); + + /** + * Sets the event that ignites this motor. + * + * @param event the {@link IgnitionEvent} that ignites this motor. + */ + public void setIgnitionEvent(IgnitionEvent event); + + + /** + * Returns the ignition delay of this motor. + * + * @return the ignition delay + */ + public double getIgnitionDelay(); + + /** + * Sets the ignition delay of this motor. + * + * @param delay the ignition delay. + */ + public void setIgnitionDelay(double delay); + + + /** + * Return the distance that the motors hang outside this motor mount. + * + * @return the overhang length. + */ + public double getMotorOverhang(); + + /** + * Sets the distance that the motors hang outside this motor mount. + * + * @param overhang the overhang length. + */ + public void setMotorOverhang(double overhang); + + + + /** + * Return the inner diameter of the motor mount. + * + * @return the inner diameter of the motor mount. + */ + public double getMotorMountDiameter(); + +} diff --git a/src/net/sf/openrocket/rocketcomponent/NoseCone.java b/src/net/sf/openrocket/rocketcomponent/NoseCone.java new file mode 100644 index 000000000..04eaa77c8 --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/NoseCone.java @@ -0,0 +1,117 @@ +package net.sf.openrocket.rocketcomponent; + +/** + * Rocket nose cones of various types. Implemented as a transition with the + * fore radius == 0. + * + * @author Sampo Niskanen + */ + +public class NoseCone extends Transition { + + + /********* Constructors **********/ + public NoseCone() { + this(Transition.Shape.OGIVE, 6*DEFAULT_RADIUS, DEFAULT_RADIUS); + } + + public NoseCone(Transition.Shape type, double length, double radius) { + super(); + super.setType(type); + super.setForeRadiusAutomatic(false); + super.setForeRadius(0); + super.setForeShoulderLength(0); + super.setForeShoulderRadius(0.9*radius); + super.setForeShoulderThickness(0); + super.setForeShoulderCapped(filled); + super.setThickness(0.002); + super.setLength(length); + super.setClipped(false); + + } + + + /********** Get/set methods for component parameters **********/ + + @Override + public double getForeRadius() { + return 0; + } + + @Override + public void setForeRadius(double r) { + // No-op + } + + @Override + public boolean isForeRadiusAutomatic() { + return false; + } + + @Override + public void setForeRadiusAutomatic(boolean b) { + // No-op + } + + @Override + public double getForeShoulderLength() { + return 0; + } + + @Override + public double getForeShoulderRadius() { + return 0; + } + + @Override + public double getForeShoulderThickness() { + return 0; + } + + @Override + public boolean isForeShoulderCapped() { + return false; + } + + @Override + public void setForeShoulderCapped(boolean capped) { + // No-op + } + + @Override + public void setForeShoulderLength(double foreShoulderLength) { + // No-op + } + + @Override + public void setForeShoulderRadius(double foreShoulderRadius) { + // No-op + } + + @Override + public void setForeShoulderThickness(double foreShoulderThickness) { + // No-op + } + + @Override + public boolean isClipped() { + return false; + } + + @Override + public void setClipped(boolean b) { + // No-op + } + + + + /********** RocketComponent methods **********/ + + /** + * Return component name. + */ + @Override + public String getComponentName() { + return "Nose cone"; + } +} diff --git a/src/net/sf/openrocket/rocketcomponent/Parachute.java b/src/net/sf/openrocket/rocketcomponent/Parachute.java new file mode 100644 index 000000000..38ff5b3d6 --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/Parachute.java @@ -0,0 +1,114 @@ +package net.sf.openrocket.rocketcomponent; + +import net.sf.openrocket.material.Material; +import net.sf.openrocket.util.MathUtil; +import net.sf.openrocket.util.Prefs; + +public class Parachute extends RecoveryDevice { + + public static final double DEFAULT_CD = 0.8; + + private double diameter; + + private Material lineMaterial; + private int lineCount = 6; + private double lineLength = 0.3; + + + public Parachute() { + this.diameter = 0.3; + this.lineMaterial = Prefs.getDefaultComponentMaterial(Parachute.class, Material.Type.LINE); + this.lineLength = 0.3; + } + + + public double getDiameter() { + return diameter; + } + + public void setDiameter(double d) { + if (MathUtil.equals(this.diameter, d)) + return; + this.diameter = d; + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + } + + + public final Material getLineMaterial() { + return lineMaterial; + } + + public final void setLineMaterial(Material mat) { + if (mat.getType() != Material.Type.LINE) { + throw new IllegalArgumentException("Attempted to set non-line material "+mat); + } + if (mat.equals(lineMaterial)) + return; + this.lineMaterial = mat; + if (getLineCount() != 0) + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + else + fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE); + } + + + public final int getLineCount() { + return lineCount; + } + + public final void setLineCount(int n) { + if (this.lineCount == n) + return; + this.lineCount = n; + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + } + + public final double getLineLength() { + return lineLength; + } + + public final void setLineLength(double length) { + if (MathUtil.equals(this.lineLength, length)) + return; + this.lineLength = length; + if (getLineCount() != 0) + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + else + fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE); + } + + + @Override + public double getComponentCD(double mach) { + return DEFAULT_CD; // TODO: HIGH: Better parachute CD estimate? + } + + @Override + public double getArea() { + return Math.PI * MathUtil.pow2(diameter/2); + } + + public void setArea(double area) { + if (MathUtil.equals(getArea(), area)) + return; + diameter = Math.sqrt(area / Math.PI) * 2; + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + } + + @Override + public double getComponentMass() { + return super.getComponentMass() + + getLineCount() * getLineLength() * getLineMaterial().getDensity(); + } + + @Override + public String getComponentName() { + return "Parachute"; + } + + @Override + public boolean isCompatible(Class type) { + return false; + } + +} diff --git a/src/net/sf/openrocket/rocketcomponent/RadialParent.java b/src/net/sf/openrocket/rocketcomponent/RadialParent.java new file mode 100644 index 000000000..41c731af6 --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/RadialParent.java @@ -0,0 +1,31 @@ +package net.sf.openrocket.rocketcomponent; + +public interface RadialParent { + + /** + * Return the outer radius of the component at local coordinate x. + * Values for x < 0 and x > getLength() are undefined. + * + * @param x the lengthwise position in the coordinates of this component. + * @return the outer radius of the component at that position. + */ + public double getOuterRadius(double x); + + /** + * Return the inner radius of the component at local coordinate x. + * Values for x < 0 and x > getLength() are undefined. + * + * @param x the lengthwise position in the coordinates of this component. + * @return the inner radius of the component at that position. + */ + public double getInnerRadius(double x); + + + /** + * Return the length of this component. + * + * @return the length of this component. + */ + public double getLength(); + +} diff --git a/src/net/sf/openrocket/rocketcomponent/RadiusRingComponent.java b/src/net/sf/openrocket/rocketcomponent/RadiusRingComponent.java new file mode 100644 index 000000000..f7b7be84d --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/RadiusRingComponent.java @@ -0,0 +1,83 @@ +package net.sf.openrocket.rocketcomponent; + +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.MathUtil; + +/** + * An inner component that consists of a hollow cylindrical component. This can be + * an inner tube, tube coupler, centering ring, bulkhead etc. + * + * The properties include the inner and outer radii, length and radial position. + * + * @author Sampo Niskanen + */ +public abstract class RadiusRingComponent extends RingComponent { + + protected double outerRadius = 0; + protected double innerRadius = 0; + + @Override + public double getOuterRadius() { + if (outerRadiusAutomatic && getParent() instanceof RadialParent) { + RocketComponent parent = getParent(); + double pos1 = this.toRelative(Coordinate.NUL, parent)[0].x; + double pos2 = this.toRelative(new Coordinate(getLength()), parent)[0].x; + pos1 = MathUtil.clamp(pos1, 0, parent.getLength()); + pos2 = MathUtil.clamp(pos2, 0, parent.getLength()); + outerRadius = Math.min(((RadialParent)parent).getInnerRadius(pos1), + ((RadialParent)parent).getInnerRadius(pos2)); + } + + return outerRadius; + } + + @Override + public void setOuterRadius(double r) { + r = Math.max(r,0); + if (MathUtil.equals(outerRadius, r) && !isOuterRadiusAutomatic()) + return; + + outerRadius = r; + outerRadiusAutomatic = false; + if (getInnerRadius() > r) { + innerRadius = r; + innerRadiusAutomatic = false; + } + + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + } + + + @Override + public double getInnerRadius() { + return innerRadius; + } + @Override + public void setInnerRadius(double r) { + r = Math.max(r,0); + if (MathUtil.equals(innerRadius, r)) + return; + + innerRadius = r; + innerRadiusAutomatic = false; + if (getOuterRadius() < r) { + outerRadius = r; + outerRadiusAutomatic = false; + } + + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + } + + + @Override + public double getThickness() { + return Math.max(getOuterRadius() - getInnerRadius(), 0); + } + @Override + public void setThickness(double thickness) { + double outer = getOuterRadius(); + + thickness = MathUtil.clamp(thickness, 0, outer); + setInnerRadius(outer - thickness); + } +} diff --git a/src/net/sf/openrocket/rocketcomponent/RecoveryDevice.java b/src/net/sf/openrocket/rocketcomponent/RecoveryDevice.java new file mode 100644 index 000000000..e85965111 --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/RecoveryDevice.java @@ -0,0 +1,212 @@ +package net.sf.openrocket.rocketcomponent; + +import net.sf.openrocket.material.Material; +import net.sf.openrocket.simulation.FlightEvent; +import net.sf.openrocket.util.MathUtil; +import net.sf.openrocket.util.Pair; +import net.sf.openrocket.util.Prefs; + + +/** + * RecoveryDevice is a class representing devices that slow down descent. + * Recovery devices report that they have no aerodynamic effect, since they + * are within the rocket during ascent. + *

+ * A recovery device includes a surface material of which it is made of. + * The mass of the component is calculated based on the material and the + * area of the device from {@link #getArea()}. {@link #getComponentMass()} + * may be overridden if additional mass needs to be included. + * + * @author Sampo Niskanen + */ +public abstract class RecoveryDevice extends MassObject { + + public static enum DeployEvent { + LAUNCH("Launch (plus NN seconds)") { + @Override + public boolean isActivationEvent(FlightEvent e, RocketComponent source) { + return e.getType() == FlightEvent.Type.LAUNCH; + } + }, + EJECTION("First ejection charge of this stage") { + @Override + public boolean isActivationEvent(FlightEvent e, RocketComponent source) { + if (e.getType() != FlightEvent.Type.EJECTION_CHARGE) + return false; + RocketComponent charge = e.getSource(); + return charge.getStageNumber() == source.getStageNumber(); + } + }, + APOGEE("Apogee") { + @Override + public boolean isActivationEvent(FlightEvent e, RocketComponent source) { + return e.getType() == FlightEvent.Type.APOGEE; + } + }, + ALTITUDE("Specific altitude during descent") { + @SuppressWarnings("unchecked") + @Override + public boolean isActivationEvent(FlightEvent e, RocketComponent source) { + if (e.getType() != FlightEvent.Type.ALTITUDE) + return false; + + double alt = ((RecoveryDevice)source).getDeployAltitude(); + Pair altitude = (Pair)e.getData(); + + return (altitude.getU() >= alt) && (altitude.getV() <= alt); + } + }, + NEVER("Never") { + @Override + public boolean isActivationEvent(FlightEvent e, RocketComponent source) { + return false; + } + } + ; + + private final String description; + + DeployEvent(String description) { + this.description = description; + } + + public abstract boolean isActivationEvent(FlightEvent e, RocketComponent source); + + @Override + public String toString() { + return description; + } + + } + + + private DeployEvent deployEvent = DeployEvent.EJECTION; + private double deployAltitude = 200; + private double deployDelay = 0; + + private double cd = Parachute.DEFAULT_CD; + private boolean cdAutomatic = true; + + + private Material.Surface material; + + + public RecoveryDevice() { + this(Prefs.getDefaultComponentMaterial(RecoveryDevice.class, Material.Type.SURFACE)); + } + + public RecoveryDevice(Material material) { + super(); + setMaterial(material); + } + + public RecoveryDevice(double length, double radius, Material material) { + super(length, radius); + setMaterial(material); + } + + + + + public abstract double getArea(); + + public abstract double getComponentCD(double mach); + + + + public double getCD() { + return getCD(0); + } + + public double getCD(double mach) { + if (cdAutomatic) + cd = getComponentCD(mach); + return cd; + } + + public void setCD(double cd) { + if (MathUtil.equals(this.cd, cd) && !isCDAutomatic()) + return; + this.cd = cd; + this.cdAutomatic = false; + fireComponentChangeEvent(ComponentChangeEvent.AERODYNAMIC_CHANGE); + } + + + public boolean isCDAutomatic() { + return cdAutomatic; + } + + public void setCDAutomatic(boolean auto) { + if (cdAutomatic == auto) + return; + this.cdAutomatic = auto; + fireComponentChangeEvent(ComponentChangeEvent.AERODYNAMIC_CHANGE); + } + + + + public final Material getMaterial() { + return material; + } + + public final void setMaterial(Material mat) { + if (!(mat instanceof Material.Surface)) { + throw new IllegalArgumentException("Attempted to set non-surface material "+mat); + } + if (mat.equals(material)) + return; + this.material = (Material.Surface)mat; + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + } + + + + + public DeployEvent getDeployEvent() { + return deployEvent; + } + + public void setDeployEvent(DeployEvent deployEvent) { + if (this.deployEvent == deployEvent) + return; + this.deployEvent = deployEvent; + fireComponentChangeEvent(ComponentChangeEvent.EVENT_CHANGE); + } + + + public double getDeployAltitude() { + return deployAltitude; + } + + public void setDeployAltitude(double deployAltitude) { + if (MathUtil.equals(this.deployAltitude, deployAltitude)) + return; + this.deployAltitude = deployAltitude; + if (getDeployEvent() == DeployEvent.ALTITUDE) + fireComponentChangeEvent(ComponentChangeEvent.EVENT_CHANGE); + else + fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE); + } + + + public double getDeployDelay() { + return deployDelay; + } + + public void setDeployDelay(double delay) { + delay = MathUtil.max(delay, 0); + if (MathUtil.equals(this.deployDelay, delay)) + return; + this.deployDelay = delay; + fireComponentChangeEvent(ComponentChangeEvent.EVENT_CHANGE); + } + + + + @Override + public double getComponentMass() { + return getArea() * getMaterial().getDensity(); + } + +} diff --git a/src/net/sf/openrocket/rocketcomponent/ReferenceType.java b/src/net/sf/openrocket/rocketcomponent/ReferenceType.java new file mode 100644 index 000000000..02264ebe5 --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/ReferenceType.java @@ -0,0 +1,50 @@ +/** + * + */ +package net.sf.openrocket.rocketcomponent; + +public enum ReferenceType { + + NOSECONE { + @Override + public double getReferenceLength(Configuration config) { + for (RocketComponent c: config) { + if (c instanceof SymmetricComponent) { + SymmetricComponent s = (SymmetricComponent)c; + if (s.getForeRadius() >= 0.0005) + return s.getForeRadius() * 2; + if (s.getAftRadius() >= 0.0005) + return s.getAftRadius() * 2; + } + } + return Rocket.DEFAULT_REFERENCE_LENGTH; + } + }, + + MAXIMUM { + @Override + public double getReferenceLength(Configuration config) { + double r = 0; + for (RocketComponent c: config) { + if (c instanceof SymmetricComponent) { + SymmetricComponent s = (SymmetricComponent)c; + r = Math.max(r, s.getForeRadius()); + r = Math.max(r, s.getAftRadius()); + } + } + r *= 2; + if (r < 0.001) + r = Rocket.DEFAULT_REFERENCE_LENGTH; + return r; + } + }, + + CUSTOM { + @Override + public double getReferenceLength(Configuration config) { + return config.getRocket().getCustomReferenceLength(); + } + }; + + public abstract double getReferenceLength(Configuration rocket); +} diff --git a/src/net/sf/openrocket/rocketcomponent/RingComponent.java b/src/net/sf/openrocket/rocketcomponent/RingComponent.java new file mode 100644 index 000000000..7c8523b00 --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/RingComponent.java @@ -0,0 +1,191 @@ +package net.sf.openrocket.rocketcomponent; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.MathUtil; + + +/** + * An inner component that consists of a hollow cylindrical component. This can be + * an inner tube, tube coupler, centering ring, bulkhead etc. + * + * The properties include the inner and outer radii, length and radial position. + * + * @author Sampo Niskanen + */ +public abstract class RingComponent extends StructuralComponent { + + protected boolean outerRadiusAutomatic = false; + protected boolean innerRadiusAutomatic = false; + + + private double radialDirection = 0; + private double radialPosition = 0; + + private double shiftY = 0; + private double shiftZ = 0; + + + + + public abstract double getOuterRadius(); + public abstract void setOuterRadius(double r); + + public abstract double getInnerRadius(); + public abstract void setInnerRadius(double r); + + public abstract double getThickness(); + public abstract void setThickness(double thickness); + + + public final boolean isOuterRadiusAutomatic() { + return outerRadiusAutomatic; + } + + protected void setOuterRadiusAutomatic(boolean auto) { + if (auto == outerRadiusAutomatic) + return; + outerRadiusAutomatic = auto; + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + } + + + public final boolean isInnerRadiusAutomatic() { + return innerRadiusAutomatic; + } + + protected void setInnerRadiusAutomatic(boolean auto) { + if (auto == innerRadiusAutomatic) + return; + innerRadiusAutomatic = auto; + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + } + + + + + public final void setLength(double length) { + double l = Math.max(length,0); + if (this.length == l) + return; + + this.length = l; + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + } + + + /** + * Return the radial direction of displacement of the component. Direction 0 + * is equivalent to the Y-direction. + * + * @return the radial direction. + */ + public double getRadialDirection() { + return radialDirection; + } + + /** + * Set the radial direction of displacement of the component. Direction 0 + * is equivalent to the Y-direction. + * + * @param dir the radial direction. + */ + public void setRadialDirection(double dir) { + dir = MathUtil.reduce180(dir); + if (radialDirection == dir) + return; + radialDirection = dir; + shiftY = radialPosition * Math.cos(radialDirection); + shiftZ = radialPosition * Math.sin(radialDirection); + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + } + + + + + /** + * Return the radial position of the component. The position is the distance + * of the center of the component from the center of the parent component. + * + * @return the radial position. + */ + public double getRadialPosition() { + return radialPosition; + } + + /** + * Set the radial position of the component. The position is the distance + * of the center of the component from the center of the parent component. + * + * @param pos the radial position. + */ + public void setRadialPosition(double pos) { + pos = Math.max(pos, 0); + if (radialPosition == pos) + return; + radialPosition = pos; + shiftY = radialPosition * Math.cos(radialDirection); + shiftZ = radialPosition * Math.sin(radialDirection); + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + } + + + + /** + * Return the number of times the component is multiplied. + */ + public int getClusterCount() { + if (this instanceof Clusterable) + return ((Clusterable)this).getClusterConfiguration().getClusterCount(); + return 1; + } + + + /** + * Shift the coordinates according to the radial position and direction. + */ + @Override + public Coordinate[] shiftCoordinates(Coordinate[] array) { + for (int i=0; i < array.length; i++) { + array[i] = array[i].add(0, shiftY, shiftZ); + } + return array; + } + + + @Override + public Collection getComponentBounds() { + List bounds = new ArrayList(); + addBound(bounds,0,getOuterRadius()); + addBound(bounds,length,getOuterRadius()); + return bounds; + } + + + + @Override + public Coordinate getComponentCG() { + return new Coordinate(length/2, 0, 0, getComponentMass()); + } + + @Override + public double getComponentMass() { + return ringMass(getOuterRadius(), getInnerRadius(), getLength(), + getMaterial().getDensity()) * getClusterCount(); + } + + + @Override + public double getLongitudalUnitInertia() { + return ringLongitudalUnitInertia(getOuterRadius(), getInnerRadius(), getLength()); + } + + @Override + public double getRotationalUnitInertia() { + return ringRotationalUnitInertia(getOuterRadius(), getInnerRadius()); + } + +} diff --git a/src/net/sf/openrocket/rocketcomponent/Rocket.java b/src/net/sf/openrocket/rocketcomponent/Rocket.java new file mode 100644 index 000000000..709c7a59a --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/Rocket.java @@ -0,0 +1,740 @@ +package net.sf.openrocket.rocketcomponent; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import javax.swing.event.ChangeListener; +import javax.swing.event.EventListenerList; + +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.MathUtil; + + +/** + * Base for all rocket components. This is the "starting point" for all rocket trees. + * It provides the actual implementations of several methods defined in RocketComponent + * (eg. the rocket listener lists) and the methods defined in RocketComponent call these. + * It also defines some other methods that concern the whole rocket, and helper methods + * that keep information about the program state. + * + * @author Sampo Niskanen + */ + +public class Rocket extends RocketComponent { + public static final double DEFAULT_REFERENCE_LENGTH = 0.01; + + private static final boolean DEBUG_LISTENERS = false; + + + /** + * The next modification ID to use. This variable may only be accessed via + * the synchronized {@link #getNextModID()} method! + */ + private static int nextModID = 1; + + + /** + * List of component change listeners. + */ + private EventListenerList listenerList = new EventListenerList(); + + /** + * When freezeList != null, events are not dispatched but stored in the list. + * When the structure is thawed, a single combined event will be fired. + */ + private List freezeList = null; + + + private int modID; + private int massModID; + private int aeroModID; + private int treeModID; + private int functionalModID; + + + private ReferenceType refType = ReferenceType.MAXIMUM; // Set in constructor + private double customReferenceLength = DEFAULT_REFERENCE_LENGTH; + + + // The default configuration used in dialogs + private final Configuration defaultConfiguration; + + + private String designer = ""; + private String revision = ""; + + + // Motor configuration list + private List motorConfigurationIDs = new ArrayList(); + private Map motorConfigurationNames = new HashMap(); + { + motorConfigurationIDs.add(null); + } + + + // Does the rocket have a perfect finish (a notable amount of laminar flow) + private boolean perfectFinish = false; + + + + ///////////// Constructor ///////////// + + public Rocket() { + super(RocketComponent.Position.AFTER); + modID = getNextModID(); + massModID = modID; + aeroModID = modID; + treeModID = modID; + functionalModID = modID; + defaultConfiguration = new Configuration(this); + } + + + + public String getDesigner() { + return designer; + } + + public void setDesigner(String s) { + if (s == null) + s = ""; + designer = s; + fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE); + } + + + public String getRevision() { + return revision; + } + + public void setRevision(String s) { + if (s == null) + s = ""; + revision = s; + fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE); + } + + + + + /** + * Return the number of stages in this rocket. + * + * @return the number of stages in this rocket. + */ + public int getStageCount() { + return this.getChildCount(); + } + + + + /** + * Return the non-negative modification ID of this rocket. The ID is changed + * every time any change occurs in the rocket. This can be used to check + * whether it is necessary to void cached data in cases where listeners can not + * or should not be used. + *

+ * Three other modification IDs are also available, {@link #getMassModID()}, + * {@link #getAerodynamicModID()} {@link #getTreeModID()}, which change every time + * a mass change, aerodynamic change, or tree change occur. Even though the values + * of the different modification ID's may be equal, they should be treated totally + * separate. + *

+ * Note that undo events restore the modification IDs that were in use at the + * corresponding undo level. Subsequent modifications, however, produce modIDs + * distinct from those already used. + * + * @return a unique ID number for this modification state. + */ + public int getModID() { + return modID; + } + + /** + * Return the non-negative mass modification ID of this rocket. See + * {@link #getModID()} for details. + * + * @return a unique ID number for this mass-modification state. + */ + public int getMassModID() { + return massModID; + } + + /** + * Return the non-negative aerodynamic modification ID of this rocket. See + * {@link #getModID()} for details. + * + * @return a unique ID number for this aerodynamic-modification state. + */ + public int getAerodynamicModID() { + return aeroModID; + } + + /** + * Return the non-negative tree modification ID of this rocket. See + * {@link #getModID()} for details. + * + * @return a unique ID number for this tree-modification state. + */ + public int getTreeModID() { + return treeModID; + } + + /** + * Return the non-negative functional modificationID of this rocket. + * This changes every time a functional change occurs. + * + * @return a unique ID number for this functional modification state. + */ + public int getFunctionalModID() { + return functionalModID; + } + + + + + public ReferenceType getReferenceType() { + return refType; + } + + public void setReferenceType(ReferenceType type) { + if (refType == type) + return; + refType = type; + fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE); + } + + + public double getCustomReferenceLength() { + return customReferenceLength; + } + + public void setCustomReferenceLength(double length) { + if (MathUtil.equals(customReferenceLength, length)) + return; + + this.customReferenceLength = Math.max(length,0.001); + + if (refType == ReferenceType.CUSTOM) { + fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE); + } + } + + + + + + /** + * Set whether the rocket has a perfect finish. This will affect whether the + * boundary layer is assumed to be fully turbulent or not. + * + * @param perfectFinish whether the finish is perfect. + */ + public void setPerfectFinish(boolean perfectFinish) { + if (this.perfectFinish == perfectFinish) + return; + this.perfectFinish = perfectFinish; + fireComponentChangeEvent(ComponentChangeEvent.AERODYNAMIC_CHANGE); + } + + + + /** + * Get whether the rocket has a perfect finish. + * + * @return the perfectFinish + */ + public boolean isPerfectFinish() { + return perfectFinish; + } + + + + /** + * Return a new unique modification ID. This method is thread-safe. + * + * @return a new modification ID unique to this session. + */ + private synchronized int getNextModID() { + return nextModID++; + } + + + /** + * Make a deep copy of the Rocket structure. This is a helper method which simply + * casts the result of the superclass method to a Rocket. + */ + @Override + public Rocket copy() { + Rocket copy = (Rocket)super.copy(); + copy.resetListeners(); + return copy; + } + + + + + + + /** + * Load the rocket structure from the source. The method loads the fields of this + * Rocket object and copies the references to siblings from the source. + * The object source should not be used after this call, as it is in + * an illegal state! + *

+ * This method is meant to be used in conjunction with undo/redo functionality, + * and therefore fires an UNDO_EVENT, masked with all applicable mass/aerodynamic/tree + * changes. + */ + public void loadFrom(Rocket r) { + super.copyFrom(r); + + int type = ComponentChangeEvent.UNDO_CHANGE | ComponentChangeEvent.NONFUNCTIONAL_CHANGE; + if (this.massModID != r.massModID) + type |= ComponentChangeEvent.MASS_CHANGE; + if (this.aeroModID != r.aeroModID) + type |= ComponentChangeEvent.AERODYNAMIC_CHANGE; + if (this.treeModID != r.treeModID) + type |= ComponentChangeEvent.TREE_CHANGE; + + this.modID = r.modID; + this.massModID = r.massModID; + this.aeroModID = r.aeroModID; + this.treeModID = r.treeModID; + this.functionalModID = r.functionalModID; + this.refType = r.refType; + this.customReferenceLength = r.customReferenceLength; + + this.motorConfigurationIDs = r.motorConfigurationIDs; + this.motorConfigurationNames = r.motorConfigurationNames; + this.perfectFinish = r.perfectFinish; + + fireComponentChangeEvent(type); + } + + + + + /////// Implement the ComponentChangeListener lists + + /** + * Creates a new EventListenerList for this component. This is necessary when cloning + * the structure. + */ + public void resetListeners() { +// System.out.println("RESETTING LISTENER LIST of Rocket "+this); + listenerList = new EventListenerList(); + } + + + public void printListeners() { + System.out.println(""+this+" has "+listenerList.getListenerCount()+" listeners:"); + Object[] list = listenerList.getListenerList(); + for (int i=1; i iterator = this.deepIterator(true); + while (iterator.hasNext()) { + iterator.next().componentChanged(e); + } + + // Notify all listeners + Object[] listeners = listenerList.getListenerList(); + for (int i = listeners.length-2; i>=0; i-=2) { + if (listeners[i] == ComponentChangeListener.class) { + ((ComponentChangeListener) listeners[i+1]).componentChanged(e); + } else if (listeners[i] == ChangeListener.class) { + ((ChangeListener) listeners[i+1]).stateChanged(e); + } + } + } + + + /** + * Freezes the rocket structure from firing any events. This may be performed to + * combine several actions on the structure into a single large action. + * thaw() must always be called afterwards. + * + * NOTE: Always use a try/finally to ensure thaw() is called: + *

+	 *     Rocket r = c.getRocket();
+	 *     try {
+	 *         r.freeze();
+	 *         // do stuff
+	 *     } finally {
+	 *         r.thaw();
+	 *     }
+	 * 
+ * + * @see #thaw() + */ + public void freeze() { + if (freezeList == null) + freezeList = new LinkedList(); + } + + /** + * Thaws a frozen rocket structure and fires a combination of the events fired during + * the freeze. The event type is a combination of those fired and the source is the + * last component to have been an event source. + * + * @see #freeze() + */ + public void thaw() { + if (freezeList == null) + return; + if (freezeList.size()==0) { + freezeList = null; + return; + } + + int type = 0; + Object c = null; + for (ComponentChangeEvent e: freezeList) { + type = type | e.getType(); + c = e.getSource(); + } + freezeList = null; + + fireComponentChangeEvent(new ComponentChangeEvent((RocketComponent)c,type)); + } + + + + + //////// Motor configurations //////// + + + /** + * Return the default configuration. This should be used in the user interface + * to ensure a consistent rocket configuration between dialogs. It should NOT + * be used in simulations not relating to the UI. + * + * @return the default {@link Configuration}. + */ + public Configuration getDefaultConfiguration() { + return defaultConfiguration; + } + + + /** + * Return an array of the motor configuration IDs. This array is guaranteed + * to contain the null ID as the first element. + * + * @return an array of the motor configuration IDs. + */ + public String[] getMotorConfigurationIDs() { + return motorConfigurationIDs.toArray(new String[0]); + } + + /** + * Add a new motor configuration ID to the motor configurations. The new ID + * is returned. + * + * @return the new motor configuration ID. + */ + public String newMotorConfigurationID() { + String id = UUID.randomUUID().toString(); + motorConfigurationIDs.add(id); + fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE); + return id; + } + + /** + * Add a specified motor configuration ID to the motor configurations. + * + * @param id the motor configuration ID. + * @return true if successful, false if the ID was already used. + */ + public boolean addMotorConfigurationID(String id) { + if (id == null || motorConfigurationIDs.contains(id)) + return false; + motorConfigurationIDs.add(id); + fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE); + return true; + } + + /** + * Remove a motor configuration ID from the configuration IDs. The null + * ID cannot be removed, and an attempt to remove it will be silently ignored. + * + * @param id the motor configuration ID to remove + */ + public void removeMotorConfigurationID(String id) { + if (id == null) + return; + motorConfigurationIDs.remove(id); + fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE); + } + + + /** + * Return the user-set name of the motor configuration. If no name has been set, + * returns an empty string (not null). + * + * @param id the motor configuration id + * @return the configuration name + */ + public String getMotorConfigurationName(String id) { + String s = motorConfigurationNames.get(id); + if (s == null) + return ""; + return s; + } + + + /** + * Set the name of the motor configuration. A name can be unset by passing + * null or an empty string. + * + * @param id the motor configuration id + * @param name the name for the motor configuration + */ + public void setMotorConfigurationName(String id, String name) { + motorConfigurationNames.put(id,name); + fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE); + } + + + /** + * Return a description for the motor configuration. This is either the + * name previously set by {@link #setMotorConfigurationName(String, String)} or + * a string generated from the motor designations of the components. + * + * @param id the motor configuration ID. + * @return a textual representation of the configuration + */ + @SuppressWarnings("null") + public String getMotorConfigurationDescription(String id) { + String name; + int motorCount = 0; + + if (!motorConfigurationIDs.contains(id)) { + throw new IllegalArgumentException("Motor configuration ID does not exist: "+id); + } + + name = motorConfigurationNames.get(id); + if (name != null && !name.equals("")) + return name; + + // Generate the description + + // First iterate over each stage and store the designations of each motor + List> list = new ArrayList>(); + List currentList = null; + + Iterator iterator = this.deepIterator(); + while (iterator.hasNext()) { + RocketComponent c = iterator.next(); + + if (c instanceof Stage) { + + currentList = new ArrayList(); + list.add(currentList); + + } else if (c instanceof MotorMount) { + + MotorMount mount = (MotorMount) c; + Motor motor = mount.getMotor(id); + + if (mount.isMotorMount() && motor != null) { + String designation = motor.getDesignation(mount.getMotorDelay(id)); + + for (int i=0; i < mount.getMotorCount(); i++) { + currentList.add(designation); + motorCount++; + } + } + + } + } + + if (motorCount == 0) { + return "[No motors]"; + } + + // Change multiple occurrences of a motor to n x motor + List stages = new ArrayList(); + + for (List stage: list) { + String stageName = ""; + String previous = null; + int count = 0; + + Collections.sort(stage); + for (String current: stage) { + if (current.equals(previous)) { + + count++; + + } else { + + if (previous != null) { + String s = ""; + if (count > 1) { + s = "" + count + "\u00d7" + previous; + } else { + s = previous; + } + + if (stageName.equals("")) + stageName = s; + else + stageName = stageName + "," + s; + } + + previous = current; + count = 1; + + } + } + if (previous != null) { + String s = ""; + if (count > 1) { + s = "" + count + "\u00d7" + previous; + } else { + s = previous; + } + + if (stageName.equals("")) + stageName = s; + else + stageName = stageName + "," + s; + } + + stages.add(stageName); + } + + name = "["; + for (int i=0; i < stages.size(); i++) { + String s = stages.get(i); + if (s.equals("")) + s = "None"; + if (i==0) + name = name + s; + else + name = name + "; " + s; + } + name += "]"; + return name; + } + + + + //////// Obligatory component information + + + @Override + public String getComponentName() { + return "Rocket"; + } + + @Override + public Coordinate getComponentCG() { + return new Coordinate(0,0,0,0); + } + + @Override + public double getComponentMass() { + return 0; + } + + @Override + public double getLongitudalUnitInertia() { + return 0; + } + + @Override + public double getRotationalUnitInertia() { + return 0; + } + + @Override + public Collection getComponentBounds() { + return Collections.emptyList(); + } + + @Override + public boolean isAerodynamic() { + return false; + } + + @Override + public boolean isMassive() { + return false; + } + + /** + * Allows only Stage components to be added to the type Rocket. + */ + @Override + public boolean isCompatible(Class type) { + return (Stage.class.isAssignableFrom(type)); + } +} diff --git a/src/net/sf/openrocket/rocketcomponent/RocketComponent.java b/src/net/sf/openrocket/rocketcomponent/RocketComponent.java new file mode 100644 index 000000000..335a22311 --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/RocketComponent.java @@ -0,0 +1,1438 @@ +package net.sf.openrocket.rocketcomponent; + +import java.awt.Color; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.EmptyStackException; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Stack; +import java.util.UUID; + +import javax.swing.event.ChangeListener; + +import net.sf.openrocket.util.ChangeSource; +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.LineStyle; +import net.sf.openrocket.util.MathUtil; + + +public abstract class RocketComponent implements ChangeSource, Cloneable, + Iterable { + + /* + * Text is suitable to the form + * Position relative to: + */ + public enum Position { + /** Position relative to the top of the parent component. */ + TOP("Top of the parent component"), + /** Position relative to the middle of the parent component. */ + MIDDLE("Middle of the parent component"), + /** Position relative to the bottom of the parent component. */ + BOTTOM("Bottom of the parent component"), + /** Position after the parent component (for body components). */ + AFTER("After the parent component"), + /** Specify an absolute X-coordinate position. */ + ABSOLUTE("Tip of the nose cone"); + + private String title; + Position(String title) { + this.title = title; + } + + @Override + public String toString() { + return title; + } + } + + //////// Parent/child trees + /** + * Parent component of the current component, or null if none exists. + */ + private RocketComponent parent = null; + + /** + * List of child components of this component. + */ + private List<RocketComponent> children = new ArrayList<RocketComponent>(); + + + //////// Parameters common to all components: + + /** + * Characteristic length of the component. This is used in calculating the coordinate + * transformations and positions of other components in reference to this component. + * This may and should be used as the "true" length of the component, where applicable. + * By default it is zero, i.e. no translation. + */ + protected double length = 0; + + /** + * Positioning of this component relative to the parent component. + */ + protected Position relativePosition; + + /** + * Offset of the position of this component relative to the normal position given by + * relativePosition. By default zero, i.e. no position change. + */ + protected double position = 0; + + + // Color of the component, null means to use the default color + private Color color = null; + private LineStyle lineStyle = null; + + + // Override mass/CG + private double overrideMass = 0; + private boolean massOverriden = false; + private double overrideCGX = 0; + private boolean cgOverriden = false; + + private boolean overrideSubcomponents = false; + + + // User-given name of the component + private String name = null; + + // User-specified comment + private String comment = ""; + + // Unique ID of the component + private String id = null; + + //// NOTE !!! All fields must be copied in the method copyFrom()! //// + + + + /** + * Default constructor. Sets the name of the component to the component's static name + * and the relative position of the component. + */ + public RocketComponent(Position relativePosition) { + // These must not fire any events, due to Rocket undo system initialization + this.name = getComponentName(); + this.relativePosition = relativePosition; + this.id = UUID.randomUUID().toString(); + } + + + + + + //////////// Methods that must be implemented //////////// + + + /** + * Static component name. The name may not vary of the parameters, it must be static. + */ + public abstract String getComponentName(); // Static component type name + + /** + * Return the component mass (regardless of mass overriding). + */ + public abstract double getComponentMass(); // Mass of non-overridden component + + /** + * Return the component CG and mass (regardless of CG or mass overriding). + */ + public abstract Coordinate getComponentCG(); // CG of non-overridden component + + + /** + * Return the longitudal (around the y- or z-axis) unitary moment of inertia. + * The unitary moment of inertia is the moment of inertia with the assumption that + * the mass of the component is one kilogram. The inertia is measured in + * respect to the non-overridden CG. + * + * @return the longitudal unitary moment of inertia of this component. + */ + public abstract double getLongitudalUnitInertia(); + + + /** + * Return the rotational (around the x-axis) unitary moment of inertia. + * The unitary moment of inertia is the moment of inertia with the assumption that + * the mass of the component is one kilogram. The inertia is measured in + * respect to the non-overridden CG. + * + * @return the rotational unitary moment of inertia of this component. + */ + public abstract double getRotationalUnitInertia(); + + + + + /** + * Test whether the given component type can be added to this component. This type safety + * is enforced by the <code>addChild()</code> methods. The return value of this method + * may change to reflect the current state of this component (e.g. two components of some + * type cannot be placed as children). + * + * @param type The RocketComponent class type to add. + * @return Whether such a component can be added. + */ + public abstract boolean isCompatible(Class<? extends RocketComponent> type); + + + /* Non-abstract helper method */ + /** + * Test whether the given component can be added to this component. This is equivalent + * to calling <code>isCompatible(c.getClass())</code>. + * + * @param c Component to test. + * @return Whether the component can be added. + * @see #isCompatible(Class) + */ + public final boolean isCompatible(RocketComponent c) { + return isCompatible(c.getClass()); + } + + + + /** + * Return a collection of bounding coordinates. The coordinates must be such that + * the component is fully enclosed in their convex hull. + * + * @return a collection of coordinates that bound the component. + */ + public abstract Collection<Coordinate> getComponentBounds(); + + /** + * Return true if the component may have an aerodynamic effect on the rocket. + */ + public abstract boolean isAerodynamic(); + + /** + * Return true if the component may have an effect on the rocket's mass. + */ + public abstract boolean isMassive(); + + + + + + //////////// Methods that may be overridden //////////// + + + /** + * Shift the coordinates in the array corresponding to radial movement. A component + * that has a radial position must shift the coordinates in this array suitably. + * If the component is clustered, then a new array must be returned with a + * coordinate for each cluster. + * <p> + * The default implementation simply returns the array, and thus produces no shift. + * + * @param c an array of coordinates to shift. + * @return an array of shifted coordinates. The method may modify the contents + * of the passed array and return the array itself. + */ + public Coordinate[] shiftCoordinates(Coordinate[] c) { + return c; + } + + + /** + * Called when any component in the tree fires a ComponentChangeEvent. This is by + * default a no-op, but subclasses may override this method to e.g. invalidate + * cached data. The overriding method *must* call + * <code>super.componentChanged(e)</code> at some point. + * + * @param e The event fired + */ + protected void componentChanged(ComponentChangeEvent e) { + // No-op + } + + + + + /** + * Return a descriptive name of the component. + * + * The description may include extra information about the type of component, + * e.g. "Conical nose cone". + * + * @return A string describing the component. + */ + @Override + public final String toString() { + if (name.equals("")) + return getComponentName(); + else + return name; + } + + + public final void printStructure() { + System.out.println("Rocket structure from '"+this.toString()+"':"); + printStructure(0); + } + + private void printStructure(int level) { + String s = ""; + + for (int i=0; i < level; i++) { + s += " "; + } + s += this.toString() + " (" + this.getComponentName()+")"; + System.out.println(s); + + for (RocketComponent c: children) { + c.printStructure(level+1); + } + } + + + /** + * Make a deep copy of the rocket component tree structure from this component + * downwards. This method does not fire any events. + * <p> + * This method must be overridden by any component that refers to mutable objects, + * or if some fields should not be copied. This should be performed by + * <code>RocketComponent c = super.copy();</code> and then cloning/modifying the + * appropriate fields. + * <p> + * This is not performed as serializing/deserializing for performance reasons. + * + * @return A deep copy of the structure. + */ + public RocketComponent copy() { + RocketComponent clone; + try { + clone = (RocketComponent)this.clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeException("CloneNotSupportedException encountered, " + + "report a bug!",e); + } + + // Reset all parent/child information + clone.parent = null; + clone.children = new ArrayList<RocketComponent>(); + + // Add copied children to the structure without firing events. + for (RocketComponent c: this.children) { + RocketComponent copy = c.copy(); + clone.children.add(copy); + copy.parent = clone; + } + + return clone; + } + + + ////////////// Methods that may not be overridden //////////// + + + + ////////// Common parameter setting/getting ////////// + + /** + * Return the color of the object to use in 2D figures, or <code>null</code> + * to use the default color. + */ + public final Color getColor() { + return color; + } + + /** + * Set the color of the object to use in 2D figures. + */ + public final void setColor(Color c) { + if ((color == null && c == null) || + (color != null && color.equals(c))) + return; + + this.color = c; + fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE); + } + + + public final LineStyle getLineStyle() { + return lineStyle; + } + + public final void setLineStyle(LineStyle style) { + if (this.lineStyle == style) + return; + this.lineStyle = style; + fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE); + } + + + + + /** + * Get the current override mass. The mass is not necessarily in use + * at the moment. + * + * @return the override mass + */ + public final double getOverrideMass() { + return overrideMass; + } + + /** + * Set the current override mass. The mass is not set to use by this + * method. + * + * @param m the override mass + */ + public final void setOverrideMass(double m) { + overrideMass = Math.max(m,0); + if (massOverriden) + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + } + + /** + * Return whether mass override is active for this component. This does NOT + * take into account whether a parent component is overriding the mass. + * + * @return whether the mass is overridden + */ + public final boolean isMassOverridden() { + return massOverriden; + } + + /** + * Set whether the mass is currently overridden. + * + * @param o whether the mass is overridden + */ + public final void setMassOverridden(boolean o) { + if (massOverriden != o) { + massOverriden = o; + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + } + } + + + + + + /** + * Return the current override CG. The CG is not necessarily overridden. + * + * @return the override CG + */ + public final Coordinate getOverrideCG() { + return getComponentCG().setX(overrideCGX); + } + + /** + * Return the x-coordinate of the current override CG. + * + * @return the x-coordinate of the override CG. + */ + public final double getOverrideCGX() { + return overrideCGX; + } + + /** + * Set the current override CG to (x,0,0). + * + * @param x the x-coordinate of the override CG to set. + */ + public final void setOverrideCGX(double x) { + if (MathUtil.equals(overrideCGX, x)) + return; + this.overrideCGX = x; + if (isCGOverridden()) + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + else + fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE); + } + + /** + * Return whether the CG is currently overridden. + * + * @return whether the CG is overridden + */ + public final boolean isCGOverridden() { + return cgOverriden; + } + + /** + * Set whether the CG is currently overridden. + * + * @param o whether the CG is overridden + */ + public final void setCGOverridden(boolean o) { + if (cgOverriden != o) { + cgOverriden = o; + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + } + } + + + + /** + * Return whether the mass and/or CG override overrides all subcomponent values + * as well. The default implementation is a normal getter/setter implementation, + * however, subclasses are allowed to override this behavior if some subclass + * always or never overrides subcomponents. In this case the subclass should + * also override {@link #isOverrideSubcomponentsEnabled()} to return + * <code>false</code>. + * + * @return whether the current mass and/or CG override overrides subcomponents as well. + */ + public boolean getOverrideSubcomponents() { + return overrideSubcomponents; + } + + + /** + * Set whether the mass and/or CG override overrides all subcomponent values + * as well. See {@link #getOverrideSubcomponents()} for details. + * + * @param override whether the mass and/or CG override overrides all subcomponent. + */ + public void setOverrideSubcomponents(boolean override) { + if (overrideSubcomponents != override) { + overrideSubcomponents = override; + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + } + } + + /** + * Return whether the option to override all subcomponents is enabled or not. + * The default implementation returns <code>false</code> if neither mass nor + * CG is overridden, <code>true</code> otherwise. + * <p> + * This method may be overridden if the setting of overriding subcomponents + * cannot be set. + * + * @return whether the option to override subcomponents is currently enabled. + */ + public boolean isOverrideSubcomponentsEnabled() { + return isCGOverridden() || isMassOverridden(); + } + + + + + /** + * Get the user-defined name of the component. + */ + public final String getName() { + return name; + } + + /** + * Set the user-defined name of the component. If name==null, sets the name to + * the default name, currently the component name. + */ + public final void setName(String name) { +// System.out.println("Set name called:"+name+" orig:"+this.name); + if (name==null || name.matches("^\\s*$")) + this.name = getComponentName(); + else + this.name = name; + fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE); + } + + + /** + * Return the comment of the component. The component may contain multiple lines + * using \n as a newline separator. + * + * @return the comment of the component. + */ + public final String getComment() { + return comment; + } + + /** + * Set the comment of the component. + * + * @param comment the comment of the component. + */ + public final void setComment(String comment) { + if (this.comment.equals(comment)) + return; + if (comment == null) + this.comment = ""; + else + this.comment = comment; + fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE); + } + + + + /** + * Returns the unique ID of the component. + * + * @return the ID of the component. + */ + public final String getID() { + return id; + } + + + /** + * Set the unique ID of the component. If <code>id</code> in <code>null</code> then + * this method generates a new unique ID for the component. + * <p> + * This method should be used only in special cases, such as when creating database + * entries with empty IDs. + * + * @param id the ID to set. + */ + public final void setID(String id) { + if (id == null) { + this.id = UUID.randomUUID().toString(); + } else { + this.id = id; + } + fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE); + } + + + + + /** + * Get the characteristic length of the component, for example the length of a body tube + * of the length of the root chord of a fin. This is used in positioning the component + * relative to its parent. + * + * If the length of a component is settable, the class must define the setter method + * itself. + */ + public final double getLength() { + return length; + } + + /** + * Get the positioning of the component relative to its parent component. + * This is one of the enums of {@link Position}. A setter method is not provided, + * but can be provided by a subclass. + */ + public final Position getRelativePosition() { + return relativePosition; + } + + + /** + * Set the positioning of the component relative to its parent component. + * The actual position of the component is maintained to the best ability. + * <p> + * The default implementation is of protected visibility, since many components + * do not support setting the relative position. A component that does support + * it should override this with a public method that simply calls this + * supermethod AND fire a suitable ComponentChangeEvent. + * + * @param position the relative positioning. + */ + protected void setRelativePosition(RocketComponent.Position position) { + if (this.relativePosition == position) + return; + + // Update position so as not to move the component + if (this.parent != null) { + double thisPos = this.toRelative(Coordinate.NUL,this.parent)[0].x; + + switch (position) { + case ABSOLUTE: + this.position = this.toAbsolute(Coordinate.NUL)[0].x; + break; + + case TOP: + this.position = thisPos; + break; + + case MIDDLE: + this.position = thisPos - (this.parent.length - this.length)/2; + break; + + case BOTTOM: + this.position = thisPos - (this.parent.length - this.length); + break; + + default: + assert(false): "Should not occur"; + } + } + + this.relativePosition = position; + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + } + + + + + /** + * Get the position value of the component. The exact meaning of the value is + * dependent on the current relative positioning. + * + * @return the positional value. + */ + public final double getPositionValue() { + return position; + } + + + /** + * Set the position value of the component. The exact meaning of the value + * depends on the current relative positioning. + * <p> + * The default implementation is of protected visibility, since many components + * do not support setting the relative position. A component that does support + * it should override this with a public method that simply calls this + * supermethod AND fire a suitable ComponentChangeEvent. + * + * @param value the position value of the component. + */ + public void setPositionValue(double value) { + if (MathUtil.equals(this.position, value)) + return; + this.position = value; + } + + + + /////////// Coordinate changes /////////// + + /** + * Returns coordinate c in absolute coordinates. Equivalent to toComponent(c,null). + */ + public Coordinate[] toAbsolute(Coordinate c) { + return toRelative(c,null); + } + + + /** + * Return coordinate <code>c</code> described in the coordinate system of + * <code>dest</code>. If <code>dest</code> is <code>null</code> returns + * absolute coordinates. + * <p> + * This method returns an array of coordinates, each of which represents a + * position of the coordinate in clustered cases. The array is guaranteed + * to contain at least one element. + * <p> + * The current implementation does not support rotating components. + * + * @param c Coordinate in the component's coordinate system. + * @param dest Destination component coordinate system. + * @return an array of coordinates describing <code>c</code> in coordinates + * relative to <code>dest</code>. + */ + public final Coordinate[] toRelative(Coordinate c, RocketComponent dest) { + double absoluteX = Double.NaN; + RocketComponent search = dest; + Coordinate[] array = new Coordinate[1]; + array[0] = c; + + RocketComponent component = this; + while ((component != search) && (component.parent != null)) { + + array = component.shiftCoordinates(array); + + switch (component.relativePosition) { + case TOP: + for (int i=0; i < array.length; i++) { + array[i] = array[i].add(component.position,0,0); + } + break; + + case MIDDLE: + for (int i=0; i < array.length; i++) { + array[i] = array[i].add(component.position + + (component.parent.length-component.length)/2,0,0); + } + break; + + case BOTTOM: + for (int i=0; i < array.length; i++) { + array[i] = array[i].add(component.position + + (component.parent.length-component.length),0,0); + } + break; + + case AFTER: + // Add length of all previous brother-components with POSITION_RELATIVE_AFTER + int index = component.parent.children.indexOf(component); + assert(index >= 0); + for (index--; index >= 0; index--) { + RocketComponent comp = component.parent.children.get(index); + double length = comp.getTotalLength(); + for (int i=0; i < array.length; i++) { + array[i] = array[i].add(length,0,0); + } + } + for (int i=0; i < array.length; i++) { + array[i] = array[i].add(component.position + component.parent.length,0,0); + } + break; + + case ABSOLUTE: + search = null; // Requires back-search if dest!=null + if (Double.isNaN(absoluteX)) { + absoluteX = component.position; + } + break; + + default: + throw new RuntimeException("Unknown relative positioning type of component"+ + component+": "+component.relativePosition); + } + + component = component.parent; // parent != null + } + + if (!Double.isNaN(absoluteX)) { + for (int i=0; i < array.length; i++) { + array[i] = array[i].setX(absoluteX + c.x); + } + } + + // Check whether destination has been found or whether to backtrack + // TODO: LOW: Backtracking into clustered components uses only one component + if ((dest != null) && (component != dest)) { + Coordinate[] origin = dest.toAbsolute(Coordinate.NUL); + for (int i=0; i < array.length; i++) { + array[i] = array[i].sub(origin[0]); + } + } + + return array; + } + + + /** + * Recursively sum the lengths of all subcomponents that have position + * Position.AFTER. + * + * @return Sum of the lengths. + */ + private final double getTotalLength() { + double l=0; + if (relativePosition == Position.AFTER) + l = length; + for (int i=0; i<children.size(); i++) + l += children.get(i).getTotalLength(); + return l; + } + + + + /////////// Total mass and CG calculation //////////// + + /** + * Return the (possibly overridden) mass of component. + * + * @return The mass of the component or the given override mass. + */ + public final double getMass() { + if (massOverriden) + return overrideMass; + return getComponentMass(); + } + + /** + * Return the (possibly overridden) center of gravity and mass. + * + * Returns the CG with the weight of the coordinate set to the weight of the component. + * Both CG and mass may be separately overridden. + * + * @return The CG of the component or the given override CG. + */ + public final Coordinate getCG() { + if (cgOverriden) + return getOverrideCG().setWeight(getMass()); + + if (massOverriden) + return getComponentCG().setWeight(getMass()); + + return getComponentCG(); + } + + + /** + * Return the longitudal (around the y- or z-axis) moment of inertia of this component. + * The moment of inertia is scaled in reference to the (possibly overridden) mass + * and is relative to the non-overridden CG. + * + * @return the longitudal moment of inertia of this component. + */ + public final double getLongitudalInertia() { + return getLongitudalUnitInertia() * getMass(); + } + + /** + * Return the rotational (around the y- or z-axis) moment of inertia of this component. + * The moment of inertia is scaled in reference to the (possibly overridden) mass + * and is relative to the non-overridden CG. + * + * @return the rotational moment of inertia of this component. + */ + public final double getRotationalInertia() { + return getRotationalUnitInertia() * getMass(); + } + + + + /////////// Children handling /////////// + + + /** + * Adds a child to the rocket component tree. The component is added to the end + * of the component's child list. This is a helper method that calls + * {@link #addChild(RocketComponent,int)}. + * + * @param component The component to add. + * @throws IllegalArgumentException if the component is already part of some + * component tree. + * @see #addChild(RocketComponent,int) + */ + public final void addChild(RocketComponent component) { + addChild(component,children.size()); + } + + + /** + * Adds a child to the rocket component tree. The component is added to + * the given position of the component's child list. + * <p> + * This method may be overridden to enforce more strict component addition rules. + * The tests should be performed first and then this method called. + * + * @param component The component to add. + * @param position Position to add component to. + * @throws IllegalArgumentException If the component is already part of + * some component tree. + */ + public void addChild(RocketComponent component, int position) { + if (component.parent != null) { + throw new IllegalArgumentException("component "+component.getComponentName()+ + " is already in a tree"); + } + if (!isCompatible(component)) { + throw new IllegalStateException("Component "+component.getComponentName()+ + " not currently compatible with component "+getComponentName()); + } + + children.add(position,component); + component.parent = this; + + fireAddRemoveEvent(component); + } + + + /** + * Removes a child from the rocket component tree. + * + * @param n remove the n'th child. + * @throws IndexOutOfBoundsException if n is out of bounds + */ + public final void removeChild(int n) { + RocketComponent component = children.remove(n); + component.parent = null; + fireAddRemoveEvent(component); + } + + /** + * Removes a child from the rocket component tree. Does nothing if the component + * is not present as a child. + * + * @param component the component to remove + */ + public final void removeChild(RocketComponent component) { + if (children.remove(component)) { + component.parent = null; + + fireAddRemoveEvent(component); + } + } + + + + + /** + * Move a child to another position. + * + * @param component the component to move + * @param position the component's new position + * @throws IllegalArgumentException If an illegal placement was attempted. + */ + public final void moveChild(RocketComponent component, int position) { + if (children.remove(component)) { + children.add(position, component); + fireAddRemoveEvent(component); + } + } + + + /** + * Fires an AERODYNAMIC_CHANGE, MASS_CHANGE or OTHER_CHANGE event depending on the + * type of component removed. + */ + private void fireAddRemoveEvent(RocketComponent component) { + Iterator<RocketComponent> iter = component.deepIterator(true); + int type = ComponentChangeEvent.TREE_CHANGE; + while (iter.hasNext()) { + RocketComponent c = iter.next(); + if (c.isAerodynamic()) + type |= ComponentChangeEvent.AERODYNAMIC_CHANGE; + if (c.isMassive()) + type |= ComponentChangeEvent.MASS_CHANGE; + } + + fireComponentChangeEvent(type); + } + + + public final int getChildCount() { + return children.size(); + } + + public final RocketComponent getChild(int n) { + return children.get(n); + } + + public final RocketComponent[] getChildren() { + return children.toArray(new RocketComponent[0]); + } + + + /** + * Returns the position of the child in this components child list, or -1 if the + * component is not a child of this component. + * + * @param child The child to search for. + * @return Position in the list or -1 if not found. + */ + public final int getChildPosition(RocketComponent child) { + return children.indexOf(child); + } + + /** + * Get the parent component of this component. Returns <code>null</code> if the component + * has no parent. + * + * @return The parent of this component or <code>null</code>. + */ + public final RocketComponent getParent() { + return parent; + } + + /** + * Get the root component of the component tree. + * + * @return The root component of the component tree. + */ + public final RocketComponent getRoot() { + RocketComponent gp = this; + while (gp.parent != null) + gp = gp.parent; + return gp; + } + + /** + * Returns the root Rocket component of this component tree. Throws an + * IllegalStateException if the root component is not a Rocket. + * + * @return The root Rocket component of the component tree. + * @throws IllegalStateException If the root component is not a Rocket. + */ + public final Rocket getRocket() { + RocketComponent r = getRoot(); + if (r instanceof Rocket) + return (Rocket)r; + throw new IllegalStateException("getRocket() called with root component " + +r.getComponentName()); + } + + + /** + * Return the Stage component that this component belongs to. Throws an + * IllegalStateException if a Stage is not in the parentage of this component. + * + * @return The Stage component this component belongs to. + * @throws IllegalStateException if a Stage component is not in the parentage. + */ + public final Stage getStage() { + RocketComponent c = this; + while (c != null) { + if (c instanceof Stage) + return (Stage)c; + c = c.getParent(); + } + throw new IllegalStateException("getStage() called without Stage as a parent."); + } + + /** + * Return the stage number of the stage this component belongs to. The stages + * are numbered from zero upwards. + * + * @return the stage number this component belongs to. + */ + public final int getStageNumber() { + if (parent == null) { + throw new IllegalArgumentException("getStageNumber() called for root component"); + } + + RocketComponent stage = this; + while (!(stage instanceof Stage)) { + stage = stage.parent; + } + return stage.parent.getChildPosition(stage); + } + + + /** + * Find a component with the given ID. The component tree is searched from this component + * down (including this component) for the ID and the corresponding component is returned, + * or null if not found. + * + * @param id ID to search for. + * @return The component with the ID, or null if not found. + */ + public final RocketComponent findComponent(String id) { + Iterator<RocketComponent> iter = this.deepIterator(true); + while (iter.hasNext()) { + RocketComponent c = iter.next(); + if (c.id.equals(id)) + return c; + } + return null; + } + + + public final RocketComponent getPreviousComponent() { + if (parent == null) + return null; + int pos = parent.getChildPosition(this); + assert(pos >= 0); + if (pos == 0) + return parent; + RocketComponent c = parent.getChild(pos-1); + while (c.getChildCount() > 0) + c = c.getChild(c.getChildCount()-1); + return c; + } + + public final RocketComponent getNextComponent() { + if (getChildCount() > 0) + return getChild(0); + + RocketComponent current = this; + RocketComponent parent = this.parent; + + while (parent != null) { + int pos = parent.getChildPosition(current); + if (pos < parent.getChildCount()-1) + return parent.getChild(pos+1); + + current = parent; + parent = current.parent; + } + return null; + } + + + /////////// Event handling ////////// + // + // Listener lists are provided by the root Rocket component, + // a single listener list for the whole rocket. + // + + /** + * Adds a ComponentChangeListener to the rocket tree. The listener is added to the root + * component, which must be of type Rocket (which overrides this method). Events of all + * subcomponents are sent to all listeners. + * + * @throws IllegalStateException - if the root component is not a Rocket + */ + public void addComponentChangeListener(ComponentChangeListener l) { + getRocket().addComponentChangeListener(l); + } + + /** + * Removes a ComponentChangeListener from the rocket tree. The listener is removed from + * the root component, which must be of type Rocket (which overrides this method). + * + * @param l Listener to remove + * @throws IllegalStateException - if the root component is not a Rocket + */ + public void removeComponentChangeListener(ComponentChangeListener l) { + getRocket().removeComponentChangeListener(l); + } + + + /** + * Adds a <code>ChangeListener</code> to the rocket tree. This is identical to + * <code>addComponentChangeListener()</code> except that it uses a + * <code>ChangeListener</code>. The same events are dispatched to the + * <code>ChangeListener</code>, as <code>ComponentChangeEvent</code> is a subclass + * of <code>ChangeEvent</code>. + * + * @throws IllegalStateException - if the root component is not a <code>Rocket</code> + */ + public void addChangeListener(ChangeListener l) { + getRocket().addChangeListener(l); + } + + /** + * Removes a ChangeListener from the rocket tree. This is identical to + * removeComponentChangeListener() except it uses a ChangeListener. + * + * @param l Listener to remove + * @throws IllegalStateException - if the root component is not a Rocket + */ + public void removeChangeListener(ChangeListener l) { + getRocket().removeChangeListener(l); + } + + + /** + * Fires a ComponentChangeEvent on the rocket structure. The call is passed to the + * root component, which must be of type Rocket (which overrides this method). + * Events of all subcomponents are sent to all listeners. + * + * If the component tree root is not a Rocket, the event is ignored. This is the + * case when constructing components not in any Rocket tree. In this case it + * would be impossible for the component to have listeners in any case. + * + * @param e Event to send + */ + protected void fireComponentChangeEvent(ComponentChangeEvent e) { + if (parent==null) { + /* Ignore if root invalid. */ + return; + } + getRoot().fireComponentChangeEvent(e); + } + + + /** + * Fires a ComponentChangeEvent of the given type. The source of the event is set to + * this component. + * + * @param type Type of event + * @see #fireComponentChangeEvent(ComponentChangeEvent) + */ + protected void fireComponentChangeEvent(int type) { + fireComponentChangeEvent(new ComponentChangeEvent(this,type)); + } + + + + /////////// Iterator implementation ////////// + + /** + * Private inner class to implement the Iterator. + * + * This iterator is fail-fast if the root of the structure is a Rocket. + */ + private class RocketComponentIterator implements Iterator<RocketComponent> { + // Stack holds iterators which still have some components left. + private final Stack<Iterator<RocketComponent>> iteratorstack = + new Stack<Iterator<RocketComponent>>(); + + private final Rocket root; + private final int treeModID; + + private final RocketComponent original; + private boolean returnSelf=false; + + // Construct iterator with component's child's iterator, if it has elements + public RocketComponentIterator(RocketComponent c, boolean returnSelf) { + + RocketComponent gp = c.getRoot(); + if (gp instanceof Rocket) { + root = (Rocket)gp; + treeModID = root.getTreeModID(); + } else { + root = null; + treeModID = -1; + } + + Iterator<RocketComponent> i = c.children.iterator(); + if (i.hasNext()) + iteratorstack.push(i); + + this.original = c; + this.returnSelf = returnSelf; + } + + public boolean hasNext() { + checkID(); + if (returnSelf) + return true; + return !iteratorstack.empty(); // Elements remain if stack is not empty + } + + public RocketComponent next() { + Iterator<RocketComponent> i; + + checkID(); + + // Return original component first + if (returnSelf) { + returnSelf=false; + return original; + } + + // Peek first iterator from stack, throw exception if empty + try { + i = iteratorstack.peek(); + } catch (EmptyStackException e) { + throw new NoSuchElementException("No further elements in " + + "RocketComponent iterator"); + } + + // Retrieve next component of the iterator, remove iterator from stack if empty + RocketComponent c = i.next(); + if (!i.hasNext()) + iteratorstack.pop(); + + // Add iterator of component children to stack if it has children + i = c.children.iterator(); + if (i.hasNext()) + iteratorstack.push(i); + + return c; + } + + private void checkID() { + if (root != null) { + if (root.getTreeModID() != treeModID) { + throw new IllegalStateException("Rocket modified while being iterated"); + } + } + } + + public void remove() { + throw new UnsupportedOperationException("remove() not supported by " + + "RocketComponent iterator"); + } + } + + /** + * Returns an iterator that iterates over all children and sub-children. + * + * The iterator iterates through all children below this object, including itself if + * returnSelf is true. The order of the iteration is not specified + * (it may be specified in the future). + * + * If an iterator iterating over only the direct children of the component is required, + * use component.getChildren().iterator() + * + * @param returnSelf boolean value specifying whether the component itself should be + * returned + * @return An iterator for the children and sub-children. + */ + public final Iterator<RocketComponent> deepIterator(boolean returnSelf) { + return new RocketComponentIterator(this,returnSelf); + } + + /** + * Returns an iterator that iterates over all children and sub-children. + * + * The iterator does NOT return the component itself. It is thus equivalent to + * deepIterator(false). + * + * @see #iterator() + * @return An iterator for the children and sub-children. + */ + public final Iterator<RocketComponent> deepIterator() { + return new RocketComponentIterator(this,false); + } + + + /** + * Return an iterator that iterates of the children of the component. The iterator + * does NOT recurse to sub-children nor return itself. + * + * @return An iterator for the children. + */ + public final Iterator<RocketComponent> iterator() { + return Collections.unmodifiableList(children).iterator(); + } + + //////////// Helper methods for subclasses + + /** + * Helper method to add rotationally symmetric bounds at the specified coordinates. + * The X-axis value is <code>x</code> and the radius at the specified position is + * <code>r</code>. + */ + protected static final void addBound(Collection<Coordinate> bounds, double x, double r) { + bounds.add(new Coordinate(x,-r,-r)); + bounds.add(new Coordinate(x, r,-r)); + bounds.add(new Coordinate(x, r, r)); + bounds.add(new Coordinate(x,-r, r)); + } + + + protected static final Coordinate ringCG(double outerRadius, double innerRadius, + double x1, double x2, double density) { + return new Coordinate((x1+x2)/2, 0, 0, + ringMass(outerRadius, innerRadius, x2-x1, density)); + } + + protected static final double ringMass(double outerRadius, double innerRadius, + double length, double density) { + return Math.PI*(MathUtil.pow2(outerRadius) - MathUtil.pow2(innerRadius)) * + length * density; + } + + protected static final double ringLongitudalUnitInertia(double outerRadius, + double innerRadius, double length) { + // 1/12 * (3 * (r1^2 + r2^2) + h^2) + return (3 * (MathUtil.pow2(innerRadius) + MathUtil.pow2(outerRadius)) + + MathUtil.pow2(length)) / 12; + } + + protected static final double ringRotationalUnitInertia(double outerRadius, + double innerRadius) { + // 1/2 * (r1^2 + r2^2) + return (MathUtil.pow2(innerRadius) + MathUtil.pow2(outerRadius))/2; + } + + + + //////////// OTHER + + + /** + * Loads the RocketComponent fields from the given component. This method is meant + * for use with the undo/redo mechanism. + * + * The fields are copied by reference, and the supplied component must not be used + * after the call, as it is in an undefined state. + * + * TODO: MEDIUM: Make general to copy all private/protected fields... + */ + protected void copyFrom(RocketComponent src) { + // Set parents and children + this.parent = null; + this.children = src.children; + src.children = new ArrayList<RocketComponent>(); + + for (RocketComponent c: this.children) { + c.parent = this; + } + + // Set all parameters + this.length = src.length; + this.relativePosition = src.relativePosition; + this.position = src.position; + this.color = src.color; + this.lineStyle = src.lineStyle; + this.overrideMass = src.overrideMass; + this.massOverriden = src.massOverriden; + this.overrideCGX = src.overrideCGX; + this.cgOverriden = src.cgOverriden; + this.overrideSubcomponents = src.overrideSubcomponents; + this.name = src.name; + this.comment = src.comment; + this.id = src.id; + } + +} diff --git a/src/net/sf/openrocket/rocketcomponent/ShockCord.java b/src/net/sf/openrocket/rocketcomponent/ShockCord.java new file mode 100644 index 000000000..2e7e4cf8a --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/ShockCord.java @@ -0,0 +1,62 @@ +package net.sf.openrocket.rocketcomponent; + +import net.sf.openrocket.material.Material; +import net.sf.openrocket.util.MathUtil; +import net.sf.openrocket.util.Prefs; + +public class ShockCord extends MassObject { + + private Material material; + private double cordLength; + + public ShockCord() { + material = Prefs.getDefaultComponentMaterial(ShockCord.class, Material.Type.LINE); + cordLength = 0.4; + } + + + + public Material getMaterial() { + return material; + } + + public void setMaterial(Material m) { + if (m.getType() != Material.Type.LINE) + throw new RuntimeException("Attempting to set non-linear material."); + if (material.equals(m)) + return; + this.material = m; + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + } + + + public double getCordLength() { + return cordLength; + } + + public void setCordLength(double length) { + length = MathUtil.max(length, 0); + if (MathUtil.equals(length, this.length)) + return; + this.cordLength = length; + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + } + + + + @Override + public double getComponentMass() { + return material.getDensity() * cordLength; + } + + @Override + public String getComponentName() { + return "Shock cord"; + } + + @Override + public boolean isCompatible(Class<? extends RocketComponent> type) { + return false; + } + +} diff --git a/src/net/sf/openrocket/rocketcomponent/Sleeve.java b/src/net/sf/openrocket/rocketcomponent/Sleeve.java new file mode 100644 index 000000000..fd0db53ef --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/Sleeve.java @@ -0,0 +1,101 @@ +package net.sf.openrocket.rocketcomponent; + +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.MathUtil; + + +/** + * A RingComponent that comes on top of another tube. It's defined by the inner + * radius and thickness. The inner radius can be automatic, in which case it + * takes the radius of the parent component. + * + * @author Sampo Niskanen <sampo.niskanen@iki.fi> + */ +public class Sleeve extends RingComponent { + + protected double innerRadius = 0; + protected double thickness = 0; + + + public Sleeve() { + super(); + setInnerRadiusAutomatic(true); + setThickness(0.001); + setLength(0.05); + } + + + @Override + public double getOuterRadius() { + return getInnerRadius() + thickness; + } + + @Override + public void setOuterRadius(double r) { + if (MathUtil.equals(getOuterRadius(), r)) + return; + + innerRadius = Math.max(r - thickness, 0); + if (thickness > r) + thickness = r; + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + } + + + @Override + public double getInnerRadius() { + // Implement parent inner radius automation + if (isInnerRadiusAutomatic() && getParent() instanceof RadialParent) { + RocketComponent parent = getParent(); + double pos1 = this.toRelative(Coordinate.NUL, parent)[0].x; + double pos2 = this.toRelative(new Coordinate(getLength()), parent)[0].x; + pos1 = MathUtil.clamp(pos1, 0, parent.getLength()); + pos2 = MathUtil.clamp(pos2, 0, parent.getLength()); + innerRadius = Math.max(((RadialParent)parent).getOuterRadius(pos1), + ((RadialParent)parent).getOuterRadius(pos2)); + } + + return innerRadius; + } + + @Override + public void setInnerRadius(double r) { + r = Math.max(r,0); + if (MathUtil.equals(innerRadius, r)) + return; + innerRadius = r; + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + } + + @Override + public double getThickness() { + return thickness; + } + + @Override + public void setThickness(double t) { + t = Math.max(t, 0); + if (MathUtil.equals(thickness, t)) + return; + thickness = t; + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + } + + + + + @Override + public void setInnerRadiusAutomatic(boolean auto) { + super.setOuterRadiusAutomatic(auto); + } + + @Override + public String getComponentName() { + return "Sleeve"; + } + + @Override + public boolean isCompatible(Class<? extends RocketComponent> type) { + return false; + } +} diff --git a/src/net/sf/openrocket/rocketcomponent/Stage.java b/src/net/sf/openrocket/rocketcomponent/Stage.java new file mode 100644 index 000000000..74a36a5eb --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/Stage.java @@ -0,0 +1,23 @@ +package net.sf.openrocket.rocketcomponent; + +public class Stage extends ComponentAssembly { + + @Override + public String getComponentName() { + return "Stage"; + } + + + /** + * Check whether the given type can be added to this component. A Stage allows + * only BodyComponents to be added. + * + * @param type The RocketComponent class type to add. + * @return Whether such a component can be added. + */ + @Override + public boolean isCompatible(Class<? extends RocketComponent> type) { + return BodyComponent.class.isAssignableFrom(type); + } + +} diff --git a/src/net/sf/openrocket/rocketcomponent/Streamer.java b/src/net/sf/openrocket/rocketcomponent/Streamer.java new file mode 100644 index 000000000..134f3ee9d --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/Streamer.java @@ -0,0 +1,105 @@ +package net.sf.openrocket.rocketcomponent; + +import net.sf.openrocket.util.MathUtil; + +public class Streamer extends RecoveryDevice { + + public static final double DEFAULT_CD = 0.6; + + public static final double MAX_COMPUTED_CD = 0.4; + + + private double stripLength; + private double stripWidth; + + + public Streamer() { + this.stripLength = 0.5; + this.stripWidth = 0.05; + } + + + public double getStripLength() { + return stripLength; + } + + public void setStripLength(double stripLength) { + if (MathUtil.equals(this.stripLength, stripLength)) + return; + this.stripLength = stripLength; + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + } + + public double getStripWidth() { + return stripWidth; + } + + public void setStripWidth(double stripWidth) { + if (MathUtil.equals(this.stripWidth, stripWidth)) + return; + this.stripWidth = stripWidth; + this.length = stripWidth; + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + } + + @Override + public void setLength(double length) { + setStripWidth(length); + } + + + public double getAspectRatio() { + if (stripWidth > 0.0001) + return stripLength/stripWidth; + return 1000; + } + + public void setAspectRatio(double ratio) { + if (MathUtil.equals(getAspectRatio(), ratio)) + return; + + ratio = Math.max(ratio, 0.01); + double area = getArea(); + stripWidth = Math.sqrt(area / ratio); + stripLength = ratio * stripWidth; + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + } + + + @Override + public double getArea() { + return stripWidth * stripLength; + } + + public void setArea(double area) { + if (MathUtil.equals(getArea(), area)) + return; + + double ratio = Math.max(getAspectRatio(), 0.01); + stripWidth = Math.sqrt(area / ratio); + stripLength = ratio * stripWidth; + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + } + + + + @Override + public double getComponentCD(double mach) { + double density = this.getMaterial().getDensity(); + double cd; + + cd = 0.034 * ((density + 0.025)/0.105) * (stripLength+1) / stripLength; + cd = MathUtil.min(cd, MAX_COMPUTED_CD); + return cd; + } + + @Override + public String getComponentName() { + return "Streamer"; + } + + @Override + public boolean isCompatible(Class<? extends RocketComponent> type) { + return false; + } +} diff --git a/src/net/sf/openrocket/rocketcomponent/StructuralComponent.java b/src/net/sf/openrocket/rocketcomponent/StructuralComponent.java new file mode 100644 index 000000000..ff6afd48d --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/StructuralComponent.java @@ -0,0 +1,29 @@ +package net.sf.openrocket.rocketcomponent; + +import net.sf.openrocket.material.Material; +import net.sf.openrocket.util.Prefs; + +public abstract class StructuralComponent extends InternalComponent { + + private Material material; + + public StructuralComponent() { + super(); + material = Prefs.getDefaultComponentMaterial(this.getClass(), Material.Type.BULK); + } + + + public final Material getMaterial() { + return material; + } + + public final void setMaterial(Material mat) { + if (mat.getType() != Material.Type.BULK) { + throw new IllegalArgumentException("Attempted to set non-bulk material "+mat); + } + if (mat.equals(material)) + return; + this.material = mat; + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + } +} diff --git a/src/net/sf/openrocket/rocketcomponent/SymmetricComponent.java b/src/net/sf/openrocket/rocketcomponent/SymmetricComponent.java new file mode 100644 index 000000000..ed0bffb6a --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/SymmetricComponent.java @@ -0,0 +1,552 @@ +package net.sf.openrocket.rocketcomponent; + +import static net.sf.openrocket.util.MathUtil.pow2; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.MathUtil; + + +/** + * Class for an axially symmetric rocket component generated by rotating + * a function y=f(x) >= 0 around the x-axis (eg. tube, cone, etc.) + * + * @author Sampo Niskanen <sampo.niskanen@iki.fi> + */ + +public abstract class SymmetricComponent extends BodyComponent implements RadialParent { + public static final double DEFAULT_RADIUS = 0.025; + public static final double DEFAULT_THICKNESS = 0.002; + + private static final int DIVISIONS = 100; // No. of divisions when integrating + + protected boolean filled = false; + protected double thickness = DEFAULT_THICKNESS; + + + // Cached data, default values signify not calculated + private double wetArea = -1; + private double planArea = -1; + private double planCenter = -1; + private double volume = -1; + private double fullVolume = -1; + private double longitudalInertia = -1; + private double rotationalInertia = -1; + private Coordinate cg = null; + + + + public SymmetricComponent() { + super(); + } + + + /** + * Return the component radius at position x. + * @param x Position on x-axis. + * @return Radius of the component at the given position, or 0 if outside + * the component. + */ + public abstract double getRadius(double x); + public abstract double getInnerRadius(double x); + + public abstract double getForeRadius(); + public abstract boolean isForeRadiusAutomatic(); + public abstract double getAftRadius(); + public abstract boolean isAftRadiusAutomatic(); + + + // Implement the Radial interface: + public final double getOuterRadius(double x) { + return getRadius(x); + } + + + @Override + public final double getRadius(double x, double theta) { + return getRadius(x); + } + + @Override + public final double getInnerRadius(double x, double theta) { + return getInnerRadius(x); + } + + + + /** + * Return the component wall thickness. + */ + public double getThickness() { + if (filled) + return Math.max(getForeRadius(),getAftRadius()); + return Math.min(thickness,Math.max(getForeRadius(),getAftRadius())); + } + + + /** + * Set the component wall thickness. Values greater than the maximum radius are not + * allowed, and will result in setting the thickness to the maximum radius. + */ + public void setThickness(double thickness) { + if ((this.thickness == thickness) && !filled) + return; + this.thickness = MathUtil.clamp(thickness,0,Math.max(getForeRadius(),getAftRadius())); + filled = false; + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + } + + + /** + * Returns whether the component is set as filled. If it is set filled, then the + * wall thickness will have no effect. + */ + public boolean isFilled() { + return filled; + } + + + /** + * Sets whether the component is set as filled. If the component is filled, then + * the wall thickness will have no effect. + */ + public void setFilled(boolean filled) { + if (this.filled == filled) + return; + this.filled = filled; + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + } + + + /** + * Adds component bounds at a number of points between 0...length. + */ + @Override + public Collection<Coordinate> getComponentBounds() { + List<Coordinate> list = new ArrayList<Coordinate>(20); + for (int n=0; n<=5; n++) { + double x = n*length/5; + double r = getRadius(x); + addBound(list,x,r); + } + return list; + } + + + + /** + * Calculate volume of the component by integrating over the length of the component. + * The method caches the result, so subsequent calls are instant. Subclasses may + * override this method for simple shapes and use this method as necessary. + * + * @return The volume of the component. + */ + @Override + public double getComponentVolume() { + if (volume < 0) + integrate(); + return volume; + } + + + /** + * Calculate full (filled) volume of the component by integrating over the length + * of the component. The method caches the result, so subsequent calls are instant. + * Subclasses may override this method for simple shapes and use this method as + * necessary. + * + * @return The filled volume of the component. + */ + public double getFullVolume() { + if (fullVolume < 0) + integrate(); + return fullVolume; + } + + + /** + * Calculate the wetted area of the component by integrating over the length + * of the component. The method caches the result, so subsequent calls are instant. + * Subclasses may override this method for simple shapes and use this method as + * necessary. + * + * @return The wetted area of the component. + */ + public double getComponentWetArea() { + if (wetArea < 0) + integrate(); + return wetArea; + } + + + /** + * Calculate the planform area of the component by integrating over the length of + * the component. The method caches the result, so subsequent calls are instant. + * Subclasses may override this method for simple shapes and use this method as + * necessary. + * + * @return The planform area of the component. + */ + public double getComponentPlanformArea() { + if (planArea < 0) + integrate(); + return planArea; + } + + + /** + * Calculate the planform center X-coordinate of the component by integrating over + * the length of the component. The planform center is defined as + * <pre> integrate(x*2*r(x)) / planform area </pre> + * The method caches the result, so subsequent calls are instant. Subclasses may + * override this method for simple shapes and use this method as necessary. + * + * @return The planform center of the component. + */ + public double getComponentPlanformCenter() { + if (planCenter < 0) + integrate(); + return planCenter; + } + + + /** + * Calculate CG of the component by integrating over the length of the component. + * The method caches the result, so subsequent calls are instant. Subclasses may + * override this method for simple shapes and use this method as necessary. + * + * @return The CG+mass of the component. + */ + @Override + public Coordinate getComponentCG() { + if (cg == null) + integrate(); + return cg; + } + + + @Override + public double getLongitudalUnitInertia() { + if (longitudalInertia < 0) { + if (getComponentVolume() > 0.0000001) // == 0.1cm^3 + integrateInertiaVolume(); + else + integrateInertiaSurface(); + } + return longitudalInertia; + } + + + @Override + public double getRotationalUnitInertia() { + if (rotationalInertia < 0) { + if (getComponentVolume() > 0.0000001) + integrateInertiaVolume(); + else + integrateInertiaSurface(); + } + return rotationalInertia; + } + + + + /** + * Performs integration over the length of the component and updates the cached variables. + */ + private void integrate() { + double x,r1,r2; + double cgx; + + // Check length > 0 + if (length <= 0) { + wetArea = 0; + planArea = 0; + planCenter = 0; + volume = 0; + cg = Coordinate.NUL; + return; + } + + + // Integrate for volume, CG, wetted area and planform area + + final double l = length/DIVISIONS; + final double pil = Math.PI*l; // PI * l + final double pil3 = Math.PI*l/3; // PI * l/3 + r1 = getRadius(0); + x = 0; + wetArea = 0; + planArea = 0; + planCenter = 0; + fullVolume = 0; + volume = 0; + cgx = 0; + + for (int n=1; n<=DIVISIONS; n++) { + /* + * r1 and r2 are the two radii + * x is the position of r1 + * hyp is the length of the hypotenuse from r1 to r2 + * height if the y-axis height of the component if not filled + */ + + r2 = getRadius(x+l); + final double hyp = MathUtil.hypot(r2-r1, l); + + + // Volume differential elements + final double dV; + final double dFullV; + + dFullV = pil3*(r1*r1 + r1*r2 + r2*r2); + if (filled || r1<thickness || r2<thickness) { + // Filled piece + dV = dFullV; + } else { + // Hollow piece + final double height = thickness*hyp/l; + dV = pil*height*(r1+r2-height); + } + + // Add to the volume-related components + volume += dV; + fullVolume += dFullV; + cgx += (x+l/2)*dV; + + // Wetted area ( * PI at the end) + wetArea += hyp*(r1+r2); + + // Planform area & center + final double p = l*(r1+r2); + planArea += p; + planCenter += (x+l/2)*p; + + // Update for next iteration + r1 = r2; + x += l; + } + + wetArea *= Math.PI; + + if (planArea > 0) + planCenter /= planArea; + + if (volume == 0) { + cg = Coordinate.NUL; + } else { + // getComponentMass is safe now + cg = new Coordinate(cgx/volume,0,0,getComponentMass()); + } + } + + + /** + * Integrate the longitudal and rotational inertia based on component volume. + * This method may be used only if the total volume is zero. + */ + private void integrateInertiaVolume() { + double x, r1, r2; + + final double l = length/DIVISIONS; + final double pil = Math.PI*l; // PI * l + final double pil3 = Math.PI*l/3; // PI * l/3 + + r1 = getRadius(0); + x = 0; + longitudalInertia = 0; + rotationalInertia = 0; + + double volume = 0; + + for (int n=1; n<=DIVISIONS; n++) { + /* + * r1 and r2 are the two radii, outer is their average + * x is the position of r1 + * hyp is the length of the hypotenuse from r1 to r2 + * height if the y-axis height of the component if not filled + */ + r2 = getRadius(x+l); + final double outer = (r1 + r2)/2; + + + // Volume differential elements + final double inner; + final double dV; + + if (filled || r1<thickness || r2<thickness) { + inner = 0; + dV = pil3*(r1*r1 + r1*r2 + r2*r2); + } else { + final double hyp = MathUtil.hypot(r2-r1, l); + final double height = thickness*hyp/l; + dV = pil*height*(r1+r2-height); + inner = Math.max(outer-height, 0); + } + + rotationalInertia += dV * (pow2(outer) + pow2(inner))/2; + longitudalInertia += dV * ((3 * (pow2(outer) + pow2(inner)) + pow2(l))/12 + + pow2(x+l/2)); + + volume += dV; + + // Update for next iteration + r1 = r2; + x += l; + } + + if (MathUtil.equals(volume,0)) { + integrateInertiaSurface(); + return; + } + + rotationalInertia /= volume; + longitudalInertia /= volume; + + // Shift longitudal inertia to CG + longitudalInertia = Math.max(longitudalInertia - pow2(getComponentCG().x), 0); + } + + + + /** + * Integrate the longitudal and rotational inertia based on component surface area. + * This method may be used only if the total volume is zero. + */ + private void integrateInertiaSurface() { + double x, r1, r2; + + final double l = length/DIVISIONS; + + r1 = getRadius(0); + x = 0; + longitudalInertia = 0; + rotationalInertia = 0; + + double surface = 0; + + for (int n=1; n<=DIVISIONS; n++) { + /* + * r1 and r2 are the two radii, outer is their average + * x is the position of r1 + * hyp is the length of the hypotenuse from r1 to r2 + * height if the y-axis height of the component if not filled + */ + r2 = getRadius(x+l); + final double hyp = MathUtil.hypot(r2-r1, l); + final double outer = (r1 + r2)/2; + + final double dS = hyp * (r1+r2) * Math.PI; + + rotationalInertia += dS * pow2(outer); + longitudalInertia += dS * ((6 * pow2(outer) + pow2(l))/12 + pow2(x+l/2)); + + surface += dS; + + // Update for next iteration + r1 = r2; + x += l; + } + + if (MathUtil.equals(surface, 0)) { + longitudalInertia = 0; + rotationalInertia = 0; + return; + } + + longitudalInertia /= surface; + rotationalInertia /= surface; + + // Shift longitudal inertia to CG + longitudalInertia = Math.max(longitudalInertia - pow2(getComponentCG().x), 0); + } + + + + + /** + * Invalidates the cached volume and CG information. + */ + @Override + protected void componentChanged(ComponentChangeEvent e) { + super.componentChanged(e); + if (!e.isOtherChange()) { + wetArea = -1; + planArea = -1; + planCenter = -1; + volume = -1; + fullVolume = -1; + longitudalInertia = -1; + rotationalInertia = -1; + cg = null; + } + } + + + + /////////// Auto radius helper methods + + + /** + * Returns the automatic radius for this component towards the + * front of the rocket. The automatics will not search towards the + * rear of the rocket for a suitable radius. A positive return value + * indicates a preferred radius, a negative value indicates that a + * match was not found. + */ + protected abstract double getFrontAutoRadius(); + + /** + * Returns the automatic radius for this component towards the + * end of the rocket. The automatics will not search towards the + * front of the rocket for a suitable radius. A positive return value + * indicates a preferred radius, a negative value indicates that a + * match was not found. + */ + protected abstract double getRearAutoRadius(); + + + + /** + * Return the previous symmetric component, or null if none exists. + * NOTE: This method currently assumes that there are no external + * "pods". + * + * @return the previous SymmetricComponent, or null. + */ + protected final SymmetricComponent getPreviousSymmetricComponent() { + RocketComponent c; + for (c = this.getPreviousComponent(); c != null; c = c.getPreviousComponent()) { + if (c instanceof SymmetricComponent) { + return (SymmetricComponent)c; + } + if (!(c instanceof Stage) && + (c.relativePosition == RocketComponent.Position.AFTER)) + return null; // Bad component type as "parent" + } + return null; + } + + /** + * Return the next symmetric component, or null if none exists. + * NOTE: This method currently assumes that there are no external + * "pods". + * + * @return the next SymmetricComponent, or null. + */ + protected final SymmetricComponent getNextSymmetricComponent() { + RocketComponent c; + for (c = this.getNextComponent(); c != null; c = c.getNextComponent()) { + if (c instanceof SymmetricComponent) { + return (SymmetricComponent)c; + } + if (!(c instanceof Stage) && + (c.relativePosition == RocketComponent.Position.AFTER)) + return null; // Bad component type as "parent" + } + return null; + } + +} diff --git a/src/net/sf/openrocket/rocketcomponent/ThicknessRingComponent.java b/src/net/sf/openrocket/rocketcomponent/ThicknessRingComponent.java new file mode 100644 index 000000000..4bded7079 --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/ThicknessRingComponent.java @@ -0,0 +1,82 @@ +package net.sf.openrocket.rocketcomponent; + +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.MathUtil; + +/** + * An inner component that consists of a hollow cylindrical component. This can be + * an inner tube, tube coupler, centering ring, bulkhead etc. + * + * The properties include the inner and outer radii, length and radial position. + * + * @author Sampo Niskanen <sampo.niskanen@iki.fi> + */ +public abstract class ThicknessRingComponent extends RingComponent { + + protected double outerRadius = 0; + protected double thickness = 0; + + + @Override + public double getOuterRadius() { + if (isOuterRadiusAutomatic() && getParent() instanceof RadialParent) { + RocketComponent parent = getParent(); + double pos1 = this.toRelative(Coordinate.NUL, parent)[0].x; + double pos2 = this.toRelative(new Coordinate(getLength()), parent)[0].x; + pos1 = MathUtil.clamp(pos1, 0, parent.getLength()); + pos2 = MathUtil.clamp(pos2, 0, parent.getLength()); + outerRadius = Math.min(((RadialParent)parent).getInnerRadius(pos1), + ((RadialParent)parent).getInnerRadius(pos2)); + } + + return outerRadius; + } + + + @Override + public void setOuterRadius(double r) { + r = Math.max(r,0); + if (MathUtil.equals(outerRadius, r) && !isOuterRadiusAutomatic()) + return; + + outerRadius = r; + outerRadiusAutomatic = false; + + if (thickness > outerRadius) + thickness = outerRadius; + + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + } + + + + @Override + public double getThickness() { + return Math.min(thickness, getOuterRadius()); + } + @Override + public void setThickness(double thickness) { + double outer = getOuterRadius(); + + thickness = MathUtil.clamp(thickness, 0, outer); + if (MathUtil.equals(getThickness(), thickness)) + return; + + this.thickness = thickness; + + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + } + + + @Override + public double getInnerRadius() { + return Math.max(getOuterRadius()-thickness, 0); + } + @Override + public void setInnerRadius(double r) { + r = Math.max(r,0); + setThickness(getOuterRadius() - r); + } + + +} diff --git a/src/net/sf/openrocket/rocketcomponent/ThrustCurveMotor.java b/src/net/sf/openrocket/rocketcomponent/ThrustCurveMotor.java new file mode 100644 index 000000000..a29f55719 --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/ThrustCurveMotor.java @@ -0,0 +1,121 @@ +package net.sf.openrocket.rocketcomponent; + +import net.sf.openrocket.util.Coordinate; + +/** + * A class of motors specified by a fixed thrust curve. This is the most + * accurate for solid rocket motors. + * + * @author Sampo Niskanen <sampo.niskanen@iki.fi> + */ +public class ThrustCurveMotor extends Motor { + + private final double[] time; + private final double[] thrust; + private final Coordinate[] cg; + + private final double totalTime; + private final double maxThrust; + + + /** + * Sole constructor. Sets all the properties of the motor. + * + * @param manufacturer the manufacturer of the motor. + * @param designation the designation of the motor. + * @param description extra description of the motor. + * @param diameter diameter of the motor. + * @param length length of the motor. + * @param time the time points for the thrust curve. + * @param thrust thrust at the time points. + * @param cg cg at the time points. + */ + public ThrustCurveMotor(String manufacturer, String designation, String description, + Motor.Type type, double[] delays, double diameter, double length, + double[] time, double[] thrust, Coordinate[] cg) { + super(manufacturer, designation, description, type, delays, diameter, length); + + double max = -1; + + // Check argument validity + if ((time.length != thrust.length) || (time.length != cg.length)) { + throw new IllegalArgumentException("Array lengths do not match, " + + "time:" + time.length + " thrust:" + thrust.length + + " cg:" + cg.length); + } + if (time.length < 2) { + throw new IllegalArgumentException("Too short thrust-curve, length=" + + time.length); + } + for (int i=0; i < time.length-1; i++) { + if (time[i+1] < time[i]) { + throw new IllegalArgumentException("Time goes backwards, " + + "time[" + i + "]=" + time[i] + " " + + "time[" + (i+1) + "]=" + time[i+1]); + } + } + if (time[0] != 0) { + throw new IllegalArgumentException("Curve starts at time=" + time[0]); + } + for (double t: thrust) { + if (t < 0) { + throw new IllegalArgumentException("Negative thrust."); + } + if (t > max) + max = t; + } + + this.maxThrust = max; + this.time = time.clone(); + this.thrust = thrust.clone(); + this.cg = cg.clone(); + this.totalTime = time[time.length-1]; + } + + + @Override + public double getTotalTime() { + return totalTime; + } + + @Override + public double getMaxThrust() { + return maxThrust; + } + + @Override + public double getThrust(double t) { + if ((t < 0) || (t > totalTime)) + return 0; + + for (int i=0; i < time.length-1; i++) { + if ((t >= time[i]) && (t <= time[i+1])) { + double delta = time[i+1] - time[i]; + t = t - time[i]; + return thrust[i] * (1 - t/delta) + thrust[i+1] * (t/delta); + } + } + assert false : "Should not be reached."; + return 0; + } + + + @Override + public Coordinate getCG(double t) { + if (t <= 0) + return cg[0]; + if (t >= totalTime) + return cg[cg.length-1]; + + for (int i=0; i < time.length-1; i++) { + if ((t >= time[i]) && (t <= time[i+1])) { + double delta = time[i+1] - time[i]; + t = t - time[i]; + return cg[i].multiply(1 - t/delta).add(cg[i+1].multiply(t/delta)); + } + } + assert false : "Should not be reached."; + return cg[cg.length-1]; + } + +} diff --git a/src/net/sf/openrocket/rocketcomponent/Transition.java b/src/net/sf/openrocket/rocketcomponent/Transition.java new file mode 100644 index 000000000..7155a7988 --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/Transition.java @@ -0,0 +1,834 @@ +package net.sf.openrocket.rocketcomponent; + +import static java.lang.Math.sin; +import static java.lang.Math.sqrt; +import static net.sf.openrocket.util.MathUtil.pow2; +import static net.sf.openrocket.util.MathUtil.pow3; + +import java.util.Collection; + +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.MathUtil; + + +public class Transition extends SymmetricComponent { + private static final double CLIP_PRECISION = 0.0001; + + + private Shape type; + private double shapeParameter; + private boolean clipped; // Not to be read - use isClipped(), which may be overriden + + private double radius1, radius2; + private boolean autoRadius1, autoRadius2; // Whether the start radius is automatic + + + private double foreShoulderRadius; + private double foreShoulderThickness; + private double foreShoulderLength; + private boolean foreShoulderCapped; + private double aftShoulderRadius; + private double aftShoulderThickness; + private double aftShoulderLength; + private boolean aftShoulderCapped; + + + // Used to cache the clip length + private double clipLength=-1; + + public Transition() { + super(); + + this.radius1 = DEFAULT_RADIUS; + this.radius2 = DEFAULT_RADIUS; + this.length = DEFAULT_RADIUS * 3; + this.autoRadius1 = true; + this.autoRadius2 = true; + + this.type = Shape.CONICAL; + this.shapeParameter = 0; + this.clipped = true; + } + + + + + //////// Fore radius //////// + + + @Override + public double getForeRadius() { + if (isForeRadiusAutomatic()) { + // Get the automatic radius from the front + double r = -1; + SymmetricComponent c = this.getPreviousSymmetricComponent(); + if (c != null) { + r = c.getFrontAutoRadius(); + } + if (r < 0) + r = DEFAULT_RADIUS; + return r; + } + return radius1; + } + + public void setForeRadius(double radius) { + if ((this.radius1 == radius) && (autoRadius1 == false)) + return; + + this.autoRadius1 = false; + this.radius1 = Math.max(radius,0); + + if (this.thickness > this.radius1 && this.thickness > this.radius2) + this.thickness = Math.max(this.radius1, this.radius2); + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + } + + @Override + public boolean isForeRadiusAutomatic() { + return autoRadius1; + } + + public void setForeRadiusAutomatic(boolean auto) { + if (autoRadius1 == auto) + return; + + autoRadius1 = auto; + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + } + + + //////// Aft radius ///////// + + @Override + public double getAftRadius() { + if (isAftRadiusAutomatic()) { + // Return the auto radius from the rear + double r = -1; + SymmetricComponent c = this.getNextSymmetricComponent(); + if (c != null) { + r = c.getRearAutoRadius(); + } + if (r < 0) + r = DEFAULT_RADIUS; + return r; + } + return radius2; + } + + + + public void setAftRadius(double radius) { + if ((this.radius2 == radius) && (autoRadius2 == false)) + return; + + this.autoRadius2 = false; + this.radius2 = Math.max(radius,0); + + if (this.thickness > this.radius1 && this.thickness > this.radius2) + this.thickness = Math.max(this.radius1, this.radius2); + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + } + + @Override + public boolean isAftRadiusAutomatic() { + return autoRadius2; + } + + public void setAftRadiusAutomatic(boolean auto) { + if (autoRadius2 == auto) + return; + + autoRadius2 = auto; + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + } + + + + //// Radius automatics + + @Override + protected double getFrontAutoRadius() { + if (isAftRadiusAutomatic()) + return -1; + return getAftRadius(); + } + + + @Override + protected double getRearAutoRadius() { + if (isForeRadiusAutomatic()) + return -1; + return getForeRadius(); + } + + + + + //////// Type & shape ///////// + + public Shape getType() { + return type; + } + + public void setType(Shape type) { + if (this.type == type) + return; + this.type = type; + this.clipped = type.isClippable(); + this.shapeParameter = type.defaultParameter(); + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + } + + public double getShapeParameter() { + return shapeParameter; + } + + public void setShapeParameter(double n) { + if (shapeParameter == n) + return; + this.shapeParameter = MathUtil.clamp(n, type.minParameter(), type.maxParameter()); + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + } + + public boolean isClipped() { + if (!type.isClippable()) + return false; + return clipped; + } + + public void setClipped(boolean c) { + if (clipped == c) + return; + clipped = c; + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + } + + public boolean isClippedEnabled() { + return type.isClippable(); + } + + public double getShapeParameterMin() { + return type.minParameter(); + } + + public double getShapeParameterMax() { + return type.maxParameter(); + } + + + //////// Shoulders //////// + + public double getForeShoulderRadius() { + return foreShoulderRadius; + } + + public void setForeShoulderRadius(double foreShoulderRadius) { + if (MathUtil.equals(this.foreShoulderRadius, foreShoulderRadius)) + return; + this.foreShoulderRadius = foreShoulderRadius; + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + } + + public double getForeShoulderThickness() { + return foreShoulderThickness; + } + + public void setForeShoulderThickness(double foreShoulderThickness) { + if (MathUtil.equals(this.foreShoulderThickness, foreShoulderThickness)) + return; + this.foreShoulderThickness = foreShoulderThickness; + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + } + + public double getForeShoulderLength() { + return foreShoulderLength; + } + + public void setForeShoulderLength(double foreShoulderLength) { + if (MathUtil.equals(this.foreShoulderLength, foreShoulderLength)) + return; + this.foreShoulderLength = foreShoulderLength; + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + } + + public boolean isForeShoulderCapped() { + return foreShoulderCapped; + } + + public void setForeShoulderCapped(boolean capped) { + if (this.foreShoulderCapped == capped) + return; + this.foreShoulderCapped = capped; + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + } + + + + + public double getAftShoulderRadius() { + return aftShoulderRadius; + } + + public void setAftShoulderRadius(double aftShoulderRadius) { + if (MathUtil.equals(this.aftShoulderRadius, aftShoulderRadius)) + return; + this.aftShoulderRadius = aftShoulderRadius; + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + } + + public double getAftShoulderThickness() { + return aftShoulderThickness; + } + + public void setAftShoulderThickness(double aftShoulderThickness) { + if (MathUtil.equals(this.aftShoulderThickness, aftShoulderThickness)) + return; + this.aftShoulderThickness = aftShoulderThickness; + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + } + + public double getAftShoulderLength() { + return aftShoulderLength; + } + + public void setAftShoulderLength(double aftShoulderLength) { + if (MathUtil.equals(this.aftShoulderLength, aftShoulderLength)) + return; + this.aftShoulderLength = aftShoulderLength; + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + } + + public boolean isAftShoulderCapped() { + return aftShoulderCapped; + } + + public void setAftShoulderCapped(boolean capped) { + if (this.aftShoulderCapped == capped) + return; + this.aftShoulderCapped = capped; + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + } + + + + + /////////// Shape implementations //////////// + + + + /** + * Return the radius at point x of the transition. + */ + @Override + public double getRadius(double x) { + if (x<0 || x>length) + return 0; + + double r1=getForeRadius(); + double r2=getAftRadius(); + + if (r1 == r2) + return r1; + + if (r1 > r2) { + x = length-x; + double tmp = r1; + r1 = r2; + r2 = tmp; + } + + if (isClipped()) { + // Check clip calculation + if (clipLength < 0) + calculateClip(r1,r2); + return type.getRadius(clipLength+x, r2, clipLength+length, shapeParameter); + } else { + // Not clipped + return r1 + type.getRadius(x, r2-r1, length, shapeParameter); + } + } + + /** + * Numerically solve clipLength from the equation + * r1 == type.getRadius(clipLength,r2,clipLength+length) + * using a binary search. It assumes getRadius() to be monotonically increasing. + */ + private void calculateClip(double r1, double r2) { + double min=0, max=length; + + if (r1 >= r2) { + double tmp=r1; + r1 = r2; + r2 = tmp; + } + + if (r1==0) { + clipLength = 0; + return; + } + + if (length <= 0) { + clipLength = 0; + return; + } + + // Required: + // getR(min,min+length,r2) - r1 < 0 + // getR(max,max+length,r2) - r1 > 0 + + int n=0; + while (type.getRadius(max, r2, max+length, shapeParameter) - r1 < 0) { + min = max; + max *= 2; + n++; + if (n>10) + break; + } + + while (true) { + clipLength = (min+max)/2; + if ((max-min)<CLIP_PRECISION) + return; + double val = type.getRadius(clipLength, r2, clipLength+length, shapeParameter); + if (val-r1 > 0) { + max = clipLength; + } else { + min = clipLength; + } + } + } + + + @Override + public double getInnerRadius(double x) { + return Math.max(getRadius(x)-thickness,0); + } + + + + @Override + public Collection<Coordinate> getComponentBounds() { + Collection<Coordinate> bounds = super.getComponentBounds(); + if (foreShoulderLength > 0.001) + addBound(bounds, -foreShoulderLength, foreShoulderRadius); + if (aftShoulderLength > 0.001) + addBound(bounds, getLength() + aftShoulderLength, aftShoulderRadius); + return bounds; + } + + @Override + public double getComponentMass() { + double mass = super.getComponentMass(); + if (getForeShoulderLength() > 0.001) { + final double or = getForeShoulderRadius(); + final double ir = Math.max(getForeShoulderRadius() - getForeShoulderThickness(), 0); + mass += ringMass(or, ir, getForeShoulderLength(), getMaterial().getDensity()); + } + if (isForeShoulderCapped()) { + final double ir = Math.max(getForeShoulderRadius() - getForeShoulderThickness(), 0); + mass += ringMass(ir, 0, getForeShoulderThickness(), getMaterial().getDensity()); + } + + if (getAftShoulderLength() > 0.001) { + final double or = getAftShoulderRadius(); + final double ir = Math.max(getAftShoulderRadius() - getAftShoulderThickness(), 0); + mass += ringMass(or, ir, getAftShoulderLength(), getMaterial().getDensity()); + } + if (isAftShoulderCapped()) { + final double ir = Math.max(getAftShoulderRadius() - getAftShoulderThickness(), 0); + mass += ringMass(ir, 0, getAftShoulderThickness(), getMaterial().getDensity()); + } + + return mass; + } + + @Override + public Coordinate getComponentCG() { + Coordinate cg = super.getComponentCG(); + if (getForeShoulderLength() > 0.001) { + final double ir = Math.max(getForeShoulderRadius() - getForeShoulderThickness(), 0); + cg = cg.average(ringCG(getForeShoulderRadius(), ir, -getForeShoulderLength(), 0, + getMaterial().getDensity())); + } + if (isForeShoulderCapped()) { + final double ir = Math.max(getForeShoulderRadius() - getForeShoulderThickness(), 0); + cg = cg.average(ringCG(ir, 0, -getForeShoulderLength(), + getForeShoulderThickness()-getForeShoulderLength(), + getMaterial().getDensity())); + } + + if (getAftShoulderLength() > 0.001) { + final double ir = Math.max(getAftShoulderRadius() - getAftShoulderThickness(), 0); + cg = cg.average(ringCG(getAftShoulderRadius(), ir, getLength(), + getLength()+getAftShoulderLength(), getMaterial().getDensity())); + } + if (isAftShoulderCapped()) { + final double ir = Math.max(getAftShoulderRadius() - getAftShoulderThickness(), 0); + cg = cg.average(ringCG(ir, 0, + getLength()+getAftShoulderLength()-getAftShoulderThickness(), + getLength()+getAftShoulderLength(), getMaterial().getDensity())); + } + return cg; + } + + + /* + * The moments of inertia are not explicitly corrected for the shoulders. + * However, since the mass is corrected, the inertia is automatically corrected + * to very nearly the correct value. + */ + + + + /** + * Returns the name of the component ("Transition"). + */ + @Override + public String getComponentName() { + return "Transition"; + } + + @Override + protected void componentChanged(ComponentChangeEvent e) { + super.componentChanged(e); + clipLength = -1; + } + + + + /** + * An enumeration listing the possible shapes of transitions. + * + * @author Sampo Niskanen <sampo.niskanen@iki.fi> + */ + public static enum Shape { + + /** + * Conical shape. + */ + CONICAL("Conical", + "A conical nose cone has a profile of a triangle.", + "A conical transition has straight sides.") { + @Override + public double getRadius(double x, double radius, double length, double param) { + assert x >= 0; + assert x <= length; + assert radius >= 0; + return radius*x/length; + } + }, + + /** + * Ogive shape. The shape parameter is the portion of an extended tangent ogive + * that will be used. That is, for param==1 a tangent ogive will be produced, and + * for smaller values the shape straightens out into a cone at param==0. + */ + OGIVE("Ogive", + "An ogive nose cone has a profile that is a segment of a circle. " + + "The shape parameter value 1 produces a <b>tangent ogive</b>, which has " + + "a smooth transition to the body tube, values less than 1 produce "+ + "<b>secant ogives</b>.", + "An ogive transition has a profile that is a segment of a circle. " + + "The shape parameter value 1 produces a <b>tangent ogive</b>, which has " + + "a smooth transition to the body tube at the aft end, values less than 1 " + + "produce <b>secant ogives</b>.") { + @Override + public boolean usesParameter() { + return true; // Range 0...1 is default + } + @Override + public double defaultParameter() { + return 1.0; // Tangent ogive by default + } + @Override + public double getRadius(double x, double radius, double length, double param) { + assert x >= 0; + assert x <= length; + assert radius >= 0; + assert param >= 0; + assert param <= 1; + + // Impossible to calculate ogive for length < radius, scale instead + // TODO: LOW: secant ogive could be calculated lower + if (length < radius) { + x = x * radius / length; + length = radius; + } + + if (param < 0.001) + return CONICAL.getRadius(x, radius, length, param); + + // Radius of circle is: + double R = sqrt((pow2(length)+pow2(radius)) * + (pow2((2-param)*length) + pow2(param*radius))/(4*pow2(param*radius))); + double L = length/param; +// double R = (radius + length*length/(radius*param*param))/2; + double y0 = sqrt(R*R - L*L); + return sqrt(R*R - (L-x)*(L-x)) - y0; + } + }, + + /** + * Ellipsoidal shape. + */ + ELLIPSOID("Ellipsoid", + "An ellipsoidal nose cone has a profile of a half-ellipse "+ + "with major axes of lengths 2×<i>Length</i> and <i>Diameter</i>.", + "An ellipsoidal transition has a profile of a half-ellipse "+ + "with major axes of lengths 2×<i>Length</i> and <i>Diameter</i>. If the "+ + "transition is not clipped, then the profile is extended at the center by the "+ + "corresponding radius.",true) { + @Override + public double getRadius(double x, double radius, double length, double param) { + assert x >= 0; + assert x <= length; + assert radius >= 0; + x = x*radius/length; + return sqrt(2*radius*x-x*x); // radius/length * sphere + } + }, + + POWER("Power series", + "A power series nose cone has a profile of "+ + "<i>Radius</i> × (<i>x</i> / <i>Length</i>)" + + "<sup><i>k</i></sup> "+ + "where <i>k</i> is the shape parameter. For <i>k</i>=0.5 this is a "+ + "<b>½-power</b> or <b>parabolic</b> nose cone, for <i>k</i>=0.75 a "+ + "<b>¾-power</b>, and for <i>k</i>=1 a <b>conical</b> nose cone.", + "A power series transition has a profile of "+ + "<i>Radius</i> × (<i>x</i> / <i>Length</i>)" + + "<sup><i>k</i></sup> "+ + "where <i>k</i> is the shape parameter. For <i>k</i>=0.5 the transition is "+ + "<b>½-power</b> or <b>parabolic</b>, for <i>k</i>=0.75 a <b>¾-power</b>, and for " + + "<i>k</i>=1 <b>conical</b>.",true) { + @Override + public boolean usesParameter() { // Range 0...1 + return true; + } + @Override + public double defaultParameter() { + return 0.5; + } + @Override + public double getRadius(double x, double radius, double length, double param) { + assert x >= 0; + assert x <= length; + assert radius >= 0; + assert param >= 0; + assert param <= 1; + if (param<=0.00001) { + if (x<=0.00001) + return 0; + else + return radius; + } + return radius*Math.pow(x/length, param); + } + + }, + + PARABOLIC("Parabolic series", + "A parabolic series nose cone has a profile of a parabola. The shape "+ + "parameter defines the segment of the parabola to utilize. The shape " + + "parameter 1.0 produces a <b>full parabola</b> which is tangent to the body " + + "tube, 0.75 produces a <b>3/4 parabola</b>, 0.5 procudes a " + + "<b>1/2 parabola</b> and 0 produces a <b>conical</b> nose cone.", + "A parabolic series transition has a profile of a parabola. The shape "+ + "parameter defines the segment of the parabola to utilize. The shape " + + "parameter 1.0 produces a <b>full parabola</b> which is tangent to the body " + + "tube at the aft end, 0.75 produces a <b>3/4 parabola</b>, 0.5 procudes a " + + "<b>1/2 parabola</b> and 0 produces a <b>conical</b> transition.") { + + // In principle a parabolic transition is clippable, but the difference is + // negligible. + + @Override + public boolean usesParameter() { // Range 0...1 + return true; + } + @Override + public double defaultParameter() { + return 1.0; + } + @Override + public double getRadius(double x, double radius, double length, double param) { + assert x >= 0; + assert x <= length; + assert radius >= 0; + assert param >= 0; + assert param <= 1; + + return radius * ((2*x/length - param*pow2(x/length))/(2-param)); + } + }, + + + + HAACK("Haack series", + "The Haack series nose cones are designed to minimize drag. The shape parameter " + + "0 produces an <b>LD-Haack</b> or <b>Von Karman</b> nose cone, which minimizes " + + "drag for fixed length and diameter, while a value of 0.333 produces an " + + "<b>LV-Haack</b> nose cone, which minimizes drag for fixed length and volume.", + "The Haack series <i>nose cones</i> are designed to minimize drag. " + + "These transition shapes are their equivalents, but do not necessarily produce " + + "optimal drag for transitions. " + + "The shape parameter 0 produces an <b>LD-Haack</b> or <b>Von Karman</b> shape, " + + "while a value of 0.333 produces an <b>LV-Haack</b> shape.",true) { + @Override + public boolean usesParameter() { + return true; + } + @Override + public double maxParameter() { + return 1.0/3.0; // Range 0...1/3 + } + @Override + public double getRadius(double x, double radius, double length, double param) { + assert x >= 0; + assert x <= length; + assert radius >= 0; + assert param >= 0; + assert param <= 2; + + double theta = Math.acos(1-2*x/length); + if (param==0) { + return radius*sqrt((theta-sin(2*theta)/2)/Math.PI); + } + return radius*sqrt((theta-sin(2*theta)/2+param*pow3(sin(theta)))/Math.PI); + } + }, + +// POLYNOMIAL("Smooth polynomial", +// "A polynomial is fitted such that the nose cone profile is horizontal "+ +// "at the aft end of the transition. The angle at the tip is defined by "+ +// "the shape parameter.", +// "A polynomial is fitted such that the transition profile is horizontal "+ +// "at the aft end of the transition. The angle at the fore end is defined "+ +// "by the shape parameter.") { +// @Override +// public boolean usesParameter() { +// return true; +// } +// @Override +// public double maxParameter() { +// return 3.0; // Range 0...3 +// } +// @Override +// public double defaultParameter() { +// return 0.0; +// } +// public double getRadius(double x, double radius, double length, double param) { +// assert x >= 0; +// assert x <= length; +// assert radius >= 0; +// assert param >= 0; +// assert param <= 3; +// // p(x) = (k-2)x^3 + (3-2k)x^2 + k*x +// x = x/length; +// return radius*((((param-2)*x + (3-2*param))*x + param)*x); +// } +// } + ; + + // Privete fields of the shapes + private final String name; + private final String transitionDesc; + private final String noseconeDesc; + private final boolean canClip; + + // Non-clippable constructor + Shape(String name, String noseconeDesc, String transitionDesc) { + this(name,noseconeDesc,transitionDesc,false); + } + + // Clippable constructor + Shape(String name, String noseconeDesc, String transitionDesc, boolean canClip) { + this.name = name; + this.canClip = canClip; + this.noseconeDesc = noseconeDesc; + this.transitionDesc = transitionDesc; + } + + + /** + * Return the name of the transition shape name. + */ + public String getName() { + return name; + } + + /** + * Get a description of the Transition shape. + */ + public String getTransitionDescription() { + return transitionDesc; + } + + /** + * Get a description of the NoseCone shape. + */ + public String getNoseConeDescription() { + return noseconeDesc; + } + + /** + * Check whether the shape differs in clipped mode. The clipping should be + * enabled by default if possible. + */ + public boolean isClippable() { + return canClip; + } + + /** + * Return whether the shape uses the shape parameter. (Default false.) + */ + public boolean usesParameter() { + return false; + } + + /** + * Return the minimum value of the shape parameter. (Default 0.) + */ + public double minParameter() { + return 0.0; + } + + /** + * Return the maximum value of the shape parameter. (Default 1.) + */ + public double maxParameter() { + return 1.0; + } + + /** + * Return the default value of the shape parameter. (Default 0.) + */ + public double defaultParameter() { + return 0.0; + } + + /** + * Calculate the basic radius of a transition with the given radius, length and + * shape parameter at the point x from the tip of the component. It is assumed + * that the fore radius if zero and the aft radius is <code>radius >= 0</code>. + * Boattails are achieved by reversing the component. + * + * @param x Position from the tip of the component. + * @param radius Aft end radius >= 0. + * @param length Length of the transition >= 0. + * @param param Valid shape parameter. + * @return The basic radius at the given position. + */ + public abstract double getRadius(double x, double radius, double length, double param); + + + /** + * Returns the name of the shape (same as getName()). + */ + @Override + public String toString() { + return name; + } + } +} diff --git a/src/net/sf/openrocket/rocketcomponent/TrapezoidFinSet.java b/src/net/sf/openrocket/rocketcomponent/TrapezoidFinSet.java new file mode 100644 index 000000000..c92c65ea9 --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/TrapezoidFinSet.java @@ -0,0 +1,169 @@ +package net.sf.openrocket.rocketcomponent; + +import net.sf.openrocket.util.Coordinate; + +/** + * A set of trapezoidal fins. The root and tip chords are perpendicular to the rocket + * base line, while the leading and aft edges may be slanted. + * + * @author Sampo Niskanen <sampo.niskanen@iki.fi> + */ + +public class TrapezoidFinSet extends FinSet { + public static final double MAX_SWEEP_ANGLE=(89*Math.PI/180.0); + + /* + * sweep tipChord + * | |___________ + * | / | + * | / | + * | / | height + * / | + * __________/________________|_____________ + * length + * == rootChord + */ + + // rootChord == length + private double tipChord = 0; + private double height = 0; + private double sweep = 0; + + + public TrapezoidFinSet() { + this (3, 0.05, 0.05, 0.025, 0.05); + } + + // TODO: HIGH: height=0 -> CP = NaN + public TrapezoidFinSet(int fins, double rootChord, double tipChord, double sweep, + double height) { + super(); + + this.setFinCount(fins); + this.length = rootChord; + this.tipChord = tipChord; + this.sweep = sweep; + this.height = height; + } + + + public void setFinShape(double rootChord, double tipChord, double sweep, double height, + double thickness) { + if (this.length==rootChord && this.tipChord==tipChord && this.sweep==sweep && + this.height==height && this.thickness==thickness) + return; + this.length=rootChord; + this.tipChord=tipChord; + this.sweep=sweep; + this.height=height; + this.thickness=thickness; + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + } + + public double getRootChord() { + return length; + } + public void setRootChord(double r) { + if (length == r) + return; + length = Math.max(r,0); + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + } + + public double getTipChord() { + return tipChord; + } + public void setTipChord(double r) { + if (tipChord == r) + return; + tipChord = Math.max(r,0); + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + } + + /** + * Get the sweep length. + */ + public double getSweep() { + return sweep; + } + /** + * Set the sweep length. + */ + public void setSweep(double r) { + if (sweep == r) + return; + sweep = r; + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + } + + /** + * Get the sweep angle. This is calculated from the true sweep and height, and is not + * stored separetely. + */ + public double getSweepAngle() { + if (height == 0) { + if (sweep > 0) + return Math.PI/2; + if (sweep < 0) + return -Math.PI/2; + return 0; + } + return Math.atan(sweep/height); + } + /** + * Sets the sweep by the sweep angle. The sweep is calculated and set by this method, + * and the angle itself is not stored. + */ + public void setSweepAngle(double r) { + if (r > MAX_SWEEP_ANGLE) + r = MAX_SWEEP_ANGLE; + if (r < -MAX_SWEEP_ANGLE) + r = -MAX_SWEEP_ANGLE; + double sweep = Math.tan(r) * height; + if (Double.isNaN(sweep) || Double.isInfinite(sweep)) + return; + setSweep(sweep); + } + + public double getHeight() { + return height; + } + public void setHeight(double r) { + if (height == r) + return; + height = Math.max(r,0); + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + } + + + + /** + * Returns the geometry of a trapezoidal fin. + */ + @Override + public Coordinate[] getFinPoints() { + Coordinate[] c = new Coordinate[4]; + + c[0] = Coordinate.NUL; + c[1] = new Coordinate(sweep,height); + c[2] = new Coordinate(sweep+tipChord,height); + c[3] = new Coordinate(length,0); + + return c; + } + + /** + * Returns the span of a trapezoidal fin. + */ + @Override + public double getSpan() { + return height; + } + + + @Override + public String getComponentName() { + return "Trapezoidal fin set"; + } + +} diff --git a/src/net/sf/openrocket/rocketcomponent/TubeCoupler.java b/src/net/sf/openrocket/rocketcomponent/TubeCoupler.java new file mode 100644 index 000000000..bb199219e --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/TubeCoupler.java @@ -0,0 +1,30 @@ +package net.sf.openrocket.rocketcomponent; + + +public class TubeCoupler extends ThicknessRingComponent { + + public TubeCoupler() { + setOuterRadiusAutomatic(true); + setThickness(0.002); + setLength(0.06); + } + + + // Make setter visible + @Override + public void setOuterRadiusAutomatic(boolean auto) { + super.setOuterRadiusAutomatic(auto); + } + + + @Override + public String getComponentName() { + return "Tube coupler"; + } + + @Override + public boolean isCompatible(Class<? extends RocketComponent> type) { + return false; + } +} + diff --git a/src/net/sf/openrocket/simulation/EulerSimulator.java b/src/net/sf/openrocket/simulation/EulerSimulator.java new file mode 100644 index 000000000..3f6213708 --- /dev/null +++ b/src/net/sf/openrocket/simulation/EulerSimulator.java @@ -0,0 +1,373 @@ +package net.sf.openrocket.simulation; + +import java.util.Collection; + +import net.sf.openrocket.aerodynamics.AerodynamicCalculator; +import net.sf.openrocket.aerodynamics.AerodynamicForces; +import net.sf.openrocket.aerodynamics.AtmosphericConditions; +import net.sf.openrocket.aerodynamics.FlightConditions; +import net.sf.openrocket.aerodynamics.GravityModel; +import net.sf.openrocket.aerodynamics.WindSimulator; +import net.sf.openrocket.rocketcomponent.Configuration; +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.MathUtil; +import net.sf.openrocket.util.Quaternion; + + + + +/** + * A flight simulator based on Euler integration. This class is out of date and + * deprecated in favor of the Runge-Kutta simulator RK4Simulator. + * + * @author Sampo Niskanen <sampo.niskanen@iki.fi> + */ +@Deprecated +public class EulerSimulator extends FlightSimulator { + /** + * Maximum roll step allowed. This is selected as an uneven division of the full + * circle so that the simulation will sample the most wind directions + */ + private static final double MAX_ROLL_STEP_ANGLE = 28.32 * Math.PI/180; + + private static final boolean DEBUG = false; + + + private FlightConditions flightConditions = null; + private double currentStep; + private double lateralPitchRate = 0; // set by calculateFlightConditions + + public EulerSimulator(AerodynamicCalculator calculator) { + super(calculator); + } + + + + protected FlightDataBranch newFlightData(String name) { + return new FlightDataBranch(name, FlightDataBranch.TYPE_TIME); // TODO: ??? + } + + + + @Override + protected RK4SimulationStatus initializeSimulation(Configuration configuration, + SimulationConditions simulation) { + + RK4SimulationStatus status = new RK4SimulationStatus(); + + status.startConditions = simulation; + + status.configuration = configuration; + status.flightData = newFlightData("Main"); + status.launchRod = true; + status.time = 0.0; + + + status.launchRodDirection = new Coordinate( + Math.sin(simulation.getLaunchRodAngle()) * + Math.cos(simulation.getLaunchRodDirection()), + Math.sin(simulation.getLaunchRodAngle()) * + Math.sin(simulation.getLaunchRodDirection()), + Math.cos(simulation.getLaunchRodAngle()) + ); + status.launchRodLength = simulation.getLaunchRodLength(); + // TODO: take into account launch lug positions + + + Quaternion o = new Quaternion(); + o.multiplyLeft(Quaternion.rotation( + new Coordinate(0, simulation.getLaunchRodAngle(), 0))); + o.multiplyLeft(Quaternion.rotation( + new Coordinate(0, 0, simulation.getLaunchRodDirection()))); + status.orientation = o; + status.position = Coordinate.NUL; + status.velocity = Coordinate.NUL; + status.rotation = Coordinate.NUL; + + status.windSimulator = new WindSimulator(); + status.windSimulator.setAverage(simulation.getWindSpeedAverage()); + status.windSimulator.setStandardDeviation(simulation.getWindSpeedDeviation()); +// status.windSimulator.reset(); + + currentStep = simulation.getTimeStep(); + + status.gravityModel = new GravityModel(simulation.getLaunchLatitude()); + + flightConditions = null; + + return status; + } + + + + @Override + protected Collection<FlightEvent> step(SimulationConditions simulation, + SimulationStatus simulationStatus) { + + RK4SimulationStatus status = (RK4SimulationStatus)simulationStatus; + FlightDataBranch data = status.flightData; + + + // Add data point and values + data.addPoint(); + data.setValue(FlightDataBranch.TYPE_TIME, status.time); + + if (DEBUG) + System.out.println("original direction: "+status.orientation.rotateZ()); + + // Calculate current flight conditions + if (flightConditions == null) { + flightConditions = new FlightConditions(status.configuration); + } + calculateFlightConditions(flightConditions, status); + + if (DEBUG) + System.out.println("flightConditions="+flightConditions); + + + // Calculate time step + double timestep; + +// double normalStep = simulation.getTimeStep(); +// double lateralStep = simulation.getMaximumStepAngle() / lateralPitchRate; +// double rotationStep = Math.abs(MAX_ROLL_STEP_ANGLE / flightConditions.getRollRate()); +// if (lateralStep < normalStep || rotationStep < normalStep) { +// System.out.printf(" t=%.3f STEP normal %.3f lateral %.3f rotation %.3f\n", +// status.time, normalStep, lateralStep, rotationStep); +// } + + + timestep = MathUtil.min(simulation.getTimeStep(), + simulation.getMaximumStepAngle() / lateralPitchRate, + Math.abs(MAX_ROLL_STEP_ANGLE / flightConditions.getRollRate())); + if (timestep < 0.0001) + timestep = 0.0001; + + + + // Calculate aerodynamic forces (only axial if still on launch rod) + AerodynamicForces forces; + calculator.setConfiguration(status.configuration); + + if (status.launchRod) { + forces = calculator.getAxialForces(status.time, flightConditions, status.warnings); + } else { + forces = calculator.getAerodynamicForces(status.time, + flightConditions, status.warnings); + } + + + // Check in case of NaN + assert(!Double.isNaN(forces.CD)); + assert(!Double.isNaN(forces.CN)); + assert(!Double.isNaN(forces.Caxial)); + assert(!Double.isNaN(forces.Cm)); + assert(!Double.isNaN(forces.Cyaw)); + assert(!Double.isNaN(forces.Cside)); + assert(!Double.isNaN(forces.Croll)); + + + //// Calculate forces and accelerations + + double dynP = (0.5 * flightConditions.getAtmosphericConditions().getDensity() * + MathUtil.pow2(flightConditions.getVelocity())); + double refArea = flightConditions.getRefArea(); + double refLength = flightConditions.getRefLength(); + + + // Linear forces + double thrust = calculateThrust(status, currentStep); + double dragForce = forces.Caxial * dynP * refArea; + double fN = forces.CN * dynP * refArea; + double fSide = forces.Cside * dynP * refArea; + + double sin = Math.sin(flightConditions.getTheta()); + double cos = Math.cos(flightConditions.getTheta()); + + double forceX = - fN * cos - fSide * sin; + double forceY = - fN * sin - fSide * cos; + double forceZ = thrust - dragForce; + + + Coordinate acceleration = new Coordinate(forceX / forces.cg.weight, + forceY / forces.cg.weight, forceZ / forces.cg.weight); + + if (DEBUG) + System.out.println(" acceleration before rotation: "+acceleration); + acceleration = status.orientation.rotate(acceleration); + if (DEBUG) + System.out.println(" acceleration after rotation: "+acceleration); + + acceleration = acceleration.sub(0, 0, status.gravityModel.getGravity()); + + + // Convert momenta + double Cm = forces.Cm - forces.CN * forces.cg.x / refLength; + double momX = (-Cm * sin - forces.Cyaw * cos) * dynP * refArea * refLength; + double momY = ( Cm * cos - forces.Cyaw * sin) * dynP * refArea * refLength; + double momZ = forces.Croll * dynP * refArea * refLength; + + assert(!Double.isNaN(momX)); + assert(!Double.isNaN(momY)); + assert(!Double.isNaN(momZ)); + assert(!Double.isNaN(forces.longitudalInertia)); + assert(!Double.isNaN(forces.rotationalInertia)); + + Coordinate rotAcc = new Coordinate(momX / forces.longitudalInertia, + momY / forces.longitudalInertia, momZ / forces.rotationalInertia); + rotAcc = status.orientation.rotate(rotAcc); + +// System.out.println(" rotAcc="+rotAcc); +// System.out.println(" momY="+momY+" lI="+forces.longitudalInertia +// +" Cm="+Cm+" forces.Cm="+forces.Cm+" cg="+forces.cg); +// System.out.println(" orient before update:"+status.orientation); + + + + // Perform position integration + Coordinate avgVel = status.velocity.add(acceleration.multiply(currentStep/2)); + status.velocity = status.velocity.add(acceleration.multiply(currentStep)); + + if (status.launchRod) { + // Project velocity onto launch rod direction + status.velocity = status.launchRodDirection.multiply( + status.velocity.dot(status.launchRodDirection)); + avgVel = status.launchRodDirection.multiply(avgVel.dot(status.launchRodDirection)); + } + + status.position = status.position.add(avgVel.multiply(currentStep)); + + if (status.launchRod) { + // Avoid sinking into ground when on the launch rod + if (status.position.z < 0) { +// System.out.println("Corrected sinking from pos:"+status.position+ +// " vel:"+status.velocity); + status.position = Coordinate.NUL; + status.velocity = Coordinate.NUL; + } + } + + + if (!status.launchRod) { + // Integrate rotation when off launch rod + Coordinate avgRot = status.rotation.add(rotAcc.multiply(currentStep/2)); + status.rotation = status.rotation.add(rotAcc.multiply(currentStep)); + Quaternion stepRotation = Quaternion.rotation(avgRot.multiply(currentStep)); + status.orientation.multiplyLeft(stepRotation); + + status.orientation.normalize(); + + if (DEBUG) + System.out.println(" step rotation "+ + (avgRot.length()*currentStep*180/Math.PI) +"\u00b0 " + + "step="+currentStep+" after: "+status.orientation.rotateZ()); + } + + status.time += currentStep; + + + + // Check rotation angle step length and correct time step if necessary + double rot = status.rotation.length() * currentStep; + if (rot > simulation.getMaximumStepAngle()) { + currentStep /= 2; + if (DEBUG) + System.out.println(" *** Step division to: "+currentStep); + } + if ((rot < simulation.getMaximumStepAngle()/3) && + (currentStep < simulation.getTimeStep() - 0.000001)) { + currentStep *= 2; + if (DEBUG) + System.out.println(" *** Step multiplication to: "+currentStep); + } + + + + // Store values + data.setValue(FlightDataBranch.TYPE_ACCELERATION_Z, acceleration.z); + data.setValue(FlightDataBranch.TYPE_ACCELERATION_TOTAL, acceleration.length()); + data.setValue(FlightDataBranch.TYPE_VELOCITY_TOTAL, status.velocity.length()); + + data.setValue(FlightDataBranch.TYPE_AXIAL_DRAG_COEFF, forces.CD); + data.setValue(FlightDataBranch.TYPE_FRICTION_DRAG_COEFF, forces.frictionCD); + data.setValue(FlightDataBranch.TYPE_PRESSURE_DRAG_COEFF, forces.pressureCD); + data.setValue(FlightDataBranch.TYPE_BASE_DRAG_COEFF, forces.baseCD); + + data.setValue(FlightDataBranch.TYPE_CP_LOCATION, forces.cp.x); + data.setValue(FlightDataBranch.TYPE_CG_LOCATION, forces.cg.x); + + data.setValue(FlightDataBranch.TYPE_MASS, forces.cg.weight); + + data.setValue(FlightDataBranch.TYPE_THRUST_FORCE, thrust); + data.setValue(FlightDataBranch.TYPE_DRAG_FORCE, dragForce); + + + Coordinate c = status.orientation.rotateZ(); + double theta = Math.atan2(c.z, MathUtil.hypot(c.x, c.y)); + double phi = Math.atan2(c.y, c.x); + data.setValue(FlightDataBranch.TYPE_ORIENTATION_THETA, theta); + data.setValue(FlightDataBranch.TYPE_ORIENTATION_PHI, phi); + + return null; + } + + + + /* + * TODO: MEDIUM: Many parameters are stored one time step late, how to fix? + */ + private void calculateFlightConditions(FlightConditions flightConditions, + RK4SimulationStatus status) { + + // Atmospheric conditions + AtmosphericConditions cond = status.startConditions.getAtmosphericModel(). + getConditions(status.position.z + status.startConditions.getLaunchAltitude()); + flightConditions.setAtmosphericConditions(cond); + status.flightData.setValue(FlightDataBranch.TYPE_AIR_TEMPERATURE, cond.temperature); + status.flightData.setValue(FlightDataBranch.TYPE_AIR_PRESSURE, cond.pressure); + status.flightData.setValue(FlightDataBranch.TYPE_SPEED_OF_SOUND, cond.getMachSpeed()); + + + // Local wind speed and direction + double wind = status.windSimulator.getWindSpeed(status.time); + status.flightData.setValue(FlightDataBranch.TYPE_WIND_VELOCITY, wind); + + Coordinate windSpeed = status.velocity.sub(wind, 0, 0); + windSpeed = status.orientation.invRotate(windSpeed); + + double theta = Math.atan2(windSpeed.y, windSpeed.x); + double velocity = windSpeed.length(); + double aoa = Math.acos(windSpeed.z / velocity); + + if (Double.isNaN(theta) || Double.isInfinite(theta)) + theta = 0; + if (Double.isNaN(velocity) || Double.isInfinite(velocity)) + velocity = 0; + if (Double.isNaN(aoa) || Double.isInfinite(aoa)) + aoa = 0; + + flightConditions.setTheta(theta); + flightConditions.setAOA(aoa); + flightConditions.setVelocity(velocity); + + status.flightData.setValue(FlightDataBranch.TYPE_AOA, aoa); + + // Roll, pitch and yaw rate + Coordinate rot = status.orientation.invRotate(status.rotation); + flightConditions.setRollRate(rot.z); + status.flightData.setValue(FlightDataBranch.TYPE_ROLL_RATE, rot.z); + double len = MathUtil.hypot(windSpeed.x, windSpeed.y); + if (len < 0.001) { + flightConditions.setPitchRate(0); + flightConditions.setYawRate(0); + lateralPitchRate = 0; + } else { + double sinTheta = windSpeed.x / len; + double cosTheta = windSpeed.y / len; + flightConditions.setPitchRate(cosTheta*rot.x + sinTheta*rot.y); + flightConditions.setYawRate(sinTheta*rot.x + cosTheta*rot.y); + lateralPitchRate = MathUtil.hypot(rot.x, rot.y); + } + } + +} diff --git a/src/net/sf/openrocket/simulation/FlightData.java b/src/net/sf/openrocket/simulation/FlightData.java new file mode 100644 index 000000000..8d0307c6b --- /dev/null +++ b/src/net/sf/openrocket/simulation/FlightData.java @@ -0,0 +1,206 @@ +package net.sf.openrocket.simulation; + +import java.util.ArrayList; +import java.util.List; + +import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.util.MathUtil; + + +public class FlightData { + + /** + * An immutable FlightData object with NaN data. + */ + public static final FlightData NaN_DATA; + static { + FlightData data = new FlightData(); + data.immute(); + NaN_DATA = data; + } + + private boolean mutable = true; + private final ArrayList<FlightDataBranch> branches = new ArrayList<FlightDataBranch>(); + + private final WarningSet warnings = new WarningSet(); + + private double maxAltitude = Double.NaN; + private double maxVelocity = Double.NaN; + private double maxAcceleration = Double.NaN; + private double maxMachNumber = Double.NaN; + private double timeToApogee = Double.NaN; + private double flightTime = Double.NaN; + private double groundHitVelocity = Double.NaN; + + + /** + * Create a FlightData object with no content. The resulting object is mutable. + */ + public FlightData() { + + } + + + /** + * Construct an immutable FlightData object with no data branches but the specified + * summary information. + * + * @param maxAltitude maximum altitude. + * @param maxVelocity maximum velocity. + * @param maxAcceleration maximum acceleration. + * @param maxMachNumber maximum Mach number. + * @param timeToApogee time to apogee. + * @param flightTime total flight time. + * @param groundHitVelocity ground hit velocity. + */ + public FlightData(double maxAltitude, double maxVelocity, double maxAcceleration, + double maxMachNumber, double timeToApogee, double flightTime, + double groundHitVelocity) { + this.maxAltitude = maxAltitude; + this.maxVelocity = maxVelocity; + this.maxAcceleration = maxAcceleration; + this.maxMachNumber = maxMachNumber; + this.timeToApogee = timeToApogee; + this.flightTime = flightTime; + this.groundHitVelocity = groundHitVelocity; + + this.immute(); + } + + + /** + * Create an immutable FlightData object with the specified branches. + * + * @param branches the branches. + */ + public FlightData(FlightDataBranch ... branches) { + this(); + + for (FlightDataBranch b: branches) + this.addBranch(b); + + calculateIntrestingValues(); + this.immute(); + } + + + + + /** + * Returns the warning set associated with this object. This WarningSet cannot be + * set, so simulations must use this warning set to store their warnings. + * The returned WarningSet should not be modified otherwise. + * + * @return the warnings generated during this simulation. + */ + public WarningSet getWarningSet() { + return warnings; + } + + + public void addBranch(FlightDataBranch branch) { + if (!mutable) + throw new IllegalStateException("FlightData has been made immutable"); + + branch.immute(); + branches.add(branch); + + if (branches.size() == 1) { + calculateIntrestingValues(); + } + } + + public int getBranchCount() { + return branches.size(); + } + + public FlightDataBranch getBranch(int n) { + return branches.get(n); + } + + + + public double getMaxAltitude() { + return maxAltitude; + } + + public double getMaxVelocity() { + return maxVelocity; + } + + public double getMaxAcceleration() { + return maxAcceleration; + } + + public double getMaxMachNumber() { + return maxMachNumber; + } + + public double getTimeToApogee() { + return timeToApogee; + } + + public double getFlightTime() { + return flightTime; + } + + public double getGroundHitVelocity() { + return groundHitVelocity; + } + + + + /** + * Calculate the max. altitude/velocity/acceleration, time to apogee, flight time + * and ground hit velocity. + */ + private void calculateIntrestingValues() { + if (branches.isEmpty()) + return; + + FlightDataBranch branch = branches.get(0); + maxAltitude = branch.getMaximum(FlightDataBranch.TYPE_ALTITUDE); + maxVelocity = branch.getMaximum(FlightDataBranch.TYPE_VELOCITY_TOTAL); + maxAcceleration = branch.getMaximum(FlightDataBranch.TYPE_ACCELERATION_TOTAL); + maxMachNumber = branch.getMaximum(FlightDataBranch.TYPE_MACH_NUMBER); + + flightTime = branch.getLast(FlightDataBranch.TYPE_TIME); + if (branch.getLast(FlightDataBranch.TYPE_ALTITUDE) < 10) { + groundHitVelocity = branch.getLast(FlightDataBranch.TYPE_VELOCITY_TOTAL); + } else { + groundHitVelocity = Double.NaN; + } + + // Time to apogee + List<Double> time = branch.get(FlightDataBranch.TYPE_TIME); + List<Double> altitude = branch.get(FlightDataBranch.TYPE_ALTITUDE); + + if (time == null || altitude == null) { + timeToApogee = Double.NaN; + return; + } + int index = 0; + for (Double alt: altitude) { + if (alt != null) { + if (MathUtil.equals(alt, maxAltitude)) + break; + } + + index++; + } + if (index < time.size()) + timeToApogee = time.get(index); + else + timeToApogee = Double.NaN; + } + + + public void immute() { + mutable = false; + } + public boolean isMutable() { + return mutable; + } + + +} diff --git a/src/net/sf/openrocket/simulation/FlightDataBranch.java b/src/net/sf/openrocket/simulation/FlightDataBranch.java new file mode 100644 index 000000000..c80c268f1 --- /dev/null +++ b/src/net/sf/openrocket/simulation/FlightDataBranch.java @@ -0,0 +1,429 @@ +package net.sf.openrocket.simulation; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import net.sf.openrocket.unit.UnitGroup; +import net.sf.openrocket.util.Pair; + + +public class FlightDataBranch { + + //// Time + public static final Type TYPE_TIME = + new Type("Time", UnitGroup.UNITS_FLIGHT_TIME, 1); + + + //// Vertical position and motion + public static final Type TYPE_ALTITUDE = + new Type("Altitude", UnitGroup.UNITS_DISTANCE, 10); + public static final Type TYPE_VELOCITY_Z = + new Type("Vertical velocity", UnitGroup.UNITS_VELOCITY, 11); + public static final Type TYPE_ACCELERATION_Z = + new Type("Vertical acceleration", UnitGroup.UNITS_ACCELERATION, 12); + + + //// Total motion + public static final Type TYPE_VELOCITY_TOTAL = + new Type("Total velocity", UnitGroup.UNITS_VELOCITY, 20); + public static final Type TYPE_ACCELERATION_TOTAL = + new Type("Total acceleration", UnitGroup.UNITS_ACCELERATION, 21); + + + //// Lateral position and motion + + public static final Type TYPE_POSITION_X = + new Type("Position upwind", UnitGroup.UNITS_DISTANCE, 30); + public static final Type TYPE_POSITION_Y = + new Type("Position parallel to wind", UnitGroup.UNITS_DISTANCE, 31); + public static final Type TYPE_POSITION_XY = + new Type("Lateral distance", UnitGroup.UNITS_DISTANCE, 32); + public static final Type TYPE_POSITION_DIRECTION = + new Type("Lateral direction", UnitGroup.UNITS_ANGLE, 33); + + public static final Type TYPE_VELOCITY_XY = + new Type("Lateral velocity", UnitGroup.UNITS_VELOCITY, 34); + public static final Type TYPE_ACCELERATION_XY = + new Type("Lateral acceleration", UnitGroup.UNITS_ACCELERATION, 35); + + + //// Angular motion + public static final Type TYPE_AOA = new Type("Angle of attack", UnitGroup.UNITS_ANGLE, 40); + public static final Type TYPE_ROLL_RATE = new Type("Roll rate", UnitGroup.UNITS_ROLL, 41); + public static final Type TYPE_PITCH_RATE = new Type("Pitch rate", UnitGroup.UNITS_ROLL, 42); + public static final Type TYPE_YAW_RATE = new Type("Yaw rate", UnitGroup.UNITS_ROLL, 43); + + + //// Stability information + public static final Type TYPE_MASS = + new Type("Mass", UnitGroup.UNITS_MASS, 50); + public static final Type TYPE_CP_LOCATION = + new Type("CP location", UnitGroup.UNITS_LENGTH, 51); + public static final Type TYPE_CG_LOCATION = + new Type("CG location", UnitGroup.UNITS_LENGTH, 52); + public static final Type TYPE_STABILITY = + new Type("Stability margin calibers", UnitGroup.UNITS_COEFFICIENT, 53); + + + //// Characteristic numbers + public static final Type TYPE_MACH_NUMBER = + new Type("Mach number", UnitGroup.UNITS_COEFFICIENT, 60); + public static final Type TYPE_REYNOLDS_NUMBER = + new Type("Reynolds number", UnitGroup.UNITS_COEFFICIENT, 61); + + + //// Thrust and drag + public static final Type TYPE_THRUST_FORCE = + new Type("Thrust", UnitGroup.UNITS_FORCE, 70); + public static final Type TYPE_DRAG_FORCE = + new Type("Drag force", UnitGroup.UNITS_FORCE, 71); + + public static final Type TYPE_DRAG_COEFF = + new Type("Drag coefficient", UnitGroup.UNITS_COEFFICIENT, 72); + public static final Type TYPE_AXIAL_DRAG_COEFF = + new Type("Axial drag coefficient", UnitGroup.UNITS_COEFFICIENT, 73); + + + //// Component drag coefficients + public static final Type TYPE_FRICTION_DRAG_COEFF = + new Type("Friction drag coefficient", UnitGroup.UNITS_COEFFICIENT, 80); + public static final Type TYPE_PRESSURE_DRAG_COEFF = + new Type("Pressure drag coefficient", UnitGroup.UNITS_COEFFICIENT, 81); + public static final Type TYPE_BASE_DRAG_COEFF = + new Type("Base drag coefficient", UnitGroup.UNITS_COEFFICIENT, 82); + + + //// Other coefficients + public static final Type TYPE_NORMAL_FORCE_COEFF = + new Type("Normal force coefficient", UnitGroup.UNITS_COEFFICIENT, 90); + public static final Type TYPE_PITCH_MOMENT_COEFF = + new Type("Pitch moment coefficient", UnitGroup.UNITS_COEFFICIENT, 91); + public static final Type TYPE_YAW_MOMENT_COEFF = + new Type("Yaw moment coefficient", UnitGroup.UNITS_COEFFICIENT, 92); + public static final Type TYPE_SIDE_FORCE_COEFF = + new Type("Side force coefficient", UnitGroup.UNITS_COEFFICIENT, 93); + public static final Type TYPE_ROLL_MOMENT_COEFF = + new Type("Roll moment coefficient", UnitGroup.UNITS_COEFFICIENT, 94); + public static final Type TYPE_ROLL_FORCING_COEFF = + new Type("Roll forcing coefficient", UnitGroup.UNITS_COEFFICIENT, 95); + public static final Type TYPE_ROLL_DAMPING_COEFF = + new Type("Roll damping coefficient", UnitGroup.UNITS_COEFFICIENT, 96); + + public static final Type TYPE_PITCH_DAMPING_MOMENT_COEFF = + new Type("Pitch damping coefficient", UnitGroup.UNITS_COEFFICIENT, 97); + public static final Type TYPE_YAW_DAMPING_MOMENT_COEFF = + new Type("Yaw damping coefficient", UnitGroup.UNITS_COEFFICIENT, 98); + + + //// Reference length + area + public static final Type TYPE_REFERENCE_LENGTH = + new Type("Reference length", UnitGroup.UNITS_LENGTH, 100); + public static final Type TYPE_REFERENCE_AREA = + new Type("Reference area", UnitGroup.UNITS_AREA, 101); + + + //// Orientation + public static final Type TYPE_ORIENTATION_THETA = + new Type("Vertical orientation (zenith)", UnitGroup.UNITS_ANGLE, 106); + public static final Type TYPE_ORIENTATION_PHI = + new Type("Lateral orientation (azimuth)", UnitGroup.UNITS_ANGLE, 107); + + + //// Atmospheric conditions + public static final Type TYPE_WIND_VELOCITY = new Type("Wind velocity", + UnitGroup.UNITS_VELOCITY, 110); + public static final Type TYPE_AIR_TEMPERATURE = new Type("Air temperature", + UnitGroup.UNITS_TEMPERATURE, 111); + public static final Type TYPE_AIR_PRESSURE = new Type("Air pressure", + UnitGroup.UNITS_PRESSURE, 112); + public static final Type TYPE_SPEED_OF_SOUND = new Type("Speed of sound", + UnitGroup.UNITS_VELOCITY, 113); + + + //// Simulation information + public static final Type TYPE_TIME_STEP = new Type("Simulation time step", + UnitGroup.UNITS_TIME_STEP, 200); + public static final Type TYPE_COMPUTATION_TIME = new Type("Computation time", + UnitGroup.UNITS_SHORT_TIME, 201); + + + /** + * Array of known data types for String -> Type conversion. + */ + private static final Type[] TYPES = { + TYPE_TIME, + TYPE_ALTITUDE, TYPE_VELOCITY_Z, TYPE_ACCELERATION_Z, + TYPE_VELOCITY_TOTAL, TYPE_ACCELERATION_TOTAL, + TYPE_POSITION_X, TYPE_POSITION_Y, TYPE_POSITION_XY, TYPE_POSITION_DIRECTION, + TYPE_VELOCITY_XY, TYPE_ACCELERATION_XY, + TYPE_AOA, TYPE_ROLL_RATE, TYPE_PITCH_RATE, TYPE_YAW_RATE, + TYPE_MASS, TYPE_CP_LOCATION, TYPE_CG_LOCATION, TYPE_STABILITY, + TYPE_MACH_NUMBER, TYPE_REYNOLDS_NUMBER, + TYPE_THRUST_FORCE, TYPE_DRAG_FORCE, + TYPE_DRAG_COEFF, TYPE_AXIAL_DRAG_COEFF, + TYPE_FRICTION_DRAG_COEFF, TYPE_PRESSURE_DRAG_COEFF, TYPE_BASE_DRAG_COEFF, + TYPE_NORMAL_FORCE_COEFF, TYPE_PITCH_MOMENT_COEFF, TYPE_YAW_MOMENT_COEFF, TYPE_SIDE_FORCE_COEFF, + TYPE_ROLL_MOMENT_COEFF, TYPE_ROLL_FORCING_COEFF, TYPE_ROLL_DAMPING_COEFF, + TYPE_PITCH_DAMPING_MOMENT_COEFF, TYPE_YAW_DAMPING_MOMENT_COEFF, + TYPE_REFERENCE_LENGTH, TYPE_REFERENCE_AREA, + TYPE_ORIENTATION_THETA, TYPE_ORIENTATION_PHI, + TYPE_WIND_VELOCITY, TYPE_AIR_TEMPERATURE, TYPE_AIR_PRESSURE, TYPE_SPEED_OF_SOUND, + TYPE_TIME_STEP, TYPE_COMPUTATION_TIME + }; + + /** + * Return a {@link Type} based on a string description. This returns known data types + * if possible, or a new type otherwise. + * + * @param s the string description of the type. + * @param u the unit group the new type should belong to if a new group is created. + * @return a data type. + */ + public static Type getType(String s, UnitGroup u) { + for (Type t: TYPES) { + if (t.getName().equalsIgnoreCase(s)) + return t; + } + return new Type(s, u); + } + + + + public static class Type implements Comparable<Type> { + private final String name; + private final UnitGroup units; + private final int priority; + private final int hashCode; + + private Type(String typeName, UnitGroup units) { + this(typeName, units, 999); + } + + public Type(String typeName, UnitGroup units, int priority) { + if (typeName == null) + throw new IllegalArgumentException("typeName is null"); + this.name = typeName; + this.units = units; + this.priority = priority; + this.hashCode = this.name.toLowerCase().hashCode(); + } + + public String getName() { + return name; + } + + public UnitGroup getUnitGroup() { + return units; + } + + @Override + public String toString() { + return name; + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof Type)) + return false; + return this.name.equalsIgnoreCase(((Type)other).name); + } + @Override + public int hashCode() { + return hashCode; + } + + @Override + public int compareTo(Type o) { + return this.priority - o.priority; + } + } + + + + + /** The name of this flight data branch. */ + private final String branchName; + + private final Map<Type, ArrayList<Double>> values = + new LinkedHashMap<Type, ArrayList<Double>>(); + + private final Map<Type, Double> maxValues = new HashMap<Type, Double>(); + private final Map<Type, Double> minValues = new HashMap<Type, Double>(); + + + private final ArrayList<Pair<Double,FlightEvent>> events = + new ArrayList<Pair<Double,FlightEvent>>(); + + private boolean mutable = true; + + + public FlightDataBranch(String name, Type... types) { + if (types.length == 0) { + throw new IllegalArgumentException("Must specify at least one data type."); + } + + this.branchName = name; + + for (Type t: types) { + if (values.containsKey(t)) { + throw new IllegalArgumentException("Value type "+t+" specified multiple " + + "times in constructor."); + } + + values.put(t, new ArrayList<Double>()); + minValues.put(t, Double.NaN); + maxValues.put(t, Double.NaN); + } + } + + + public String getBranchName() { + return branchName; + } + + public Type[] getTypes() { + Type[] array = values.keySet().toArray(new Type[0]); + Arrays.sort(array); + return array; + } + + public int getLength() { + for (Type t: values.keySet()) { + return values.get(t).size(); + } + return 0; + } + + + + public void addPoint() { + if (!mutable) + throw new IllegalStateException("FlightDataBranch has been made immutable."); + for (Type t: values.keySet()) { + values.get(t).add(Double.NaN); + } + } + + public void setValue(Type type, double value) { + if (!mutable) + throw new IllegalStateException("FlightDataBranch has been made immutable."); + ArrayList<Double> list = values.get(type); + if (list == null) { + + list = new ArrayList<Double>(); + int n = getLength(); + for (int i=0; i < n; i++) { + list.add(Double.NaN); + } + values.put(type, list); + minValues.put(type, value); + maxValues.put(type, value); + + } + list.set(list.size()-1, value); + double min = minValues.get(type); + double max = maxValues.get(type); + + if (Double.isNaN(min) || (value < min)) { + minValues.put(type, value); + } + if (Double.isNaN(max) || (value > max)) { + maxValues.put(type, value); + } + } + + + @SuppressWarnings("unchecked") + public List<Double> get(Type type) { + ArrayList<Double> list = values.get(type); + if (list==null) + return null; + return (List<Double>)list.clone(); + } + + + public double get(Type type, int index) { + ArrayList<Double> list = values.get(type); + if (list==null) + return Double.NaN; + return list.get(index); + } + + + /** + * Return the last value of the specified type in the branch, or NaN if the type is + * unavailable. + * + * @param type the parameter type. + * @return the last value in this branch, or NaN. + */ + public double getLast(Type type) { + ArrayList<Double> list = values.get(type); + if (list==null || list.isEmpty()) + return Double.NaN; + return list.get(list.size()-1); + } + + /** + * Return the minimum value of the specified type in the branch, or NaN if the type + * is unavailable. + * + * @param type the parameter type. + * @return the minimum value in this branch, or NaN. + */ + public double getMinimum(Type type) { + Double v = minValues.get(type); + if (v==null) + return Double.NaN; + return v; + } + + /** + * Return the maximum value of the specified type in the branch, or NaN if the type + * is unavailable. + * + * @param type the parameter type. + * @return the maximum value in this branch, or NaN. + */ + public double getMaximum(Type type) { + Double v = maxValues.get(type); + if (v==null) + return Double.NaN; + return v; + } + + + public void addEvent(double time, FlightEvent event) { + if (!mutable) + throw new IllegalStateException("FlightDataBranch has been made immutable."); + events.add(new Pair<Double,FlightEvent>(time,event)); + } + + + /** + * Return the list of events. The list is a list of (time, event) pairs. + * + * @return the list of events during the flight. + */ + @SuppressWarnings("unchecked") + public List<Pair<Double, FlightEvent>> getEvents() { + return (List<Pair<Double, FlightEvent>>) events.clone(); + } + + + /** + * Make this FlightDataBranch immutable. Any calls to the set methods that would + * modify this object will after this call throw an <code>IllegalStateException</code>. + */ + public void immute() { + mutable = false; + } + + public boolean isMutable() { + return mutable; + } +} diff --git a/src/net/sf/openrocket/simulation/FlightEvent.java b/src/net/sf/openrocket/simulation/FlightEvent.java new file mode 100644 index 000000000..d4c1984be --- /dev/null +++ b/src/net/sf/openrocket/simulation/FlightEvent.java @@ -0,0 +1,135 @@ +package net.sf.openrocket.simulation; + +import net.sf.openrocket.rocketcomponent.RocketComponent; + + +/** + * A class that defines an event during the flight of a rocket. + * + * @author Sampo Niskanen <sampo.niskanen@iki.fi> + */ +public class FlightEvent implements Comparable<FlightEvent> { + + /** + * The type of the flight event. + * + * @author Sampo Niskanen <sampo.niskanen@iki.fi> + */ + public enum Type { + /** + * Rocket launch. + */ + LAUNCH, + /** + * When the motor has lifted off the ground. + */ + LIFTOFF, + /** + * Launch rod has been cleared. + */ + LAUNCHROD, + /** + * Ignition of a motor. Source is the motor mount the motor of which has ignited. + */ + IGNITION, + /** + * Burnout of a motor. Source is the motor mount the motor of which has burnt out. + */ + BURNOUT, + /** + * Ejection charge of a motor fired. Source is the motor mount the motor of + * which has exploded its ejection charge. + */ + EJECTION_CHARGE, + /** + * Opening of a recovery device. Source is the RecoveryComponent which has opened. + */ + RECOVERY_DEVICE_DEPLOYMENT, + /** + * Separation of a stage. Source is the stage which has separated all lower stages. + */ + STAGE_SEPARATION, + /** + * Apogee has been reached. + */ + APOGEE, + /** + * Ground has been hit after flight. + */ + GROUND_HIT, + + /** + * End of simulation. Placing this to the queue will end the simulation. + */ + SIMULATION_END, + + /** + * A change in altitude has occurred. Data is a <code>Pair<Double,Double></code> + * which contains the old and new altitudes. + */ + ALTITUDE + } + + private final Type type; + private final double time; + private final RocketComponent source; + private final Object data; + + + public FlightEvent(Type type, double time) { + this(type, time, null); + } + + public FlightEvent(Type type, double time, RocketComponent source) { + this(type,time,source,null); + } + + public FlightEvent(Type type, double time, RocketComponent source, Object data) { + this.type = type; + this.time = time; + this.source = source; + this.data = data; + } + + + + public Type getType() { + return type; + } + + public double getTime() { + return time; + } + + public RocketComponent getSource() { + return source; + } + + public Object getData() { + return data; + } + + + public FlightEvent resetSource() { + return new FlightEvent(type, time, null, data); + } + + /** + * Compares this event to another event depending on the event time. Secondary + * sorting is performed based on the event type ordinal. + */ + @Override + public int compareTo(FlightEvent o) { + if (this.time < o.time) + return -1; + if (this.time > o.time) + return 1; + + return this.type.ordinal() - o.type.ordinal(); + } + + @Override + public String toString() { + return "FlightEvent[type="+type.toString()+",time="+time+",source="+source+"]"; + } +} diff --git a/src/net/sf/openrocket/simulation/FlightSimulator.java b/src/net/sf/openrocket/simulation/FlightSimulator.java new file mode 100644 index 000000000..2b67530ea --- /dev/null +++ b/src/net/sf/openrocket/simulation/FlightSimulator.java @@ -0,0 +1,573 @@ +package net.sf.openrocket.simulation; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.PriorityQueue; + +import net.sf.openrocket.aerodynamics.AerodynamicCalculator; +import net.sf.openrocket.aerodynamics.AtmosphericConditions; +import net.sf.openrocket.aerodynamics.AtmosphericModel; +import net.sf.openrocket.aerodynamics.Warning; +import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.rocketcomponent.Clusterable; +import net.sf.openrocket.rocketcomponent.Configuration; +import net.sf.openrocket.rocketcomponent.Motor; +import net.sf.openrocket.rocketcomponent.MotorMount; +import net.sf.openrocket.rocketcomponent.RecoveryDevice; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.simulation.exception.SimulationException; +import net.sf.openrocket.simulation.exception.SimulationLaunchException; +import net.sf.openrocket.simulation.exception.SimulationNotSupportedException; +import net.sf.openrocket.unit.UnitGroup; +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.MathUtil; +import net.sf.openrocket.util.Pair; + + + + +/** + * Abstract class that implements a flight simulation using a specific + * {@link AerodynamicCalculator}. The simulation methods are the <code>simulate</code> + * methods. + * <p> + * This class contains the event flight event handling mechanisms common to all + * simulations. The simulator calls the {@link #step(SimulationConditions, SimulationStatus)} + * method periodically to take time steps. Concrete subclasses of this class specify + * how the actual time steps are taken (e.g. Euler or Runge-Kutta integration). + * + * @author Sampo Niskanen <sampo.niskanen@iki.fi> + */ +public abstract class FlightSimulator { + + public static final double RECOVERY_TIME_STEP = 0.5; + + /** The {@link AerodynamicCalculator} to use to calculate the aerodynamic forces. */ + protected AerodynamicCalculator calculator = null; + + /** The {@link AtmosphericModel} used to model the atmosphere. */ + protected AtmosphericModel atmosphericModel; + + /** Listener list. */ + protected final List<SimulationListener> listeners = new ArrayList<SimulationListener>(); + + + private PriorityQueue<FlightEvent> eventQueue; + private WarningSet warnings; + + + public FlightSimulator() { + + } + + public FlightSimulator(AerodynamicCalculator calculator) { + this.calculator = calculator; + } + + + + + public AerodynamicCalculator getCalculator() { + return calculator; + } + + public void setCalculator(AerodynamicCalculator calc) { + this.calculator = calc; + } + + + /** + * Will be removed! Use {@link #simulate(SimulationConditions)} instead. + */ + @Deprecated + public FlightData simulate(SimulationConditions simulation, + boolean simulateBranches, WarningSet warnings) + throws SimulationNotSupportedException { + try { + return simulate(simulation); + } catch (SimulationException e) { + throw new SimulationNotSupportedException(e); + } + } + + + public FlightData simulate(SimulationConditions simulation) + throws SimulationException { + + // Set up flight data + FlightData flightData = new FlightData(); + + // Set up rocket configuration + Configuration configuration = calculator.getConfiguration(); + configuration.setAllStages(); + configuration.setMotorConfigurationID(simulation.getMotorConfigurationID()); + + if (!configuration.hasMotors()) { + throw new SimulationLaunchException("No motors defined."); + } + + // Set up the event queue + eventQueue = new PriorityQueue<FlightEvent>(); + eventQueue.add(new FlightEvent(FlightEvent.Type.LAUNCH, 0, simulation.getRocket())); + + // Initialize the simulation + SimulationStatus status = initializeSimulation(configuration, simulation); + status.warnings = flightData.getWarningSet(); + warnings = flightData.getWarningSet(); + + + // Start the simulation + while (handleEvents(eventQueue, status)) { + + // Take the step + double oldAlt = status.position.z; + + if (status.deployedRecoveryDevices.isEmpty()) { + step(simulation, status); + } else { + recoveryStep(simulation, status); + } + + + // Add appropriate events + + if (!status.liftoff) { + + // Avoid sinking into ground before liftoff + if (status.position.z < 0) { + status.position = Coordinate.NUL; + status.velocity = Coordinate.NUL; + } + // Detect liftoff + if (status.position.z > 0.01) { + eventQueue.add(new FlightEvent(FlightEvent.Type.LIFTOFF, status.time)); + status.liftoff = true; + } + + } else { + + // Check ground hit after liftoff + if (status.position.z < 0) { + status.position = status.position.setZ(0); + eventQueue.add(new FlightEvent(FlightEvent.Type.GROUND_HIT, status.time)); + eventQueue.add(new FlightEvent(FlightEvent.Type.SIMULATION_END, status.time)); + } + + } + + + // Add altitude event + eventQueue.add(new FlightEvent(FlightEvent.Type.ALTITUDE, status.time, + status.configuration.getRocket(), + new Pair<Double,Double>(oldAlt,status.position.z))); + + + // Check for launch guide clearance + if (status.launchRod && status.position.length() > status.launchRodLength) { + eventQueue.add(new FlightEvent(FlightEvent.Type.LAUNCHROD, status.time, null)); + status.launchRod = false; + } + + + // Check for apogee + if (!status.apogeeReached && status.position.z < oldAlt - 0.001) { + eventQueue.add(new FlightEvent(FlightEvent.Type.APOGEE, status.time, + status.configuration.getRocket())); + status.apogeeReached = true; + } + + + // Call listeners + SimulationListener[] array = listeners.toArray(new SimulationListener[0]); + for (SimulationListener l: array) { + addListenerEvents(l.stepTaken(status)); + } + } + + flightData.addBranch(status.flightData); + + System.out.println("Warnings at the end: "+flightData.getWarningSet()); + + // TODO: HIGH: Simulate branches + return flightData; + } + + + + + /** + * Handles events occurring during the flight from the <code>eventQueue</code>. + * Each event that has occurred before or at the current simulation time is + * processed. Suitable events are also added to the flight data. + * + * @param data the FlightData to add events to. + * @param endEvent the event at which to end this simulation. + * @param simulateBranches whether to invoke a separate simulation of separated lower + * stages + * @throws SimulationException + */ + private boolean handleEvents(PriorityQueue<FlightEvent> queue, SimulationStatus status) + throws SimulationException { + FlightEvent e; + boolean ret = true; + + e = queue.peek(); + // Skip to events if no motor has ignited yet + if (!status.motorIgnited) { + if (e == null || Double.isNaN(e.getTime()) || e.getTime() > 1000000) { + throw new SimulationLaunchException("No motors ignited."); + } + status.time = e.getTime(); + } + + while ((e != null) && (e.getTime() <= status.time)) { + e = queue.poll(); + + // If no motor has ignited and no events are occurring, abort + if (!status.motorIgnited) { + if (e == null || Double.isNaN(e.getTime()) || e.getTime() > 1000000) { + throw new SimulationLaunchException("No motors ignited."); + } + } + + // If event is motor burnout without liftoff, abort + if (e.getType() == FlightEvent.Type.BURNOUT && !status.liftoff) { + throw new SimulationLaunchException("Motor burnout without liftoff."); + } + + // Add event to flight data + if (e.getType() != FlightEvent.Type.ALTITUDE) { + status.flightData.addEvent(status.time, e.resetSource()); + } + + // Check for motor ignition events, add ignition events to queue + Iterator<MotorMount> iterator = status.configuration.motorIterator(); + while (iterator.hasNext()) { + MotorMount mount = iterator.next(); + if (mount.getIgnitionEvent().isActivationEvent(e, (RocketComponent)mount)) { + queue.add(new FlightEvent(FlightEvent.Type.IGNITION, + status.time + mount.getIgnitionDelay(), (RocketComponent)mount)); + } + } + + // Handle motor ignition events, add burnout events + if (e.getType() == FlightEvent.Type.IGNITION) { + status.motorIgnited = true; + + String id = status.configuration.getMotorConfigurationID(); + MotorMount mount = (MotorMount) e.getSource(); + Motor motor = mount.getMotor(id); + + status.configuration.setIgnitionTime(mount, e.getTime()); + queue.add(new FlightEvent(FlightEvent.Type.BURNOUT, + e.getTime() + motor.getTotalTime(), (RocketComponent)mount)); + queue.add(new FlightEvent(FlightEvent.Type.EJECTION_CHARGE, + e.getTime() + motor.getTotalTime() + mount.getMotorDelay(id), + (RocketComponent)mount)); + } + + + // Handle stage separation on motor ignition + if (e.getType() == FlightEvent.Type.IGNITION) { + RocketComponent mount = (RocketComponent) e.getSource(); + int n = mount.getStageNumber(); + if (n < mount.getRocket().getStageCount()-1) { + if (status.configuration.isStageActive(n+1)) { + queue.add(new FlightEvent(FlightEvent.Type.STAGE_SEPARATION, e.getTime(), + mount.getStage())); + } + } + } + if (e.getType() == FlightEvent.Type.STAGE_SEPARATION) { + RocketComponent stage = (RocketComponent) e.getSource(); + int n = stage.getStageNumber(); + status.configuration.setToStage(n); + } + + + // Handle recovery device deployment + Iterator<RocketComponent> iterator1 = status.configuration.iterator(); + while (iterator1.hasNext()) { + RocketComponent c = iterator1.next(); + if (!(c instanceof RecoveryDevice)) + continue; + if (((RecoveryDevice)c).getDeployEvent().isActivationEvent(e, c)) { + // Delay event by at least 1ms to allow stage separation to occur first + queue.add(new FlightEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, + e.getTime() + Math.max(0.001, ((RecoveryDevice)c).getDeployDelay()), c)); + } + } + if (e.getType() == FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT) { + RocketComponent c = e.getSource(); + int n = c.getStageNumber(); + // Ignore event if stage not active + if (status.configuration.isStageActive(n)) { + + // Check whether any motor is active anymore + iterator = status.configuration.motorIterator(); + while (iterator.hasNext()) { + MotorMount mount = iterator.next(); + Motor motor = mount.getMotor(status.configuration.getMotorConfigurationID()); + if (motor == null) + continue; + if (status.configuration.getIgnitionTime(mount) + motor.getAverageTime() + > status.time) { + warnings.add(Warning.RECOVERY_DEPLOYMENT_WHILE_BURNING); + } + } + + // Check for launch rod + if (status.launchRod) { + warnings.add(Warning.fromString("Recovery device device deployed while on " + + "the launch guide.")); + } + + // Check current velocity + if (status.velocity.length() > 20) { + // TODO: LOW: Custom warning. + warnings.add(Warning.fromString("Recovery device deployment at high " + + "speed (" + + UnitGroup.UNITS_VELOCITY.toStringUnit(status.velocity.length()) + + ").")); + } + + status.liftoff = true; + status.deployedRecoveryDevices.add((RecoveryDevice)c); + } + } + + + + // Simulation end + if (e.getType() == FlightEvent.Type.SIMULATION_END) { + ret = false; + } + + + // Call listeners + SimulationListener[] array = listeners.toArray(new SimulationListener[0]); + for (SimulationListener l: array) { + addListenerEvents(l.handleEvent(e, status)); + } + + + e = queue.peek(); + // Skip to events if no motor has ignited yet + if (!status.motorIgnited) { + if (e == null || Double.isNaN(e.getTime()) || e.getTime() > 1000000) { + throw new SimulationLaunchException("No motors ignited."); + } + status.time = e.getTime(); + } + } + return ret; + } + + + // TODO: MEDIUM: Create method storeData() which is overridden by simulators + + + /** + * Perform a step during recovery. This is a 3-DOF simulation using simple Euler + * integration. + * + * @param conditions the simulation conditions. + * @param status the current simulation status. + */ + protected void recoveryStep(SimulationConditions conditions, SimulationStatus status) { + double totalCD = 0; + double refArea = status.configuration.getReferenceArea(); + + // TODO: MEDIUM: Call listeners during recovery phase + + // Get the atmospheric conditions + AtmosphericConditions atmosphere = conditions.getAtmosphericModel().getConditions( + conditions.getLaunchAltitude() + status.position.z); + + //// Local wind speed and direction + double windSpeed = status.windSimulator.getWindSpeed(status.time); + Coordinate airSpeed = status.velocity.add(windSpeed, 0, 0); + + // Get total CD + double mach = airSpeed.length() / atmosphere.getMachSpeed(); + for (RecoveryDevice c: status.deployedRecoveryDevices) { + totalCD += c.getCD(mach) * c.getArea() / refArea; + } + + // Compute drag force + double dynP = (0.5 * atmosphere.getDensity() * airSpeed.length2()); + double dragForce = totalCD * dynP * refArea; + double mass = calculator.getCG(status.time).weight; + + + // Compute drag acceleration + Coordinate linearAcceleration; + if (airSpeed.length() > 0.001) { + linearAcceleration = airSpeed.normalize().multiply(-dragForce/mass); + } else { + linearAcceleration = Coordinate.NUL; + } + + // Add effect of gravity + linearAcceleration = linearAcceleration.sub(0, 0, status.gravityModel.getGravity()); + + + // Select time step + double timeStep = MathUtil.min(0.5/linearAcceleration.length(), RECOVERY_TIME_STEP); + + // Perform Euler integration + status.position = (status.position.add(status.velocity.multiply(timeStep)). + add(linearAcceleration.multiply(MathUtil.pow2(timeStep)/2))); + status.velocity = status.velocity.add(linearAcceleration.multiply(timeStep)); + status.time += timeStep; + + + // Store data + FlightDataBranch data = status.flightData; + boolean extra = status.startConditions.getCalculateExtras(); + data.addPoint(); + + data.setValue(FlightDataBranch.TYPE_TIME, status.time); + data.setValue(FlightDataBranch.TYPE_ALTITUDE, status.position.z); + data.setValue(FlightDataBranch.TYPE_POSITION_X, status.position.x); + data.setValue(FlightDataBranch.TYPE_POSITION_Y, status.position.y); + if (extra) { + data.setValue(FlightDataBranch.TYPE_POSITION_XY, + MathUtil.hypot(status.position.x, status.position.y)); + data.setValue(FlightDataBranch.TYPE_POSITION_DIRECTION, + Math.atan2(status.position.y, status.position.x)); + + data.setValue(FlightDataBranch.TYPE_VELOCITY_XY, + MathUtil.hypot(status.velocity.x, status.velocity.y)); + data.setValue(FlightDataBranch.TYPE_ACCELERATION_XY, + MathUtil.hypot(linearAcceleration.x, linearAcceleration.y)); + + data.setValue(FlightDataBranch.TYPE_ACCELERATION_TOTAL,linearAcceleration.length()); + + double Re = airSpeed.length() * + calculator.getConfiguration().getLength() / + atmosphere.getKinematicViscosity(); + data.setValue(FlightDataBranch.TYPE_REYNOLDS_NUMBER, Re); + } + + data.setValue(FlightDataBranch.TYPE_VELOCITY_Z, status.velocity.z); + data.setValue(FlightDataBranch.TYPE_ACCELERATION_Z, linearAcceleration.z); + + data.setValue(FlightDataBranch.TYPE_VELOCITY_TOTAL, airSpeed.length()); + data.setValue(FlightDataBranch.TYPE_MACH_NUMBER, mach); + + data.setValue(FlightDataBranch.TYPE_MASS, mass); + + data.setValue(FlightDataBranch.TYPE_THRUST_FORCE, 0); + data.setValue(FlightDataBranch.TYPE_DRAG_FORCE, dragForce); + + data.setValue(FlightDataBranch.TYPE_WIND_VELOCITY, windSpeed); + data.setValue(FlightDataBranch.TYPE_AIR_TEMPERATURE, atmosphere.temperature); + data.setValue(FlightDataBranch.TYPE_AIR_PRESSURE, atmosphere.pressure); + data.setValue(FlightDataBranch.TYPE_SPEED_OF_SOUND, atmosphere.getMachSpeed()); + + data.setValue(FlightDataBranch.TYPE_TIME_STEP, timeStep); + if (status.simulationStartTime != Long.MIN_VALUE) + data.setValue(FlightDataBranch.TYPE_COMPUTATION_TIME, + (System.nanoTime() - status.simulationStartTime)/1000000000.0); + } + + + + + /** + * Add events that listeners have returned, and add a Warning to the + * simulation if necessary. + * + * @param events a collection of the events, or <code>null</code>. + */ + protected final void addListenerEvents(Collection<FlightEvent> events) { + if (events == null) + return; + for (FlightEvent e: events) { + if (e != null && e.getTime() < 1000000) { + warnings.add(Warning.LISTENERS_AFFECTED); + eventQueue.add(e); + } + } + } + + + + /** + * Calculate the average thrust produced by the motors in the current configuration. + * The average is taken between <code>status.time</code> and + * <code>status.time + timestep</code>. + * <p> + * Note: Using this method does not take into account any moments generated by + * off-center motors. + * + * @param status the current simulation status. + * @param timestep the time step of the current iteration. + * @return the average thrust during the time step. + */ + protected double calculateThrust(SimulationStatus status, double timestep) { + double thrust = 0; + Iterator<MotorMount> iterator = status.configuration.motorIterator(); + + while (iterator.hasNext()) { + MotorMount mount = iterator.next(); + + // Count the number of motors in a cluster + int count = 1; + for (RocketComponent c = (RocketComponent)mount; c != null; c = c.getParent()) { + if (c instanceof Clusterable) + count *= ((Clusterable)c).getClusterConfiguration().getClusterCount(); + } + + Motor motor = mount.getMotor(status.configuration.getMotorConfigurationID()); + double ignitionTime = status.configuration.getIgnitionTime(mount); + double time = status.time - ignitionTime; + thrust += count * motor.getThrust(time, time + timestep); + // TODO: MEDIUM: Moment generated by motors + } + + return thrust; + } + + + + /** + * Initialize a new {@link SimulationStatus} object for simulation using this simulator. + * + * @param configuration the starting configuration of the rocket. + * @param simulation the simulation conditions. + * @return a {@link SimulationStatus} object for the simulation. + */ + protected abstract SimulationStatus initializeSimulation(Configuration configuration, + SimulationConditions simulation); + + /** + * Make a time step. The current status of the simulation is stored in the + * variable <code>status</code> and must be updated by this call. + * + * @param simulation the simulation conditions. + * @param status the current simulation status, received originally from + * {@link #initializeSimulation(Configuration, SimulationConditions)} + * @return a collection of flight events to handle, or null for none. + */ + + + protected abstract Collection<FlightEvent> step(SimulationConditions simulation, + SimulationStatus status) throws SimulationException; + + + + public void addSimulationListener(SimulationListener l) { + listeners.add(l); + } + public void removeSimulationListener(SimulationListener l) { + listeners.remove(l); + } + public void resetSimulationListeners() { + listeners.clear(); + } + +} diff --git a/src/net/sf/openrocket/simulation/RK4SimulationStatus.java b/src/net/sf/openrocket/simulation/RK4SimulationStatus.java new file mode 100644 index 000000000..b34a9b011 --- /dev/null +++ b/src/net/sf/openrocket/simulation/RK4SimulationStatus.java @@ -0,0 +1,23 @@ +package net.sf.openrocket.simulation; + +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.Quaternion; + +public class RK4SimulationStatus extends SimulationStatus { + public Quaternion orientation; + public Coordinate rotation; + + public Coordinate launchRodDirection; + + + /** + * Provides a copy of the simulation status. The orientation quaternion is + * cloned as well, so changing it does not affect other simulation status objects. + */ + @Override + public RK4SimulationStatus clone() { + RK4SimulationStatus copy = (RK4SimulationStatus) super.clone(); + copy.orientation = this.orientation.clone(); + return copy; + } +} diff --git a/src/net/sf/openrocket/simulation/RK4Simulator.java b/src/net/sf/openrocket/simulation/RK4Simulator.java new file mode 100644 index 000000000..dd5ec4993 --- /dev/null +++ b/src/net/sf/openrocket/simulation/RK4Simulator.java @@ -0,0 +1,642 @@ +package net.sf.openrocket.simulation; + +import java.util.Collection; + +import net.sf.openrocket.aerodynamics.AerodynamicCalculator; +import net.sf.openrocket.aerodynamics.AerodynamicForces; +import net.sf.openrocket.aerodynamics.AtmosphericConditions; +import net.sf.openrocket.aerodynamics.FlightConditions; +import net.sf.openrocket.aerodynamics.GravityModel; +import net.sf.openrocket.aerodynamics.Warning; +import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.aerodynamics.WindSimulator; +import net.sf.openrocket.rocketcomponent.Configuration; +import net.sf.openrocket.rocketcomponent.LaunchLug; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.simulation.exception.SimulationException; +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.MathUtil; +import net.sf.openrocket.util.Quaternion; +import net.sf.openrocket.util.Rotation2D; + + +public class RK4Simulator extends FlightSimulator { + + /** + * A recommended reasonably accurate time step. + */ + public static final double RECOMMENDED_TIME_STEP = 0.05; + + /** + * A recommended maximum angle step value. + */ + public static final double RECOMMENDED_ANGLE_STEP = 3*Math.PI/180; + + /** + * Maximum roll step allowed. This is selected as an uneven division of the full + * circle so that the simulation will sample the most wind directions + */ + private static final double MAX_ROLL_STEP_ANGLE = 28.32 * Math.PI/180; +// private static final double MAX_ROLL_STEP_ANGLE = 8.32 * Math.PI/180; + + private static final double MAX_ROLL_RATE_CHANGE = 2 * Math.PI/180; + private static final double MAX_PITCH_CHANGE = 2 * Math.PI/180; + + + private static final boolean DEBUG = false; + + + /* Single instance so it doesn't have to be created each semi-step. */ + private final FlightConditions flightConditions = new FlightConditions(null); + + + private Coordinate linearAcceleration; + private Coordinate angularAcceleration; + + // set by calculateFlightConditions and calculateAcceleration: + private double timestep; + private double oldTimestep; + private AerodynamicForces forces; + private double windSpeed; + private double thrustForce, dragForce; + private double lateralPitchRate = 0; + + private double rollAcceleration = 0; + private double lateralPitchAcceleration = 0; + + private double maxVelocityZ = 0; + private double startWarningTime = -1; + + private Rotation2D thetaRotation; + + + public RK4Simulator() { + super(); + } + + + public RK4Simulator(AerodynamicCalculator calculator) { + super(calculator); + } + + + + + + @Override + protected RK4SimulationStatus initializeSimulation(Configuration configuration, + SimulationConditions simulation) { + + RK4SimulationStatus status = new RK4SimulationStatus(); + + status.startConditions = simulation; + + status.configuration = configuration; + // TODO: LOW: Branch names + status.flightData = new FlightDataBranch("Main", FlightDataBranch.TYPE_TIME); + status.launchRod = true; + status.time = 0.0; + status.simulationStartTime = System.nanoTime(); + + status.launchRodDirection = new Coordinate( + Math.sin(simulation.getLaunchRodAngle()) * + Math.cos(simulation.getLaunchRodDirection()), + Math.sin(simulation.getLaunchRodAngle()) * + Math.sin(simulation.getLaunchRodDirection()), + Math.cos(simulation.getLaunchRodAngle()) + ); + status.launchRodLength = simulation.getLaunchRodLength(); + + // Take into account launch lug positions + double lugPosition = Double.NaN; + for (RocketComponent c: configuration) { + if (c instanceof LaunchLug) { + double pos = c.toAbsolute(new Coordinate(c.getLength()))[0].x; + if (Double.isNaN(lugPosition) || pos > lugPosition) { + lugPosition = pos; + } + } + } + if (!Double.isNaN(lugPosition)) { + double maxX = 0; + for (Coordinate c: configuration.getBounds()) { + if (c.x > maxX) + maxX = c.x; + } + if (maxX >= lugPosition) { + status.launchRodLength = Math.max(0, + status.launchRodLength - (maxX - lugPosition)); + } + } + + + Quaternion o = new Quaternion(); + o.multiplyLeft(Quaternion.rotation( + new Coordinate(0, simulation.getLaunchRodAngle(), 0))); + o.multiplyLeft(Quaternion.rotation( + new Coordinate(0, 0, simulation.getLaunchRodDirection()))); + status.orientation = o; + status.position = Coordinate.NUL; + status.velocity = Coordinate.NUL; + status.rotation = Coordinate.NUL; + + /* + * Force a very small deviation to the wind speed to avoid insanely + * perfect conditions (rocket dropping at exactly 180 deg AOA). + */ + status.windSimulator = new WindSimulator(); + status.windSimulator.setAverage(simulation.getWindSpeedAverage()); + status.windSimulator.setStandardDeviation( + Math.max(simulation.getWindSpeedDeviation(), 0.005)); +// status.windSimulator.reset(); + + status.gravityModel = new GravityModel(simulation.getLaunchLatitude()); + + rollAcceleration = 0; + lateralPitchAcceleration = 0; + oldTimestep = -1; + maxVelocityZ = 0; + startWarningTime = -1; + + return status; + } + + + + @Override + protected Collection<FlightEvent> step(SimulationConditions simulation, + SimulationStatus simulationStatus) throws SimulationException { + + RK4SimulationStatus status = (RK4SimulationStatus)simulationStatus; + + //////// Perform RK4 integration: //////// + + Coordinate k1a, k1v, k1ra, k1rv; // Acceleration, velocity, rot.acc, rot.vel + Coordinate k2a, k2v, k2ra, k2rv; + Coordinate k3a, k3v, k3ra, k3rv; + Coordinate k4a, k4v, k4ra, k4rv; + RK4SimulationStatus status2; + + + // Calculate time step and store data after first call to calculateFlightConditions + calculateFlightConditions(status); + + + /* + * Select the time step to use. It is the minimum of the following: + * 1. the user-specified time step + * 2. the maximum pitch step angle limit + * 3. the maximum roll step angle limit + * 4. the maximum roll rate change limit (using previous step acceleration) + * 5. the maximum pitch change limit (using previous step acceleration) + * + * The last two are required since near the steady-state roll rate the roll rate + * may oscillate significantly even between the sub-steps of the RK4 integration. + * + * Additionally a low-pass filter is applied to the time step selectively + * if the new time step is longer than the previous time step. + */ + double dt1 = simulation.getTimeStep(); + double dt2 = simulation.getMaximumStepAngle() / lateralPitchRate; + double dt3 = Math.abs(MAX_ROLL_STEP_ANGLE / flightConditions.getRollRate()); + double dt4 = Math.abs(MAX_ROLL_RATE_CHANGE / rollAcceleration); + double dt5 = Math.abs(MAX_PITCH_CHANGE / lateralPitchAcceleration); + timestep = MathUtil.min(dt1,dt2,dt3); + timestep = MathUtil.min(timestep,dt4,dt5); + + if (oldTimestep > 0 && oldTimestep < timestep) { + timestep = 0.3*timestep + 0.7*oldTimestep; + } + + if (timestep < 0.001) + timestep = 0.001; + + oldTimestep = timestep; + if (DEBUG) + System.out.printf("Time step: %.3f dt1=%.3f dt2=%.3f dt3=%.3f dt4=%.3f dt5=%.3f\n", + timestep,dt1,dt2,dt3,dt4,dt5); + + + + //// First position, k1 = f(t, y) + + //calculateFlightConditions already called + calculateAcceleration(status); + k1a = linearAcceleration; + k1ra = angularAcceleration; + k1v = status.velocity; + k1rv = status.rotation; + + + // Store the flight information + storeData(status); + + + + //// Second position, k2 = f(t + h/2, y + k1*h/2) + + status2 = status.clone(); + status2.time = status.time + timestep/2; + status2.position = status.position.add(k1v.multiply(timestep/2)); + status2.velocity = status.velocity.add(k1a.multiply(timestep/2)); + status2.orientation.multiplyLeft(Quaternion.rotation(k1rv.multiply(timestep/2))); + status2.rotation = status.rotation.add(k1ra.multiply(timestep/2)); + + calculateFlightConditions(status2); + calculateAcceleration(status2); + k2a = linearAcceleration; + k2ra = angularAcceleration; + k2v = status2.velocity; + k2rv = status2.rotation; + + + //// Third position, k3 = f(t + h/2, y + k2*h/2) + + status2.orientation = status.orientation.clone(); // All others are set explicitly + status2.position = status.position.add(k2v.multiply(timestep/2)); + status2.velocity = status.velocity.add(k2a.multiply(timestep/2)); + status2.orientation.multiplyLeft(Quaternion.rotation(k2rv.multiply(timestep/2))); + status2.rotation = status.rotation.add(k2ra.multiply(timestep/2)); + + calculateFlightConditions(status2); + calculateAcceleration(status2); + k3a = linearAcceleration; + k3ra = angularAcceleration; + k3v = status2.velocity; + k3rv = status2.rotation; + + + + //// Fourth position, k4 = f(t + h, y + k3*h) + + status2.orientation = status.orientation.clone(); // All others are set explicitly + status2.time = status.time + timestep; + status2.position = status.position.add(k3v.multiply(timestep)); + status2.velocity = status.velocity.add(k3a.multiply(timestep)); + status2.orientation.multiplyLeft(Quaternion.rotation(k3rv.multiply(timestep))); + status2.rotation = status.rotation.add(k3ra.multiply(timestep)); + + calculateFlightConditions(status2); + calculateAcceleration(status2); + k4a = linearAcceleration; + k4ra = angularAcceleration; + k4v = status2.velocity; + k4rv = status2.rotation; + + + + //// Sum all together, y(n+1) = y(n) + h*(k1 + 2*k2 + 2*k3 + k4)/6 + + Coordinate deltaV, deltaP, deltaR, deltaO; + deltaV = k2a.add(k3a).multiply(2).add(k1a).add(k4a).multiply(timestep/6); + deltaP = k2v.add(k3v).multiply(2).add(k1v).add(k4v).multiply(timestep/6); + deltaR = k2ra.add(k3ra).multiply(2).add(k1ra).add(k4ra).multiply(timestep/6); + deltaO = k2rv.add(k3rv).multiply(2).add(k1rv).add(k4rv).multiply(timestep/6); + + if (DEBUG) + System.out.println("Rot.Acc: "+deltaR+" k1:"+k1ra+" k2:"+k2ra+" k3:"+k3ra+ + " k4:"+k4ra); + + status.velocity = status.velocity.add(deltaV); + status.position = status.position.add(deltaP); + status.rotation = status.rotation.add(deltaR); + status.orientation.multiplyLeft(Quaternion.rotation(deltaO)); + + + status.orientation.normalizeIfNecessary(); + + status.time = status.time + timestep; + + + return null; + } + + + + /** + * Calculate the linear and angular acceleration at the given status. The results + * are stored in the fields {@link #linearAcceleration} and {@link #angularAcceleration}. + * + * @param status the status of the rocket. + * @throws SimulationException + */ + private void calculateAcceleration(RK4SimulationStatus status) throws SimulationException { + + /** + * Check whether to store warnings or not. Warnings are ignored when on the + * launch rod or 0.25 seconds after departure, and when the velocity has dropped + * below 20% of the max. velocity. + */ + WarningSet warnings = status.warnings; + maxVelocityZ = MathUtil.max(maxVelocityZ, status.velocity.z); + if (status.launchRod) { + warnings = null; + } else { + if (status.velocity.z < 0.2 * maxVelocityZ) + warnings = null; + if (startWarningTime < 0) + startWarningTime = status.time + 0.25; + } + if (status.time < startWarningTime) + warnings = null; + + + // Calculate aerodynamic forces (only axial if still on launch rod) + calculator.setConfiguration(status.configuration); + + if (status.launchRod) { + forces = calculator.getAxialForces(status.time, flightConditions, warnings); + } else { + forces = calculator.getAerodynamicForces(status.time, flightConditions, warnings); + } + + + // Allow listeners to modify the forces + int mod = flightConditions.getModCount(); + SimulationListener[] list = listeners.toArray(new SimulationListener[0]); + for (SimulationListener l: list) { + l.forceCalculation(status, flightConditions, forces); + } + if (flightConditions.getModCount() != mod) { + status.warnings.add(Warning.LISTENERS_AFFECTED); + } + + + assert(!Double.isNaN(forces.CD)); + assert(!Double.isNaN(forces.CN)); + assert(!Double.isNaN(forces.Caxial)); + assert(!Double.isNaN(forces.Cm)); + assert(!Double.isNaN(forces.Cyaw)); + assert(!Double.isNaN(forces.Cside)); + assert(!Double.isNaN(forces.Croll)); + + + //////// Calculate forces and accelerations //////// + + double dynP = (0.5 * flightConditions.getAtmosphericConditions().getDensity() * + MathUtil.pow2(flightConditions.getVelocity())); + double refArea = flightConditions.getRefArea(); + double refLength = flightConditions.getRefLength(); + + + // Linear forces + thrustForce = calculateThrust(status, timestep); + dragForce = forces.Caxial * dynP * refArea; + double fN = forces.CN * dynP * refArea; + double fSide = forces.Cside * dynP * refArea; + +// double sin = Math.sin(flightConditions.getTheta()); +// double cos = Math.cos(flightConditions.getTheta()); + +// double forceX = - fN * cos - fSide * sin; +// double forceY = - fN * sin - fSide * cos; + double forceZ = thrustForce - dragForce; + + +// linearAcceleration = new Coordinate(forceX / forces.cg.weight, +// forceY / forces.cg.weight, forceZ / forces.cg.weight); + linearAcceleration = new Coordinate(-fN / forces.cg.weight, -fSide / forces.cg.weight, + forceZ / forces.cg.weight); + + linearAcceleration = thetaRotation.rotateZ(linearAcceleration); + linearAcceleration = status.orientation.rotate(linearAcceleration); + + linearAcceleration = linearAcceleration.sub(0, 0, status.gravityModel.getGravity()); + + + // If still on launch rod, project acceleration onto launch rod direction and + // set angular acceleration to zero. + if (status.launchRod) { + linearAcceleration = status.launchRodDirection.multiply( + linearAcceleration.dot(status.launchRodDirection)); + angularAcceleration = Coordinate.NUL; + rollAcceleration = 0; + lateralPitchAcceleration = 0; + return; + } + + + // Convert momenta + double Cm = forces.Cm - forces.CN * forces.cg.x / refLength; + double Cyaw = forces.Cyaw - forces.Cside * forces.cg.x / refLength; + +// double momX = (-Cm * sin - Cyaw * cos) * dynP * refArea * refLength; +// double momY = ( Cm * cos - Cyaw * sin) * dynP * refArea * refLength; + double momX = -Cyaw * dynP * refArea * refLength; + double momY = Cm * dynP * refArea * refLength; + + double momZ = forces.Croll * dynP * refArea * refLength; + if (DEBUG) + System.out.printf("Croll: %.3f dynP=%.3f momZ=%.3f\n",forces.Croll,dynP,momZ); + + assert(!Double.isNaN(momX)); + assert(!Double.isNaN(momY)); + assert(!Double.isNaN(momZ)); + assert(!Double.isNaN(forces.longitudalInertia)); + assert(!Double.isNaN(forces.rotationalInertia)); + + angularAcceleration = new Coordinate(momX / forces.longitudalInertia, + momY / forces.longitudalInertia, momZ / forces.rotationalInertia); + + rollAcceleration = angularAcceleration.z; + // TODO: LOW: This should be hypot, but does it matter? + lateralPitchAcceleration = MathUtil.max(Math.abs(angularAcceleration.x), + Math.abs(angularAcceleration.y)); + + if (DEBUG) + System.out.println("rot.inertia = "+forces.rotationalInertia); + + angularAcceleration = thetaRotation.rotateZ(angularAcceleration); + + angularAcceleration = status.orientation.rotate(angularAcceleration); + } + + + /** + * Calculate the flight conditions for the current rocket status. The conditions + * are stored in the field {@link #flightConditions}. Additional information that + * is calculated and will be stored in the flight data is also computed into the + * suitable fields. + * @throws SimulationException + */ + private void calculateFlightConditions(RK4SimulationStatus status) throws SimulationException { + + flightConditions.setReference(status.configuration); + + + //// Atmospheric conditions + AtmosphericConditions atmosphere = status.startConditions.getAtmosphericModel(). + getConditions(status.position.z + status.startConditions.getLaunchAltitude()); + flightConditions.setAtmosphericConditions(atmosphere); + + + //// Local wind speed and direction + windSpeed = status.windSimulator.getWindSpeed(status.time); + Coordinate airSpeed = status.velocity.add(windSpeed, 0, 0); + airSpeed = status.orientation.invRotate(airSpeed); + + + // Lateral direction: + double len = MathUtil.hypot(airSpeed.x, airSpeed.y); + if (len > 0.0001) { + thetaRotation = new Rotation2D(airSpeed.y/len, airSpeed.x/len); + flightConditions.setTheta(Math.atan2(airSpeed.y, airSpeed.x)); + } else { + thetaRotation = Rotation2D.ID; + flightConditions.setTheta(0); + } + + double velocity = airSpeed.length(); + flightConditions.setVelocity(velocity); + if (velocity > 0.01) { + // aoa must be calculated from the monotonous cosine + // sine can be calculated by a simple division + flightConditions.setAOA(Math.acos(airSpeed.z / velocity), len / velocity); + } else { + flightConditions.setAOA(0); + } + + + // Roll, pitch and yaw rate + Coordinate rot = status.orientation.invRotate(status.rotation); + rot = thetaRotation.invRotateZ(rot); + + flightConditions.setRollRate(rot.z); + if (len < 0.001) { + flightConditions.setPitchRate(0); + flightConditions.setYawRate(0); + lateralPitchRate = 0; + } else { + flightConditions.setPitchRate(rot.y); + flightConditions.setYawRate(rot.x); + // TODO: LOW: set this as power of two? + lateralPitchRate = MathUtil.hypot(rot.x, rot.y); + } + + + // Allow listeners to modify the conditions + int mod = flightConditions.getModCount(); + SimulationListener[] list = listeners.toArray(new SimulationListener[0]); + for (SimulationListener l: list) { + l.flightConditions(status, flightConditions); + } + if (mod != flightConditions.getModCount()) { + // Re-calculate cached values + thetaRotation = new Rotation2D(flightConditions.getTheta()); + lateralPitchRate = MathUtil.hypot(flightConditions.getPitchRate(), + flightConditions.getYawRate()); + status.warnings.add(Warning.LISTENERS_AFFECTED); + } + + } + + + + private void storeData(RK4SimulationStatus status) { + FlightDataBranch data = status.flightData; + boolean extra = status.startConditions.getCalculateExtras(); + + data.addPoint(); + data.setValue(FlightDataBranch.TYPE_TIME, status.time); + data.setValue(FlightDataBranch.TYPE_ALTITUDE, status.position.z); + data.setValue(FlightDataBranch.TYPE_POSITION_X, status.position.x); + data.setValue(FlightDataBranch.TYPE_POSITION_Y, status.position.y); + + if (extra) { + data.setValue(FlightDataBranch.TYPE_POSITION_XY, + MathUtil.hypot(status.position.x, status.position.y)); + data.setValue(FlightDataBranch.TYPE_POSITION_DIRECTION, + Math.atan2(status.position.y, status.position.x)); + + data.setValue(FlightDataBranch.TYPE_VELOCITY_XY, + MathUtil.hypot(status.velocity.x, status.velocity.y)); + data.setValue(FlightDataBranch.TYPE_ACCELERATION_XY, + MathUtil.hypot(linearAcceleration.x, linearAcceleration.y)); + + data.setValue(FlightDataBranch.TYPE_ACCELERATION_TOTAL,linearAcceleration.length()); + + double Re = flightConditions.getVelocity() * + calculator.getConfiguration().getLength() / + flightConditions.getAtmosphericConditions().getKinematicViscosity(); + data.setValue(FlightDataBranch.TYPE_REYNOLDS_NUMBER, Re); + } + + data.setValue(FlightDataBranch.TYPE_VELOCITY_Z, status.velocity.z); + data.setValue(FlightDataBranch.TYPE_ACCELERATION_Z, linearAcceleration.z); + + data.setValue(FlightDataBranch.TYPE_VELOCITY_TOTAL, flightConditions.getVelocity()); + data.setValue(FlightDataBranch.TYPE_MACH_NUMBER, flightConditions.getMach()); + + if (!status.launchRod) { + data.setValue(FlightDataBranch.TYPE_CP_LOCATION, forces.cp.x); + data.setValue(FlightDataBranch.TYPE_CG_LOCATION, forces.cg.x); + data.setValue(FlightDataBranch.TYPE_STABILITY, + (forces.cp.x - forces.cg.x) / flightConditions.getRefLength()); + } + data.setValue(FlightDataBranch.TYPE_MASS, forces.cg.weight); + + data.setValue(FlightDataBranch.TYPE_THRUST_FORCE, thrustForce); + data.setValue(FlightDataBranch.TYPE_DRAG_FORCE, dragForce); + + if (!status.launchRod) { + data.setValue(FlightDataBranch.TYPE_PITCH_MOMENT_COEFF, + forces.Cm - forces.CN * forces.cg.x / flightConditions.getRefLength()); + data.setValue(FlightDataBranch.TYPE_YAW_MOMENT_COEFF, + forces.Cyaw - forces.Cside * forces.cg.x / flightConditions.getRefLength()); + data.setValue(FlightDataBranch.TYPE_NORMAL_FORCE_COEFF, forces.CN); + data.setValue(FlightDataBranch.TYPE_SIDE_FORCE_COEFF, forces.Cside); + data.setValue(FlightDataBranch.TYPE_ROLL_MOMENT_COEFF, forces.Croll); + data.setValue(FlightDataBranch.TYPE_ROLL_FORCING_COEFF, forces.CrollForce); + data.setValue(FlightDataBranch.TYPE_ROLL_DAMPING_COEFF, forces.CrollDamp); + data.setValue(FlightDataBranch.TYPE_PITCH_DAMPING_MOMENT_COEFF, + forces.pitchDampingMoment); + } + + data.setValue(FlightDataBranch.TYPE_DRAG_COEFF, forces.CD); + data.setValue(FlightDataBranch.TYPE_AXIAL_DRAG_COEFF, forces.Caxial); + data.setValue(FlightDataBranch.TYPE_FRICTION_DRAG_COEFF, forces.frictionCD); + data.setValue(FlightDataBranch.TYPE_PRESSURE_DRAG_COEFF, forces.pressureCD); + data.setValue(FlightDataBranch.TYPE_BASE_DRAG_COEFF, forces.baseCD); + + data.setValue(FlightDataBranch.TYPE_REFERENCE_LENGTH, flightConditions.getRefLength()); + data.setValue(FlightDataBranch.TYPE_REFERENCE_AREA, flightConditions.getRefArea()); + + + data.setValue(FlightDataBranch.TYPE_PITCH_RATE, flightConditions.getPitchRate()); + data.setValue(FlightDataBranch.TYPE_YAW_RATE, flightConditions.getYawRate()); + + + + if (extra) { + Coordinate c = status.orientation.rotateZ(); + double theta = Math.atan2(c.z, MathUtil.hypot(c.x, c.y)); + double phi = Math.atan2(c.y, c.x); + if (phi < -(Math.PI-0.0001)) + phi = Math.PI; + data.setValue(FlightDataBranch.TYPE_ORIENTATION_THETA, theta); + data.setValue(FlightDataBranch.TYPE_ORIENTATION_PHI, phi); + } + + data.setValue(FlightDataBranch.TYPE_AOA, flightConditions.getAOA()); + data.setValue(FlightDataBranch.TYPE_ROLL_RATE, flightConditions.getRollRate()); + + data.setValue(FlightDataBranch.TYPE_WIND_VELOCITY, windSpeed); + data.setValue(FlightDataBranch.TYPE_AIR_TEMPERATURE, + flightConditions.getAtmosphericConditions().temperature); + data.setValue(FlightDataBranch.TYPE_AIR_PRESSURE, + flightConditions.getAtmosphericConditions().pressure); + data.setValue(FlightDataBranch.TYPE_SPEED_OF_SOUND, + flightConditions.getAtmosphericConditions().getMachSpeed()); + + + data.setValue(FlightDataBranch.TYPE_TIME_STEP, timestep); + data.setValue(FlightDataBranch.TYPE_COMPUTATION_TIME, + (System.nanoTime() - status.simulationStartTime)/1000000000.0); + + +// data.setValue(FlightDataBranch.TYPE_, 0); + + } + + +} diff --git a/src/net/sf/openrocket/simulation/SimulationConditions.java b/src/net/sf/openrocket/simulation/SimulationConditions.java new file mode 100644 index 000000000..4d932b251 --- /dev/null +++ b/src/net/sf/openrocket/simulation/SimulationConditions.java @@ -0,0 +1,402 @@ +package net.sf.openrocket.simulation; + +import java.util.ArrayList; +import java.util.List; + +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import net.sf.openrocket.aerodynamics.AtmosphericModel; +import net.sf.openrocket.aerodynamics.ExtendedISAModel; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.util.ChangeSource; +import net.sf.openrocket.util.MathUtil; + + +public class SimulationConditions implements ChangeSource, Cloneable { + + public static final double MAX_LAUNCH_ROD_ANGLE = Math.PI/3; + + /** + * The ISA standard atmosphere. + */ + private static final AtmosphericModel ISA_ATMOSPHERIC_MODEL = new ExtendedISAModel(); + + + private final Rocket rocket; + private String motorID = null; + + + /* + * NOTE: When adding/modifying parameters, they must also be added to the + * equals and copyFrom methods!! + */ + + // TODO: HIGH: Fetch default values from Prefs! + + private double launchRodLength = 1; + + /** Launch rod angle > 0, radians from vertical */ + private double launchRodAngle = 0; + + /** Launch rod direction, 0 = upwind, PI = downwind. */ + private double launchRodDirection = 0; + + + private double windAverage = 2.0; + private double windTurbulence = 0.1; + + private double launchAltitude = 0; + private double launchLatitude = 45; + + private boolean useISA = true; + private double launchTemperature = ExtendedISAModel.STANDARD_TEMPERATURE; + private double launchPressure = ExtendedISAModel.STANDARD_PRESSURE; + private AtmosphericModel atmosphericModel = null; + + + private double timeStep = RK4Simulator.RECOMMENDED_TIME_STEP; + private double maximumAngle = RK4Simulator.RECOMMENDED_ANGLE_STEP; + + private boolean calculateExtras = true; + + + private List<ChangeListener> listeners = new ArrayList<ChangeListener>(); + + + + public SimulationConditions(Rocket rocket) { + this.rocket = rocket; + } + + + + public Rocket getRocket() { + return rocket; + } + + + public String getMotorConfigurationID() { + return motorID; + } + + public void setMotorConfigurationID(String id) { + if (id != null) + id = id.intern(); + if (id == motorID) + return; + motorID = id; + fireChangeEvent(); + } + + + public double getLaunchRodLength() { + return launchRodLength; + } + + public void setLaunchRodLength(double launchRodLength) { + if (MathUtil.equals(this.launchRodLength, launchRodLength)) + return; + this.launchRodLength = launchRodLength; + fireChangeEvent(); + } + + + public double getLaunchRodAngle() { + return launchRodAngle; + } + + public void setLaunchRodAngle(double launchRodAngle) { + launchRodAngle = MathUtil.clamp(launchRodAngle, 0, MAX_LAUNCH_ROD_ANGLE); + if (MathUtil.equals(this.launchRodAngle, launchRodAngle)) + return; + this.launchRodAngle = launchRodAngle; + fireChangeEvent(); + } + + + public double getLaunchRodDirection() { + return launchRodDirection; + } + + public void setLaunchRodDirection(double launchRodDirection) { + launchRodDirection = MathUtil.reduce180(launchRodDirection); + if (MathUtil.equals(this.launchRodDirection, launchRodDirection)) + return; + this.launchRodDirection = launchRodDirection; + fireChangeEvent(); + } + + + + public double getWindSpeedAverage() { + return windAverage; + } + + public void setWindSpeedAverage(double windAverage) { + if (MathUtil.equals(this.windAverage, windAverage)) + return; + this.windAverage = MathUtil.max(windAverage, 0); + fireChangeEvent(); + } + + + public double getWindSpeedDeviation() { + return windAverage * windTurbulence; + } + + public void setWindSpeedDeviation(double windDeviation) { + if (windAverage < 0.1) { + windAverage = 0.1; + } + setWindTurbulenceIntensity(windDeviation / windAverage); + } + + + /** + * Return the wind turbulence intensity (standard deviation / average). + * + * @return the turbulence intensity + */ + public double getWindTurbulenceIntensity() { + return windTurbulence; + } + + /** + * Set the wind standard deviation to match the given turbulence intensity. + * + * @param intensity the turbulence intensity + */ + public void setWindTurbulenceIntensity(double intensity) { + // Does not check equality so that setWindSpeedDeviation can be sure of event firing + this.windTurbulence = intensity; + fireChangeEvent(); + } + + + + + + public double getLaunchAltitude() { + return launchAltitude; + } + + public void setLaunchAltitude(double altitude) { + if (MathUtil.equals(this.launchAltitude, altitude)) + return; + this.launchAltitude = altitude; + fireChangeEvent(); + } + + + public double getLaunchLatitude() { + return launchLatitude; + } + + public void setLaunchLatitude(double launchLatitude) { + launchLatitude = MathUtil.clamp(launchLatitude, -90, 90); + if (MathUtil.equals(this.launchLatitude, launchLatitude)) + return; + this.launchLatitude = launchLatitude; + fireChangeEvent(); + } + + + + + + public boolean isISAAtmosphere() { + return useISA; + } + + public void setISAAtmosphere(boolean isa) { + if (isa == useISA) + return; + useISA = isa; + fireChangeEvent(); + } + + + public double getLaunchTemperature() { + return launchTemperature; + } + + + + public void setLaunchTemperature(double launchTemperature) { + if (MathUtil.equals(this.launchTemperature, launchTemperature)) + return; + this.launchTemperature = launchTemperature; + this.atmosphericModel = null; + fireChangeEvent(); + } + + + + public double getLaunchPressure() { + return launchPressure; + } + + + + public void setLaunchPressure(double launchPressure) { + if (MathUtil.equals(this.launchPressure, launchPressure)) + return; + this.launchPressure = launchPressure; + this.atmosphericModel = null; + fireChangeEvent(); + } + + + /** + * Returns an atmospheric model corresponding to the launch conditions. The + * atmospheric models may be shared between different calls. + * + * @return an AtmosphericModel object. + */ + public AtmosphericModel getAtmosphericModel() { + if (useISA) { + return ISA_ATMOSPHERIC_MODEL; + } + if (atmosphericModel == null) { + atmosphericModel = new ExtendedISAModel(launchAltitude, + launchTemperature, launchPressure); + } + return atmosphericModel; + } + + + public double getTimeStep() { + return timeStep; + } + + public void setTimeStep(double timeStep) { + if (MathUtil.equals(this.timeStep, timeStep)) + return; + this.timeStep = timeStep; + fireChangeEvent(); + } + + public double getMaximumStepAngle() { + return maximumAngle; + } + + public void setMaximumStepAngle(double maximumAngle) { + maximumAngle = MathUtil.clamp(maximumAngle, 1*Math.PI/180, 20*Math.PI/180); + if (MathUtil.equals(this.maximumAngle, maximumAngle)) + return; + this.maximumAngle = maximumAngle; + fireChangeEvent(); + } + + + + public boolean getCalculateExtras() { + return calculateExtras; + } + + + + public void setCalculateExtras(boolean calculateExtras) { + if (this.calculateExtras == calculateExtras) + return; + this.calculateExtras = calculateExtras; + fireChangeEvent(); + } + + + + @Override + public SimulationConditions clone() { + try { + SimulationConditions copy = (SimulationConditions)super.clone(); + copy.listeners = new ArrayList<ChangeListener>(); + return copy; + } catch (CloneNotSupportedException e) { + throw new RuntimeException(e); + } + } + + + public void copyFrom(SimulationConditions src) { + if (this.rocket != src.rocket) { + throw new IllegalArgumentException("Unable to copy simulation conditions of "+ + "a difference rocket"); + } + if (this.equals(src)) + return; + + this.motorID = src.motorID; + this.launchAltitude = src.launchAltitude; + this.launchLatitude = src.launchLatitude; + this.launchPressure = src.launchPressure; + this.launchRodAngle = src.launchRodAngle; + this.launchRodDirection = src.launchRodDirection; + this.launchRodLength = src.launchRodLength; + this.launchTemperature = src.launchTemperature; + this.maximumAngle = src.maximumAngle; + this.timeStep = src.timeStep; + this.windAverage = src.windAverage; + this.windTurbulence = src.windTurbulence; + this.calculateExtras = src.calculateExtras; + + fireChangeEvent(); + } + + + + /** + * Compares whether the two simulation conditions are equal. The two are considered + * equal if the rocket, motor id and all variables are equal. + */ + @Override + public boolean equals(Object other) { + if (!(other instanceof SimulationConditions)) + return false; + SimulationConditions o = (SimulationConditions)other; + return ((this.rocket == o.rocket) && + this.motorID == o.motorID && + MathUtil.equals(this.launchAltitude, o.launchAltitude) && + MathUtil.equals(this.launchLatitude, o.launchLatitude) && + MathUtil.equals(this.launchPressure, o.launchPressure) && + MathUtil.equals(this.launchRodAngle, o.launchRodAngle) && + MathUtil.equals(this.launchRodDirection, o.launchRodDirection) && + MathUtil.equals(this.launchRodLength, o.launchRodLength) && + MathUtil.equals(this.launchTemperature, o.launchTemperature) && + MathUtil.equals(this.maximumAngle, o.maximumAngle) && + MathUtil.equals(this.timeStep, o.timeStep) && + MathUtil.equals(this.windAverage, o.windAverage) && + MathUtil.equals(this.windTurbulence, o.windTurbulence) && + this.calculateExtras == o.calculateExtras); + } + + /** + * Hashcode method compatible with {@link #equals(Object)}. + */ + @Override + public int hashCode() { + if (motorID == null) + return rocket.hashCode(); + return rocket.hashCode() + motorID.hashCode(); + } + + @Override + public void addChangeListener(ChangeListener listener) { + listeners.add(listener); + } + + @Override + public void removeChangeListener(ChangeListener listener) { + listeners.remove(listener); + } + + private final ChangeEvent event = new ChangeEvent(this); + private void fireChangeEvent() { + ChangeListener[] array = listeners.toArray(new ChangeListener[0]); + + for (int i=array.length-1; i >=0; i--) { + array[i].stateChanged(event); + } + } + +} diff --git a/src/net/sf/openrocket/simulation/SimulationListener.java b/src/net/sf/openrocket/simulation/SimulationListener.java new file mode 100644 index 000000000..0f427a92e --- /dev/null +++ b/src/net/sf/openrocket/simulation/SimulationListener.java @@ -0,0 +1,50 @@ +package net.sf.openrocket.simulation; + +import java.util.Collection; + +import net.sf.openrocket.aerodynamics.AerodynamicForces; +import net.sf.openrocket.aerodynamics.FlightConditions; +import net.sf.openrocket.simulation.exception.SimulationException; + + + +public interface SimulationListener { + + + public void flightConditions(SimulationStatus status, FlightConditions conditions) + throws SimulationException; + + + public void forceCalculation(SimulationStatus status, FlightConditions conditions, + AerodynamicForces forces) throws SimulationException; + + + /** + * Called every time a simulation step has been taken. The parameter contains the + * simulation status. This method may abort the simulation by returning a + * <code>SIMULATION_END</code> event. Note that this event and all others within + * the current time are still handled, so be careful not to create an infinite loop + * of events. + * + * @param status the current flight status. + * @return new flight events to handle, or <code>null</code> for none. + */ + public Collection<FlightEvent> stepTaken(SimulationStatus status) + throws SimulationException; + + + /** + * Called every time an event is handled by the simulation system. The parameters + * contain the event and current simulation status. This method may abort the + * simulation by returning a <code>SIMULATION_END</code> event. Note that this + * event and all others within the current time are still handled, so be careful + * not to create an infinite loop of events. + * + * @param event the event that triggered this call. + * @param status the current flight status. + * @return new flight events to handle, or <code>null</code> for none. + */ + public Collection<FlightEvent> handleEvent(FlightEvent event, SimulationStatus status) + throws SimulationException; + +} diff --git a/src/net/sf/openrocket/simulation/SimulationStatus.java b/src/net/sf/openrocket/simulation/SimulationStatus.java new file mode 100644 index 000000000..4d05cbf16 --- /dev/null +++ b/src/net/sf/openrocket/simulation/SimulationStatus.java @@ -0,0 +1,72 @@ +package net.sf.openrocket.simulation; + +import java.util.HashSet; +import java.util.Set; + +import net.sf.openrocket.aerodynamics.GravityModel; +import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.aerodynamics.WindSimulator; +import net.sf.openrocket.rocketcomponent.Configuration; +import net.sf.openrocket.rocketcomponent.RecoveryDevice; +import net.sf.openrocket.util.Coordinate; + + +public class SimulationStatus implements Cloneable { + + public SimulationConditions startConditions; + + public double time; + public Configuration configuration; + public FlightDataBranch flightData; + + public Coordinate position; + public Coordinate velocity; + + public WindSimulator windSimulator; + public GravityModel gravityModel; + + public double launchRodLength; + + + /** Nanosecond time when the simulation was started. */ + public long simulationStartTime = Long.MIN_VALUE; + + + /** Set to true when a motor has ignited. */ + public boolean motorIgnited = false; + + /** Set to true when the rocket has risen from the ground. */ + public boolean liftoff = false; + + /** <code>true</code> while the rocket is on the launch rod. */ + public boolean launchRod = true; + + /** Set to true when apogee has been detected. */ + public boolean apogeeReached = false; + + /** Contains a list of deployed recovery devices. */ + public final Set<RecoveryDevice> deployedRecoveryDevices = new HashSet<RecoveryDevice>(); + + + public WarningSet warnings; + + + /** Available for special purposes by the listeners. */ + public Object extra = null; + + + /** + * Returns a (shallow) copy of this object. The general purpose is that the + * conditions, flight data etc. point to the same objects. However, subclasses + * are allowed to deep-clone specific objects, such as those pertaining to the + * current orientation of the rocket. + */ + @Override + public SimulationStatus clone() { + try { + return (SimulationStatus) super.clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeException("BUG: CloneNotSupportedException?!?",e); + } + } +} diff --git a/src/net/sf/openrocket/simulation/exception/SimulationCancelledException.java b/src/net/sf/openrocket/simulation/exception/SimulationCancelledException.java new file mode 100644 index 000000000..e22934324 --- /dev/null +++ b/src/net/sf/openrocket/simulation/exception/SimulationCancelledException.java @@ -0,0 +1,27 @@ +package net.sf.openrocket.simulation.exception; + + +/** + * An exception signifying that a simulation was cancelled. + * + * @author Sampo Niskanen <sampo.niskanen@iki.fi> + */ +public class SimulationCancelledException extends SimulationException { + + public SimulationCancelledException() { + + } + + public SimulationCancelledException(String message) { + super(message); + } + + public SimulationCancelledException(Throwable cause) { + super(cause); + } + + public SimulationCancelledException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/net/sf/openrocket/simulation/exception/SimulationException.java b/src/net/sf/openrocket/simulation/exception/SimulationException.java new file mode 100644 index 000000000..f180f892c --- /dev/null +++ b/src/net/sf/openrocket/simulation/exception/SimulationException.java @@ -0,0 +1,21 @@ +package net.sf.openrocket.simulation.exception; + +public class SimulationException extends Exception { + + public SimulationException() { + + } + + public SimulationException(String message) { + super(message); + } + + public SimulationException(Throwable cause) { + super(cause); + } + + public SimulationException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/net/sf/openrocket/simulation/exception/SimulationLaunchException.java b/src/net/sf/openrocket/simulation/exception/SimulationLaunchException.java new file mode 100644 index 000000000..1c840e35f --- /dev/null +++ b/src/net/sf/openrocket/simulation/exception/SimulationLaunchException.java @@ -0,0 +1,27 @@ +package net.sf.openrocket.simulation.exception; + +/** + * An exception signifying that a problem occurred at launch, for example + * that no motors were defined or no motors ignited. + * + * @author Sampo Niskanen <sampo.niskanen@iki.fi> + */ +public class SimulationLaunchException extends SimulationException { + + public SimulationLaunchException() { + + } + + public SimulationLaunchException(String message) { + super(message); + } + + public SimulationLaunchException(Throwable cause) { + super(cause); + } + + public SimulationLaunchException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/net/sf/openrocket/simulation/exception/SimulationListenerException.java b/src/net/sf/openrocket/simulation/exception/SimulationListenerException.java new file mode 100644 index 000000000..d6bd737a8 --- /dev/null +++ b/src/net/sf/openrocket/simulation/exception/SimulationListenerException.java @@ -0,0 +1,21 @@ +package net.sf.openrocket.simulation.exception; + + +public class SimulationListenerException extends SimulationException { + + public SimulationListenerException() { + } + + public SimulationListenerException(String message) { + super(message); + } + + public SimulationListenerException(Throwable cause) { + super(cause); + } + + public SimulationListenerException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/net/sf/openrocket/simulation/exception/SimulationNotSupportedException.java b/src/net/sf/openrocket/simulation/exception/SimulationNotSupportedException.java new file mode 100644 index 000000000..66618258f --- /dev/null +++ b/src/net/sf/openrocket/simulation/exception/SimulationNotSupportedException.java @@ -0,0 +1,30 @@ +package net.sf.openrocket.simulation.exception; + + +/** + * A exception that signifies that the attempted simulation is not supported. + * The reason for not being supported may be due to unsupported combination of + * simulator/calculator, unsupported rocket structure or other reasons. + * <p> + * This exception signifies a fatal problem in the simulation; for non-fatal conditions + * add a warning to the simulation results. + * + * @author Sampo Niskanen <sampo.niskanen@iki.fi> + */ +public class SimulationNotSupportedException extends SimulationException { + + public SimulationNotSupportedException() { + } + + public SimulationNotSupportedException(String message) { + super(message); + } + + public SimulationNotSupportedException(Throwable cause) { + super(cause); + } + + public SimulationNotSupportedException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/net/sf/openrocket/simulation/listeners/AbstractSimulationListener.java b/src/net/sf/openrocket/simulation/listeners/AbstractSimulationListener.java new file mode 100644 index 000000000..d6624505e --- /dev/null +++ b/src/net/sf/openrocket/simulation/listeners/AbstractSimulationListener.java @@ -0,0 +1,41 @@ +package net.sf.openrocket.simulation.listeners; + +import java.util.Collection; + +import net.sf.openrocket.aerodynamics.AerodynamicForces; +import net.sf.openrocket.aerodynamics.FlightConditions; +import net.sf.openrocket.simulation.FlightEvent; +import net.sf.openrocket.simulation.SimulationListener; +import net.sf.openrocket.simulation.SimulationStatus; +import net.sf.openrocket.simulation.exception.SimulationException; + + +public abstract class AbstractSimulationListener implements SimulationListener { + + @Override + public void flightConditions(SimulationStatus status, FlightConditions conditions) + throws SimulationException { + // No-op + } + + @Override + public void forceCalculation(SimulationStatus status, FlightConditions conditions, + AerodynamicForces forces) throws SimulationException { + // No-op + } + + @Override + public Collection<FlightEvent> handleEvent(FlightEvent event, + SimulationStatus status) throws SimulationException { + // No-op + return null; + } + + @Override + public Collection<FlightEvent> stepTaken(SimulationStatus status) + throws SimulationException { + // No-op + return null; + } + +} diff --git a/src/net/sf/openrocket/simulation/listeners/ApogeeEndListener.java b/src/net/sf/openrocket/simulation/listeners/ApogeeEndListener.java new file mode 100644 index 000000000..7f5f8fdae --- /dev/null +++ b/src/net/sf/openrocket/simulation/listeners/ApogeeEndListener.java @@ -0,0 +1,31 @@ +package net.sf.openrocket.simulation.listeners; + +import java.util.Collection; +import java.util.Collections; + +import net.sf.openrocket.simulation.FlightEvent; +import net.sf.openrocket.simulation.SimulationStatus; +import net.sf.openrocket.simulation.exception.SimulationException; + + +/** + * A simulation listeners that ends the simulation when apogee is reached. + * + * @author Sampo Niskanen <sampo.niskanen@iki.fi> + */ +public class ApogeeEndListener extends AbstractSimulationListener { + + public static final ApogeeEndListener INSTANCE = new ApogeeEndListener(); + + @Override + public Collection<FlightEvent> handleEvent(FlightEvent event, + SimulationStatus status) throws SimulationException { + + if (event.getType() == FlightEvent.Type.APOGEE) { + return Collections.singleton(new FlightEvent(FlightEvent.Type.SIMULATION_END, + status.time)); + } + return null; + } + +} diff --git a/src/net/sf/openrocket/simulation/listeners/CSVSaveListener.java b/src/net/sf/openrocket/simulation/listeners/CSVSaveListener.java new file mode 100644 index 000000000..dea0d01a3 --- /dev/null +++ b/src/net/sf/openrocket/simulation/listeners/CSVSaveListener.java @@ -0,0 +1,309 @@ +package net.sf.openrocket.simulation.listeners; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.PrintStream; +import java.util.Collection; +import java.util.Iterator; + +import net.sf.openrocket.rocketcomponent.FinSet; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.simulation.FlightDataBranch; +import net.sf.openrocket.simulation.FlightEvent; +import net.sf.openrocket.simulation.SimulationStatus; + + +public class CSVSaveListener extends AbstractSimulationListener { + + private static enum Types { + TIME { + @Override + public double getValue(SimulationStatus status) { + return status.time; + } + }, + POSITION_X { + @Override + public double getValue(SimulationStatus status) { + return status.position.x; + } + }, + POSITION_Y { + @Override + public double getValue(SimulationStatus status) { + return status.position.y; + } + }, + ALTITUDE { + @Override + public double getValue(SimulationStatus status) { + return status.position.z; + } + }, + VELOCITY_X { + @Override + public double getValue(SimulationStatus status) { + return status.velocity.x; + } + }, + VELOCITY_Y { + @Override + public double getValue(SimulationStatus status) { + return status.velocity.y; + } + }, + VELOCITY_Z { + @Override + public double getValue(SimulationStatus status) { + return status.velocity.z; + } + }, + THETA { + @Override + public double getValue(SimulationStatus status) { + return status.flightData.getLast(FlightDataBranch.TYPE_ORIENTATION_THETA); + } + }, + PHI { + @Override + public double getValue(SimulationStatus status) { + return status.flightData.getLast(FlightDataBranch.TYPE_ORIENTATION_PHI); + } + }, + AOA { + @Override + public double getValue(SimulationStatus status) { + return status.flightData.getLast(FlightDataBranch.TYPE_AOA); + } + }, + ROLLRATE { + @Override + public double getValue(SimulationStatus status) { + return status.flightData.getLast(FlightDataBranch.TYPE_ROLL_RATE); + } + }, + PITCHRATE { + @Override + public double getValue(SimulationStatus status) { + return status.flightData.getLast(FlightDataBranch.TYPE_PITCH_RATE); + } + }, + + PITCHMOMENT { + @Override + public double getValue(SimulationStatus status) { + return status.flightData.getLast(FlightDataBranch.TYPE_PITCH_MOMENT_COEFF); + } + }, + YAWMOMENT { + @Override + public double getValue(SimulationStatus status) { + return status.flightData.getLast(FlightDataBranch.TYPE_YAW_MOMENT_COEFF); + } + }, + ROLLMOMENT { + @Override + public double getValue(SimulationStatus status) { + return status.flightData.getLast(FlightDataBranch.TYPE_ROLL_MOMENT_COEFF); + } + }, + NORMALFORCE { + @Override + public double getValue(SimulationStatus status) { + return status.flightData.getLast(FlightDataBranch.TYPE_NORMAL_FORCE_COEFF); + } + }, + SIDEFORCE { + @Override + public double getValue(SimulationStatus status) { + return status.flightData.getLast(FlightDataBranch.TYPE_SIDE_FORCE_COEFF); + } + }, + AXIALFORCE { + @Override + public double getValue(SimulationStatus status) { + return status.flightData.getLast(FlightDataBranch.TYPE_DRAG_FORCE); + } + }, + WINDSPEED { + @Override + public double getValue(SimulationStatus status) { + return status.flightData.getLast(FlightDataBranch.TYPE_WIND_VELOCITY); + } + }, + PITCHDAMPING { + @Override + public double getValue(SimulationStatus status) { + return status.flightData.getLast(FlightDataBranch. + TYPE_PITCH_DAMPING_MOMENT_COEFF); + } + }, + CA { + @Override + public double getValue(SimulationStatus status) { + return status.flightData.getLast(FlightDataBranch.TYPE_AXIAL_DRAG_COEFF); + } + }, + CD { + @Override + public double getValue(SimulationStatus status) { + return status.flightData.getLast(FlightDataBranch.TYPE_DRAG_COEFF); + } + }, + CDpressure { + @Override + public double getValue(SimulationStatus status) { + return status.flightData.getLast(FlightDataBranch.TYPE_PRESSURE_DRAG_COEFF); + } + }, + CDfriction { + @Override + public double getValue(SimulationStatus status) { + return status.flightData.getLast(FlightDataBranch.TYPE_FRICTION_DRAG_COEFF); + } + }, + CDbase { + @Override + public double getValue(SimulationStatus status) { + return status.flightData.getLast(FlightDataBranch.TYPE_BASE_DRAG_COEFF); + } + }, + MACH { + @Override + public double getValue(SimulationStatus status) { + return status.flightData.getLast(FlightDataBranch.TYPE_MACH_NUMBER); + } + }, + RE { + @Override + public double getValue(SimulationStatus status) { + return status.flightData.getLast(FlightDataBranch.TYPE_REYNOLDS_NUMBER); + } + }, + + CONTROL_ANGLE { + @Override + public double getValue(SimulationStatus status) { + Iterator<RocketComponent> iterator = + status.configuration.getRocket().deepIterator(); + FinSet fin = null; + + while (iterator.hasNext()) { + RocketComponent c = iterator.next(); + if (c instanceof FinSet && c.getName().equals("CONTROL")) { + fin = (FinSet)c; + break; + } + } + if (fin==null) + return 0; + return fin.getCantAngle(); + } + }, + + EXTRA { + @Override + public double getValue(SimulationStatus status) { + if (status.extra instanceof Double) + return (Double)status.extra; + else + return Double.NaN; + } + }, + + MASS { + @Override + public double getValue(SimulationStatus status) { + return status.flightData.getLast(FlightDataBranch.TYPE_MASS); + } + } + + ; + + public abstract double getValue(SimulationStatus status); + } + + + public static final String FILENAME_FORMAT = "simulation-%03d.csv"; + + private File file; + private PrintStream output = null; + + + + @Override + public Collection<FlightEvent> handleEvent(FlightEvent event, + SimulationStatus status) { + + if (event.getType() == FlightEvent.Type.LAUNCH) { + int n = 1; + + if (output != null) { + System.err.println("WARNING: Ending simulation logging to CSV file " + + "(SIMULATION_END not encountered)."); + output.close(); + output = null; + } + + do { + file = new File(String.format(FILENAME_FORMAT, n)); + n++; + } while (file.exists()); + + System.err.println("Opening file "+file+" for CSV output."); + try { + output = new PrintStream(file); + } catch (FileNotFoundException e) { + System.err.println("ERROR OPENING FILE: "+e); + } + + final Types[] types = Types.values(); + StringBuilder s = new StringBuilder("# " + types[0].toString()); + for (int i=1; i<types.length; i++) { + s.append("," + types[i].toString()); + } + output.println(s); + + } else if (event.getType() == FlightEvent.Type.SIMULATION_END && output != null) { + + System.err.println("Ending simulation logging to CSV file: "+file); + output.close(); + output = null; + + } else if (event.getType() != FlightEvent.Type.ALTITUDE){ + + if (output != null) { + output.println("# Event "+event); + } else { + System.err.println("WARNING: Event "+event+" encountered without open file"); + } + + } + + return null; + } + + + @Override + public Collection<FlightEvent> stepTaken(SimulationStatus status) { + + final Types[] types = Types.values(); + StringBuilder s; + + if (output != null) { + + s = new StringBuilder("" + types[0].getValue(status)); + for (int i=1; i<types.length; i++) { + s.append("," + types[i].getValue(status)); + } + output.println(s); + + } else { + + System.err.println("WARNING: stepTaken called with no open file " + + "(t="+status.time+")"); + } + + return null; + } +} diff --git a/src/net/sf/openrocket/simulation/listeners/InterruptListener.java b/src/net/sf/openrocket/simulation/listeners/InterruptListener.java new file mode 100644 index 000000000..44c2a252f --- /dev/null +++ b/src/net/sf/openrocket/simulation/listeners/InterruptListener.java @@ -0,0 +1,31 @@ +package net.sf.openrocket.simulation.listeners; + +import java.util.Collection; + +import net.sf.openrocket.simulation.FlightEvent; +import net.sf.openrocket.simulation.SimulationStatus; +import net.sf.openrocket.simulation.exception.SimulationCancelledException; + + +/** + * A simulation listener that throws a {@link SimulationCancelledException} if + * this thread has been interrupted. The conditions is checked every time a step + * is taken. + * + * @author Sampo Niskanen <sampo.niskanen@iki.fi> + */ +public class InterruptListener extends AbstractSimulationListener { + + public static final InterruptListener INSTANCE = new InterruptListener(); + + @Override + public Collection<FlightEvent> stepTaken(SimulationStatus status) + throws SimulationCancelledException { + + if (Thread.interrupted()) { + throw new SimulationCancelledException("The simulation was interrupted."); + } + + return null; + } +} diff --git a/src/net/sf/openrocket/simulation/listeners/PrintSimulationListener.java b/src/net/sf/openrocket/simulation/listeners/PrintSimulationListener.java new file mode 100644 index 000000000..85717ee2c --- /dev/null +++ b/src/net/sf/openrocket/simulation/listeners/PrintSimulationListener.java @@ -0,0 +1,38 @@ +package net.sf.openrocket.simulation.listeners; + +import java.util.Collection; + +import net.sf.openrocket.simulation.FlightDataBranch; +import net.sf.openrocket.simulation.FlightEvent; +import net.sf.openrocket.simulation.SimulationStatus; + + +public class PrintSimulationListener extends AbstractSimulationListener { + + @Override + public Collection<FlightEvent> handleEvent(FlightEvent event, + SimulationStatus status) { + + System.out.println("*** handleEvent *** "+event.toString() + + " position="+status.position + " velocity="+status.velocity); + return null; + } + + @Override + public Collection<FlightEvent> stepTaken(SimulationStatus status) { + + FlightDataBranch data = status.flightData; + System.out.printf("*** stepTaken *** time=%.3f position="+status.position+ + " velocity="+status.velocity+"=%.3f\n", status.time, status.velocity.length()); + System.out.printf(" thrust=%.3fN drag==%.3fN mass=%.3fkg " + + "accZ=%.3fm/s2 acc=%.3fm/s2\n", + data.getLast(FlightDataBranch.TYPE_THRUST_FORCE), + data.getLast(FlightDataBranch.TYPE_DRAG_FORCE), + data.getLast(FlightDataBranch.TYPE_MASS), + data.getLast(FlightDataBranch.TYPE_ACCELERATION_Z), + data.getLast(FlightDataBranch.TYPE_ACCELERATION_TOTAL)); + + return null; + } + +} diff --git a/src/net/sf/openrocket/simulation/listeners/RollSaveListener.java b/src/net/sf/openrocket/simulation/listeners/RollSaveListener.java new file mode 100644 index 000000000..d80f423ea --- /dev/null +++ b/src/net/sf/openrocket/simulation/listeners/RollSaveListener.java @@ -0,0 +1,159 @@ +package net.sf.openrocket.simulation.listeners; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.PrintStream; +import java.util.Collection; + +import net.sf.openrocket.simulation.FlightDataBranch; +import net.sf.openrocket.simulation.FlightEvent; +import net.sf.openrocket.simulation.SimulationStatus; + + +public class RollSaveListener extends AbstractSimulationListener { + + private static enum Types { + TIME { + @Override + public double getValue(SimulationStatus status) { + return status.time; + } + }, + ALTITUDE { + @Override + public double getValue(SimulationStatus status) { + return status.position.z; + } + }, + VELOCITY_Z { + @Override + public double getValue(SimulationStatus status) { + return status.velocity.z; + } + }, + THETA { + @Override + public double getValue(SimulationStatus status) { + return status.flightData.getLast(FlightDataBranch.TYPE_ORIENTATION_THETA); + } + }, + AOA { + @Override + public double getValue(SimulationStatus status) { + return status.flightData.getLast(FlightDataBranch.TYPE_AOA); + } + }, + ROLLRATE { + @Override + public double getValue(SimulationStatus status) { + return status.flightData.getLast(FlightDataBranch.TYPE_ROLL_RATE); + } + }, + PITCHRATE { + @Override + public double getValue(SimulationStatus status) { + return status.flightData.getLast(FlightDataBranch.TYPE_PITCH_RATE); + } + }, + ROLLMOMENT { + @Override + public double getValue(SimulationStatus status) { + return status.flightData.getLast(FlightDataBranch.TYPE_ROLL_MOMENT_COEFF); + } + }, + MACH { + @Override + public double getValue(SimulationStatus status) { + return status.flightData.getLast(FlightDataBranch.TYPE_MACH_NUMBER); + } + }, + + ; + + public abstract double getValue(SimulationStatus status); + } + + + public static final String FILENAME_FORMAT = "simulation-%03d.csv"; + + private File file; + private PrintStream output = null; + + + + @Override + public Collection<FlightEvent> handleEvent(FlightEvent event, + SimulationStatus status) { + + if (event.getType() == FlightEvent.Type.LAUNCH) { + int n = 1; + + if (output != null) { + System.err.println("WARNING: Ending simulation logging to CSV file " + + "(SIMULATION_END not encountered)."); + output.close(); + output = null; + } + + do { + file = new File(String.format(FILENAME_FORMAT, n)); + n++; + } while (file.exists()); + + System.err.println("Opening file "+file+" for CSV output."); + try { + output = new PrintStream(file); + } catch (FileNotFoundException e) { + System.err.println("ERROR OPENING FILE: "+e); + } + + final Types[] types = Types.values(); + StringBuilder s = new StringBuilder("# " + types[0].toString()); + for (int i=1; i<types.length; i++) { + s.append("," + types[i].toString()); + } + output.println(s); + + } else if (event.getType() == FlightEvent.Type.SIMULATION_END && output != null) { + + System.err.println("Ending simulation logging to CSV file: "+file); + output.close(); + output = null; + + } else if (event.getType() != FlightEvent.Type.ALTITUDE){ + + if (output != null) { + output.println("# Event "+event); + } else { + System.err.println("WARNING: Event "+event+" encountered without open file"); + } + + } + + return null; + } + + + @Override + public Collection<FlightEvent> stepTaken(SimulationStatus status) { + + final Types[] types = Types.values(); + StringBuilder s; + + if (output != null) { + + s = new StringBuilder("" + types[0].getValue(status)); + for (int i=1; i<types.length; i++) { + s.append("," + types[i].getValue(status)); + } + output.println(s); + + } else { + + System.err.println("WARNING: stepTaken called with no open file " + + "(t="+status.time+")"); + } + + return null; + } +} diff --git a/src/net/sf/openrocket/simulation/listeners/StopSimulationListener.java b/src/net/sf/openrocket/simulation/listeners/StopSimulationListener.java new file mode 100644 index 000000000..ece85e2a5 --- /dev/null +++ b/src/net/sf/openrocket/simulation/listeners/StopSimulationListener.java @@ -0,0 +1,59 @@ +package net.sf.openrocket.simulation.listeners; + +import java.util.Collection; +import java.util.Collections; + +import net.sf.openrocket.simulation.FlightEvent; +import net.sf.openrocket.simulation.SimulationStatus; + + +public class StopSimulationListener extends AbstractSimulationListener { + + private final int REPORT = 500; + + private final double stopTime; + private final int stopStep; + + private int step = 0; + + private long startTime = -1; + private long time = -1; + + public StopSimulationListener(double t, int n) { + stopTime = t; + stopStep = n; + } + + + @Override + public Collection<FlightEvent> handleEvent(FlightEvent event, + SimulationStatus status) { + + if (event.getType() == FlightEvent.Type.LAUNCH) { + System.out.println("Simulation starting."); + time = System.nanoTime(); + startTime = System.nanoTime(); + } + + return null; + } + + @Override + public Collection<FlightEvent> stepTaken(SimulationStatus status) { + step ++; + if ((step%REPORT) == 0) { + long t = System.nanoTime(); + + System.out.printf("Step %4d, time=%.3f, took %d us/step (avg. %d us/step)\n", + step,status.time,(t-time)/1000/REPORT,(t-startTime)/1000/step); + time = t; + } + if (status.time >= stopTime || step >= stopStep) { + System.out.printf("Stopping simulation, step=%d time=%.3f\n",step,status.time); + return Collections.singleton(new FlightEvent(FlightEvent.Type.SIMULATION_END, + status.time, null)); + } + return null; + } + +} diff --git a/src/net/sf/openrocket/simulation/listeners/haisu/HaisuCatoListener.java b/src/net/sf/openrocket/simulation/listeners/haisu/HaisuCatoListener.java new file mode 100644 index 000000000..2790d92e8 --- /dev/null +++ b/src/net/sf/openrocket/simulation/listeners/haisu/HaisuCatoListener.java @@ -0,0 +1,38 @@ +package net.sf.openrocket.simulation.listeners.haisu; + +import net.sf.openrocket.aerodynamics.AerodynamicForces; +import net.sf.openrocket.aerodynamics.FlightConditions; +import net.sf.openrocket.simulation.SimulationStatus; +import net.sf.openrocket.simulation.listeners.AbstractSimulationListener; + +public class HaisuCatoListener extends AbstractSimulationListener { + + private static final double POSITION = 0.8; + private static final double CNA = 5.16; + + private final double alpha; + + public HaisuCatoListener(double alpha) { + this.alpha = alpha; + } + + @Override + public void forceCalculation(SimulationStatus status, FlightConditions conditions, + AerodynamicForces forces) { + + double cn = CNA * alpha; + double cm = cn * POSITION / conditions.getRefLength(); + + double theta = conditions.getTheta(); + double costheta = Math.cos(theta); + + forces.CN += cn * costheta; + forces.Cm += cm * costheta; + + if (Math.abs(costheta) < 0.99) { + System.err.println("THETA = "+(theta*180/Math.PI)+ " aborting..."); + System.exit(1); + } + } + +} diff --git a/src/net/sf/openrocket/simulation/listeners/haisu/RollControlListener.java b/src/net/sf/openrocket/simulation/listeners/haisu/RollControlListener.java new file mode 100644 index 000000000..139996aa9 --- /dev/null +++ b/src/net/sf/openrocket/simulation/listeners/haisu/RollControlListener.java @@ -0,0 +1,124 @@ +package net.sf.openrocket.simulation.listeners.haisu; + +import java.util.Collection; + +import net.sf.openrocket.aerodynamics.FlightConditions; +import net.sf.openrocket.rocketcomponent.FinSet; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.simulation.FlightEvent; +import net.sf.openrocket.simulation.SimulationStatus; +import net.sf.openrocket.simulation.listeners.AbstractSimulationListener; +import net.sf.openrocket.util.MathUtil; + + +public class RollControlListener extends AbstractSimulationListener { + + private static final double DELTA_T = 0.01; + private static final double START_TIME = 0.5; + + private static final double MACH = 0.9; + + private static final double SETPOINT = 0.0; + + private static final double TURNRATE = 10 * Math.PI/180; // per second + + + /* + * At M=0.3 KP oscillation threshold between 0.35 and 0.4. Good KI=3 + * At M=0.6 KP oscillation threshold between 0.07 and 0.08 Good KI=2 + * At M=0.9 KP oscillation threshold between 0.013 and 0.014 Good KI=0.5 + */ + private static final double KP = 0.007; + private static final double KI = 0.2; + + + private static final double MAX_ANGLE = 15 * Math.PI/180; + + + + + private double rollrate; + + private double intState = 0; + + private double finPosition = 0; + + + public RollControlListener() { + } + + @Override + public void flightConditions(SimulationStatus status, FlightConditions conditions) { + // Limit movement: + +// conditions.setAOA(0); +// conditions.setTheta(0); +// conditions.setMach(MACH); +// conditions.setPitchRate(0); +// conditions.setYawRate(0); +// status.position = new Coordinate(0,0,100); +// status.velocity = Coordinate.NUL; + + + rollrate = conditions.getRollRate(); + } + + @Override + public Collection<FlightEvent> stepTaken(SimulationStatus status) { + + if (status.time < START_TIME) + return null; + + // PID controller + FinSet finset = null; + for (RocketComponent c: status.configuration) { + if ((c instanceof FinSet) && (c.getName().equals("CONTROL"))) { + finset = (FinSet)c; + break; + } + } + if (finset==null) { + throw new RuntimeException("CONTROL fin not found"); + } + + + double error = SETPOINT - rollrate; + + + error = Math.signum(error) * error * error; //// pow2(error) + + double p = KP * error; + intState += error * DELTA_T; + double i = KI * intState; + + double value = p+i; + + + if (Math.abs(value) > MAX_ANGLE) { + System.err.printf("Attempting to set angle %.1f at t=%.3f, clamping.\n", + value*180/Math.PI, status.time); + value = MathUtil.clamp(value, -MAX_ANGLE, MAX_ANGLE); + } + + + if (finPosition < value) { + finPosition = Math.min(finPosition + TURNRATE*DELTA_T, value); + } else { + finPosition = Math.max(finPosition - TURNRATE*DELTA_T, value); + } + + if (MathUtil.equals(status.time*10, Math.rint(status.time*10))) { + System.err.printf("t=%.3f angle=%.1f current=%.1f\n",status.time, + value*180/Math.PI, finPosition*180/Math.PI); + } + + finset.setCantAngle(finPosition); + + return null; + } + + + + + +} diff --git a/src/net/sf/openrocket/unit/CaliberUnit.java b/src/net/sf/openrocket/unit/CaliberUnit.java new file mode 100644 index 000000000..4d9784c82 --- /dev/null +++ b/src/net/sf/openrocket/unit/CaliberUnit.java @@ -0,0 +1,102 @@ +package net.sf.openrocket.unit; + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; + +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import net.sf.openrocket.rocketcomponent.Configuration; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.rocketcomponent.SymmetricComponent; +import net.sf.openrocket.util.MathUtil; + + +public class CaliberUnit extends GeneralUnit { + + public static final double DEFAULT_CALIBER = 0.01; + + private final Configuration configuration; + private final Rocket rocket; + + private double caliber = -1; + + + /* Listener for rocket and configuration, resets the caliber to -1. */ + private final ChangeListener listener = new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + caliber = -1; + } + }; + + + + public CaliberUnit(Configuration configuration) { + super(1.0, "cal"); + this.configuration = configuration; + + if (configuration == null) { + this.rocket = null; + } else { + this.rocket = configuration.getRocket(); + configuration.addChangeListener(listener); + } + } + + public CaliberUnit(Rocket rocket) { + super(1.0, "cal"); + this.configuration = null; + this.rocket = rocket; + if (rocket != null) { + rocket.addChangeListener(listener); + } + } + + + @Override + public double fromUnit(double value) { + if (caliber < 0) + calculateCaliber(); + + return value * caliber; + } + + @Override + public double toUnit(double value) { + if (caliber < 0) + calculateCaliber(); + + return value / caliber; + } + + + // TODO: HIGH: Check caliber calculation method... + private void calculateCaliber() { + caliber = 0; + + Iterator<RocketComponent> iterator; + if (configuration != null) { + iterator = configuration.iterator(); + } else if (rocket != null) { + iterator = rocket.deepIterator(); + } else { + Collection<RocketComponent> set = Collections.emptyList(); + iterator = set.iterator(); + } + + while (iterator.hasNext()) { + RocketComponent c = iterator.next(); + if (c instanceof SymmetricComponent) { + double r1 = ((SymmetricComponent)c).getForeRadius() * 2; + double r2 = ((SymmetricComponent)c).getAftRadius() * 2; + caliber = MathUtil.max(caliber, r1, r2); + } + } + + if (caliber <= 0) + caliber = DEFAULT_CALIBER; + } +} diff --git a/src/net/sf/openrocket/unit/DegreeUnit.java b/src/net/sf/openrocket/unit/DegreeUnit.java new file mode 100644 index 000000000..e694aa69e --- /dev/null +++ b/src/net/sf/openrocket/unit/DegreeUnit.java @@ -0,0 +1,27 @@ +package net.sf.openrocket.unit; + +import java.text.DecimalFormat; + +public class DegreeUnit extends GeneralUnit { + + public DegreeUnit() { + super(Math.PI/180.0,"\u00b0"); + } + + @Override + public boolean hasSpace() { + return false; + } + + @Override + public double round(double v) { + return Math.rint(v); + } + + private final DecimalFormat decFormat = new DecimalFormat("0.#"); + @Override + public String toString(double value) { + double val = toUnit(value); + return decFormat.format(val); + } +} diff --git a/src/net/sf/openrocket/unit/FixedPrecisionUnit.java b/src/net/sf/openrocket/unit/FixedPrecisionUnit.java new file mode 100644 index 000000000..2946dc707 --- /dev/null +++ b/src/net/sf/openrocket/unit/FixedPrecisionUnit.java @@ -0,0 +1,137 @@ +package net.sf.openrocket.unit; + +import java.util.ArrayList; + +public class FixedPrecisionUnit extends Unit { + + private final double precision; + private final String formatString; + + public FixedPrecisionUnit(String unit, double precision) { + this(unit, precision, 1.0); + } + + public FixedPrecisionUnit(String unit, double precision, double multiplier) { + super(multiplier, unit); + + this.precision = precision; + + int decimals = 0; + double p = precision; + while ((p - Math.floor(p)) > 0.0000001) { + p *= 10; + decimals++; + } + formatString = "%." + decimals + "f"; + } + + + @Override + public double getNextValue(double value) { + return round(value + precision); + } + + @Override + public double getPreviousValue(double value) { + return round(value - precision); + } + + + @Override + public double round(double value) { + return Math.rint(value/precision)*precision; + } + + + + + @Override + public String toString(double value) { + return String.format(formatString, value); + } + + + + // TODO: LOW: This is copied from GeneralUnit, perhaps combine + @Override + public Tick[] getTicks(double start, double end, double minor, double major) { + // Convert values + start = toUnit(start); + end = toUnit(end); + minor = toUnit(minor); + major = toUnit(major); + + if (minor <= 0 || major <= 0 || major < minor) { + throw new IllegalArgumentException("getTicks called with minor="+minor+" major="+major); + } + + ArrayList<Tick> ticks = new ArrayList<Tick>(); + + int mod2,mod3,mod4; // Moduli for minor-notable, major-nonnotable, major-notable + double minstep; + + // Find the smallest possible step size + double one=1; + while (one > minor) + one /= 10; + while (one < minor) + one *= 10; + // one is the smallest round-ten that is larger than minor + if (one/2 >= minor) { + // smallest step is round-five + minstep = one/2; + mod2 = 2; // Changed later if clashes with major ticks + } else { + minstep = one; + mod2 = 10; // Changed later if clashes with major ticks + } + + // Find step size for major ticks + one = 1; + while (one > major) + one /= 10; + while (one < major) + one *= 10; + if (one/2 >= major) { + // major step is round-five, major-notable is next round-ten + double majorstep = one/2; + mod3 = (int)Math.round(majorstep/minstep); + mod4 = mod3*2; + } else { + // major step is round-ten, major-notable is next round-ten + mod3 = (int)Math.round(one/minstep); + mod4 = mod3*10; + } + // Check for clashes between minor-notable and major-nonnotable + if (mod3 == mod2) { + if (mod2==2) + mod2 = 1; // Every minor tick is notable + else + mod2 = 5; // Every fifth minor tick is notable + } + + + // Calculate starting position + int pos = (int)Math.ceil(start/minstep); +// System.out.println("mod2="+mod2+" mod3="+mod3+" mod4="+mod4); + while (pos*minstep <= end) { + double unitValue = pos*minstep; + double value = fromUnit(unitValue); + + if (pos%mod4 == 0) + ticks.add(new Tick(value,unitValue,true,true)); + else if (pos%mod3 == 0) + ticks.add(new Tick(value,unitValue,true,false)); + else if (pos%mod2 == 0) + ticks.add(new Tick(value,unitValue,false,true)); + else + ticks.add(new Tick(value,unitValue,false,false)); + + pos++; + } + + return ticks.toArray(new Tick[0]); + } + + +} diff --git a/src/net/sf/openrocket/unit/GeneralUnit.java b/src/net/sf/openrocket/unit/GeneralUnit.java new file mode 100644 index 000000000..d604fe6e1 --- /dev/null +++ b/src/net/sf/openrocket/unit/GeneralUnit.java @@ -0,0 +1,228 @@ +package net.sf.openrocket.unit; + +import java.util.ArrayList; + +public class GeneralUnit extends Unit { + + private final int significantNumbers; + private final int decimalRounding; + + // Values smaller that this are rounded using decimal rounding + // [pre-calculated as 10^(significantNumbers-1)] + private final double decimalLimit; + + // Pre-calculated as 10^significantNumbers + private final double significantNumbersLimit; + + + public GeneralUnit(double multiplier, String unit) { + this(multiplier, unit, 2, 10); + } + + public GeneralUnit(double multiplier, String unit, int significantNumbers) { + this(multiplier, unit, significantNumbers, 10); + } + + public GeneralUnit(double multiplier, String unit, int significantNumbers, int decimalRounding) { + super(multiplier, unit); + assert(significantNumbers>0); + assert(decimalRounding>0); + + this.significantNumbers = significantNumbers; + this.decimalRounding = decimalRounding; + + double d=1; + double e=10; + for (int i=1; i<significantNumbers; i++) { + d *= 10.0; + e *= 10.0; + } + decimalLimit = d; + significantNumbersLimit = e; + } + + @Override + public double round(double value) { + if (value < decimalLimit) { + // Round to closest 1/decimalRounding + return Math.rint(value*decimalRounding)/decimalRounding; + } else { + // Round to given amount of significant numbers + double m = 1; + while (value >= significantNumbersLimit) { + m *= 10.0; + value /= 10.0; + } + return Math.rint(value)*m; + } + } + + + + + // TODO: LOW: untested + // start, end and scale in this units +// @Override + public ArrayList<Tick> getTicks(double start, double end, double scale) { + ArrayList<Tick> ticks = new ArrayList<Tick>(); + double delta; + int normal, major; + + // TODO: LOW: more fine-grained (e.g. 0||||5||||10||||15||||20) + if (scale <= 1.0/decimalRounding) { + delta = 1.0/decimalRounding; + normal = 1; + major = decimalRounding; + } else if (scale <= 1.0) { + delta = 1.0/decimalRounding; + normal = decimalRounding; + major = decimalRounding*10; + } else { + double r = scale; + delta = 1; + while (r > 10) { + r /= 10; + delta *= 10; + } + normal = 10; + major = 100; // TODO: LOW: More fine-grained with 5 + } + + double v = Math.ceil(start/delta)*delta; + int n = (int)Math.round(v/delta); + +// while (v <= end) { +// if (n%major == 0) +// ticks.add(new Tick(v,Tick.MAJOR)); +// else if (n%normal == 0) +// ticks.add(new Tick(v,Tick.NORMAL)); +// else +// ticks.add(new Tick(v,Tick.MINOR)); +// v += delta; +// n++; +// } + + return ticks; + } + + @Override + public Tick[] getTicks(double start, double end, double minor, double major) { + // Convert values + start = toUnit(start); + end = toUnit(end); + minor = toUnit(minor); + major = toUnit(major); + + if (minor <= 0 || major <= 0 || major < minor) { + throw new IllegalArgumentException("getTicks called with minor="+minor+" major="+major); + } + + ArrayList<Tick> ticks = new ArrayList<Tick>(); + + int mod2,mod3,mod4; // Moduli for minor-notable, major-nonnotable, major-notable + double minstep; + + // Find the smallest possible step size + double one=1; + while (one > minor) + one /= 10; + while (one < minor) + one *= 10; + // one is the smallest round-ten that is larger than minor + if (one/2 >= minor) { + // smallest step is round-five + minstep = one/2; + mod2 = 2; // Changed later if clashes with major ticks + } else { + minstep = one; + mod2 = 10; // Changed later if clashes with major ticks + } + + // Find step size for major ticks + one = 1; + while (one > major) + one /= 10; + while (one < major) + one *= 10; + if (one/2 >= major) { + // major step is round-five, major-notable is next round-ten + double majorstep = one/2; + mod3 = (int)Math.round(majorstep/minstep); + mod4 = mod3*2; + } else { + // major step is round-ten, major-notable is next round-ten + mod3 = (int)Math.round(one/minstep); + mod4 = mod3*10; + } + // Check for clashes between minor-notable and major-nonnotable + if (mod3 == mod2) { + if (mod2==2) + mod2 = 1; // Every minor tick is notable + else + mod2 = 5; // Every fifth minor tick is notable + } + + + // Calculate starting position + int pos = (int)Math.ceil(start/minstep); +// System.out.println("mod2="+mod2+" mod3="+mod3+" mod4="+mod4); + while (pos*minstep <= end) { + double unitValue = pos*minstep; + double value = fromUnit(unitValue); + + if (pos%mod4 == 0) + ticks.add(new Tick(value,unitValue,true,true)); + else if (pos%mod3 == 0) + ticks.add(new Tick(value,unitValue,true,false)); + else if (pos%mod2 == 0) + ticks.add(new Tick(value,unitValue,false,true)); + else + ticks.add(new Tick(value,unitValue,false,false)); + + pos++; + } + + return ticks.toArray(new Tick[0]); + } + + + @Override + public double getNextValue(double value) { + // TODO: HIGH: Auto-generated method stub + return value+1; + } + + @Override + public double getPreviousValue(double value) { + // TODO: HIGH: Auto-generated method stub + return value-1; + } + + + ///// TESTING: + + private static void printTicks(double start, double end, double minor, double major) { + Tick[] ticks = Unit.NOUNIT2.getTicks(start, end, minor, major); + String str = "Ticks for ("+start+","+end+","+minor+","+major+"):"; + for (int i=0; i<ticks.length; i++) { + str += " "+ticks[i].value; + if (ticks[i].major) { + if (ticks[i].notable) + str += "*"; + else + str += "o"; + } else { + if (ticks[i].notable) + str += "_"; + else + str += " "; + } + } + System.out.println(str); + } + public static void main(String[] arg) { + printTicks(0,100,1,10); + printTicks(4.7,11.0,0.15,0.7); + } + +} diff --git a/src/net/sf/openrocket/unit/RadianUnit.java b/src/net/sf/openrocket/unit/RadianUnit.java new file mode 100644 index 000000000..18f1aff62 --- /dev/null +++ b/src/net/sf/openrocket/unit/RadianUnit.java @@ -0,0 +1,22 @@ +package net.sf.openrocket.unit; + +import java.text.DecimalFormat; + +public class RadianUnit extends GeneralUnit { + + public RadianUnit() { + super(1,"rad"); + } + + @Override + public double round(double v) { + return Math.rint(v*10.0)/10.0; + } + + private final DecimalFormat decFormat = new DecimalFormat("0.0"); + @Override + public String toString(double value) { + double val = toUnit(value); + return decFormat.format(val); + } +} diff --git a/src/net/sf/openrocket/unit/TemperatureUnit.java b/src/net/sf/openrocket/unit/TemperatureUnit.java new file mode 100644 index 000000000..23645ceb4 --- /dev/null +++ b/src/net/sf/openrocket/unit/TemperatureUnit.java @@ -0,0 +1,27 @@ +package net.sf.openrocket.unit; + +public class TemperatureUnit extends FixedPrecisionUnit { + + protected final double addition; + + public TemperatureUnit(double multiplier, double addition, String unit) { + super(unit, 1, multiplier); + + this.addition = addition; + } + + @Override + public boolean hasSpace() { + return false; + } + + @Override + public double toUnit(double value) { + return value/multiplier - addition; + } + + @Override + public double fromUnit(double value) { + return (value + addition)*multiplier; + } +} diff --git a/src/net/sf/openrocket/unit/Tick.java b/src/net/sf/openrocket/unit/Tick.java new file mode 100644 index 000000000..4448aac13 --- /dev/null +++ b/src/net/sf/openrocket/unit/Tick.java @@ -0,0 +1,28 @@ +package net.sf.openrocket.unit; + +public final class Tick { + public final double value; + public final double unitValue; + public final boolean major; + public final boolean notable; + + public Tick(double value, double unitValue, boolean major, boolean notable) { + this.value = value; + this.unitValue = unitValue; + this.major = major; + this.notable = notable; + } + + @Override + public String toString() { + String s = "Tick[value="+value; + if (major) + s += ",major"; + else + s += ",minor"; + if (notable) + s += ",notable"; + s+= "]"; + return s; + } +} diff --git a/src/net/sf/openrocket/unit/Unit.java b/src/net/sf/openrocket/unit/Unit.java new file mode 100644 index 000000000..3686bedc3 --- /dev/null +++ b/src/net/sf/openrocket/unit/Unit.java @@ -0,0 +1,223 @@ +package net.sf.openrocket.unit; + +import java.text.DecimalFormat; + +public abstract class Unit { + + /** No unit with 2 digit precision */ + public static final Unit NOUNIT2 = new GeneralUnit(1,"\u200b", 2); // zero-width space + + protected final double multiplier; // meters = units * multiplier + protected final String unit; + + /** + * Creates a new Unit with a given multiplier and unit name. + * + * Multiplier e.g. 1 in = 0.0254 meter + * + * @param multiplier The multiplier to use on the value, 1 this unit == multiplier SI units + * @param unit The unit's short form. + */ + public Unit(double multiplier, String unit) { + if (multiplier == 0) + throw new IllegalArgumentException("Unit has multiplier=0"); + this.multiplier = multiplier; + this.unit = unit; + } + + /** + * Converts from SI units to this unit. The default implementation simply divides by the + * multiplier. + * + * @param value Value in SI unit + * @return Value in these units + */ + public double toUnit(double value) { + return value/multiplier; + } + + /** + * Convert from this type of units to SI units. The default implementation simply + * multiplies by the multiplier. + * + * @param value Value in these units + * @return Value in SI units + */ + public double fromUnit(double value) { + return value*multiplier; + } + + + /** + * Return the unit name. + * + * @return the unit. + */ + public String getUnit() { + return unit; + } + + /** + * Whether the value and unit should be separated by a whitespace. This method + * returns true as most units have a space between the value and unit, but may be + * overridden. + * + * @return true if the value and unit should be separated + */ + public boolean hasSpace() { + return true; + } + + + // Testcases for toString(double) + public static void main(String arg[]) { + System.out.println(NOUNIT2.toString(0.0049)); + System.out.println(NOUNIT2.toString(0.0050)); + System.out.println(NOUNIT2.toString(0.0051)); + System.out.println(NOUNIT2.toString(0.00123)); + System.out.println(NOUNIT2.toString(0.0123)); + System.out.println(NOUNIT2.toString(0.1234)); + System.out.println(NOUNIT2.toString(1.2345)); + System.out.println(NOUNIT2.toString(12.345)); + System.out.println(NOUNIT2.toString(123.456)); + System.out.println(NOUNIT2.toString(1234.5678)); + System.out.println(NOUNIT2.toString(12345.6789)); + System.out.println(NOUNIT2.toString(123456.789)); + System.out.println(NOUNIT2.toString(1234567.89)); + System.out.println(NOUNIT2.toString(12345678.9)); + + System.out.println(NOUNIT2.toString(-0.0049)); + System.out.println(NOUNIT2.toString(-0.0050)); + System.out.println(NOUNIT2.toString(-0.0051)); + System.out.println(NOUNIT2.toString(-0.00123)); + System.out.println(NOUNIT2.toString(-0.0123)); + System.out.println(NOUNIT2.toString(-0.1234)); + System.out.println(NOUNIT2.toString(-1.2345)); + System.out.println(NOUNIT2.toString(-12.345)); + System.out.println(NOUNIT2.toString(-123.456)); + System.out.println(NOUNIT2.toString(-1234.5678)); + System.out.println(NOUNIT2.toString(-12345.6789)); + System.out.println(NOUNIT2.toString(-123456.789)); + System.out.println(NOUNIT2.toString(-1234567.89)); + System.out.println(NOUNIT2.toString(-12345678.9)); + + } + + + @Override + public String toString() { + return unit; + } + + private static final DecimalFormat intFormat = new DecimalFormat("#"); + private static final DecimalFormat decFormat = new DecimalFormat("0.##"); + private static final DecimalFormat expFormat = new DecimalFormat("0.00E0"); + + /** + * Format the given value (in SI units) to a string representation of the value in this + * units. An suitable amount of decimals for the unit are used in the representation. + * The unit is not appended to the numerical value. + * + * @param value Value in SI units. + * @return A string representation of the number in these units. + */ + public String toString(double value) { + double val = toUnit(value); + + if (Math.abs(val) > 1E6) { + return expFormat.format(val); + } + if (Math.abs(val) >= 100) { + return intFormat.format(val); + } + if (Math.abs(val) <= 0.005) { + return "0"; + } + + double sign = Math.signum(val); + val = Math.abs(val); + double mul = 1.0; + while (val < 100) { + mul *= 10; + val *= 10; + } + val = Math.rint(val)/mul * sign; + + return decFormat.format(val); + } + + + /** + * Return a string with the specified value and unit. The value is converted into + * this unit. If <code>value</code> is NaN, returns "N/A" (not applicable). + * + * @param value the value to print in SI units. + * @return the value and unit, or "N/A". + */ + public String toStringUnit(double value) { + if (Double.isNaN(value)) + return "N/A"; + + String s = toString(value); + if (hasSpace()) + s += " "; + s += unit; + return s; + } + + + + + /** + * Round the value (in the current units) to a precision suitable for rough valuing + * (approximately 2 significant numbers). + * + * @param value Value in current units + * @return Rounded value. + */ + public abstract double round(double value); + + /** + * Return the next rounded value after the given value. + * @param value Value in these units. + * @return The next suitable rounded value. + */ + public abstract double getNextValue(double value); + + /** + * Return the previous rounded value before the given value. + * @param value Value in these units. + * @return The previous suitable rounded value. + */ + public abstract double getPreviousValue(double value); + + //public abstract ArrayList<Tick> getTicks(double start, double end, double scale); + + /** + * Return ticks in the range start - end (in current units). minor is the minimum + * distance between minor, non-notable ticks and major the minimum distance between + * major non-notable ticks. The values are in current units, i.e. no conversion is + * performed. + */ + public abstract Tick[] getTicks(double start, double end, double minor, double major); + + /** + * Compares whether the two units are equal. Equality requires the unit classes, + * multiplier values and units to be equal. + */ + @Override + public boolean equals(Object other) { + if (other == null) + return false; + if (this.getClass() != other.getClass()) + return false; + return ((this.multiplier == ((Unit)other).multiplier) && + this.unit.equals(((Unit)other).unit)); + } + + @Override + public int hashCode() { + return this.getClass().hashCode() + this.unit.hashCode(); + } + +} diff --git a/src/net/sf/openrocket/unit/UnitGroup.java b/src/net/sf/openrocket/unit/UnitGroup.java new file mode 100644 index 000000000..091a25e69 --- /dev/null +++ b/src/net/sf/openrocket/unit/UnitGroup.java @@ -0,0 +1,525 @@ +package net.sf.openrocket.unit; + +import static net.sf.openrocket.util.MathUtil.pow2; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import net.sf.openrocket.rocketcomponent.Configuration; +import net.sf.openrocket.rocketcomponent.Rocket; + + +/** + * A group of units (eg. length, mass etc.). Contains a list of different units of a same + * quantity. + * + * @author Sampo Niskanen <sampo.niskanen@iki.fi> + */ + +public class UnitGroup { + + public static final UnitGroup UNITS_NONE; + + public static final UnitGroup UNITS_MOTOR_DIMENSIONS; + public static final UnitGroup UNITS_LENGTH; + public static final UnitGroup UNITS_DISTANCE; + + public static final UnitGroup UNITS_AREA; + public static final UnitGroup UNITS_STABILITY; + public static final UnitGroup UNITS_VELOCITY; + public static final UnitGroup UNITS_ACCELERATION; + public static final UnitGroup UNITS_MASS; + public static final UnitGroup UNITS_ANGLE; + public static final UnitGroup UNITS_DENSITY_BULK; + public static final UnitGroup UNITS_DENSITY_SURFACE; + public static final UnitGroup UNITS_DENSITY_LINE; + public static final UnitGroup UNITS_FORCE; + public static final UnitGroup UNITS_IMPULSE; + + /** Time in the order of less than a second (time step etc). */ + public static final UnitGroup UNITS_TIME_STEP; + + /** Time in the order of seconds (motor delay etc). */ + public static final UnitGroup UNITS_SHORT_TIME; + + /** Time in the order of the flight time of a rocket. */ + public static final UnitGroup UNITS_FLIGHT_TIME; + public static final UnitGroup UNITS_ROLL; + public static final UnitGroup UNITS_TEMPERATURE; + public static final UnitGroup UNITS_PRESSURE; + public static final UnitGroup UNITS_RELATIVE; + public static final UnitGroup UNITS_ROUGHNESS; + + public static final UnitGroup UNITS_COEFFICIENT; + + + public static final Map<String, UnitGroup> UNITS; + + + /* + * Note: Units may not use HTML tags. + */ + static { + UNITS_NONE = new UnitGroup(); + UNITS_NONE.addUnit(Unit.NOUNIT2); + + UNITS_LENGTH = new UnitGroup(); + UNITS_LENGTH.addUnit(new GeneralUnit(0.001,"mm")); + UNITS_LENGTH.addUnit(new GeneralUnit(0.01,"cm")); + UNITS_LENGTH.addUnit(new GeneralUnit(1,"m")); + UNITS_LENGTH.addUnit(new GeneralUnit(0.0254,"in")); + UNITS_LENGTH.addUnit(new GeneralUnit(0.3048,"ft")); + UNITS_LENGTH.setDefaultUnit(1); + + UNITS_MOTOR_DIMENSIONS = new UnitGroup(); + UNITS_MOTOR_DIMENSIONS.addUnit(new GeneralUnit(0.001,"mm")); + UNITS_MOTOR_DIMENSIONS.addUnit(new GeneralUnit(0.01,"cm")); + UNITS_MOTOR_DIMENSIONS.addUnit(new GeneralUnit(0.0254,"in")); + UNITS_MOTOR_DIMENSIONS.setDefaultUnit(0); + + UNITS_DISTANCE = new UnitGroup(); + UNITS_DISTANCE.addUnit(new GeneralUnit(1,"m")); + UNITS_DISTANCE.addUnit(new GeneralUnit(1000,"km")); + UNITS_DISTANCE.addUnit(new GeneralUnit(0.3048,"ft")); + UNITS_DISTANCE.addUnit(new GeneralUnit(0.9144,"yd")); + UNITS_DISTANCE.addUnit(new GeneralUnit(1609.344,"mi")); + + UNITS_AREA = new UnitGroup(); + UNITS_AREA.addUnit(new GeneralUnit(pow2(0.001),"mm\u00b2")); + UNITS_AREA.addUnit(new GeneralUnit(pow2(0.01),"cm\u00b2")); + UNITS_AREA.addUnit(new GeneralUnit(1,"m\u00b2")); + UNITS_AREA.addUnit(new GeneralUnit(pow2(0.0254),"in\u00b2")); + UNITS_AREA.addUnit(new GeneralUnit(pow2(0.3048),"ft\u00b2")); + UNITS_AREA.setDefaultUnit(1); + + + UNITS_STABILITY = new UnitGroup(); + UNITS_STABILITY.addUnit(new GeneralUnit(0.001,"mm")); + UNITS_STABILITY.addUnit(new GeneralUnit(0.01,"cm")); + UNITS_STABILITY.addUnit(new GeneralUnit(0.0254,"in")); + UNITS_STABILITY.addUnit(new CaliberUnit((Rocket)null)); + UNITS_STABILITY.setDefaultUnit(3); + + UNITS_VELOCITY = new UnitGroup(); + UNITS_VELOCITY.addUnit(new GeneralUnit(1, "m/s")); + UNITS_VELOCITY.addUnit(new GeneralUnit(1/3.6, "km/h")); + UNITS_VELOCITY.addUnit(new GeneralUnit(1/0.3048, "ft/s")); + UNITS_VELOCITY.addUnit(new GeneralUnit(0.44704, "mph")); + + UNITS_ACCELERATION = new UnitGroup(); + UNITS_ACCELERATION.addUnit(new GeneralUnit(1, "m/s\u00b2")); + UNITS_ACCELERATION.addUnit(new GeneralUnit(1/0.3048, "ft/s\00b2")); + + + UNITS_MASS = new UnitGroup(); + UNITS_MASS.addUnit(new GeneralUnit(0.001,"g")); + UNITS_MASS.addUnit(new GeneralUnit(1,"kg")); + UNITS_MASS.addUnit(new GeneralUnit(0.0283495,"oz")); + UNITS_MASS.addUnit(new GeneralUnit(0.0453592,"lb")); + + UNITS_ANGLE = new UnitGroup(); + UNITS_ANGLE.addUnit(new DegreeUnit()); + UNITS_ANGLE.addUnit(new FixedPrecisionUnit("rad",0.01)); + + UNITS_DENSITY_BULK = new UnitGroup(); + UNITS_DENSITY_BULK.addUnit(new GeneralUnit(1000,"g/cm\u00b3")); + UNITS_DENSITY_BULK.addUnit(new GeneralUnit(1,"kg/m\u00b3")); + UNITS_DENSITY_BULK.addUnit(new GeneralUnit(1729.004,"oz/in\u00b3")); + UNITS_DENSITY_BULK.addUnit(new GeneralUnit(16.01846,"lb/ft\u00b3")); + + UNITS_DENSITY_SURFACE = new UnitGroup(); + UNITS_DENSITY_SURFACE.addUnit(new GeneralUnit(10,"g/cm\u00b2")); + UNITS_DENSITY_SURFACE.addUnit(new GeneralUnit(0.001,"g/m\u00b2")); + UNITS_DENSITY_SURFACE.addUnit(new GeneralUnit(1,"kg/m\u00b2")); + UNITS_DENSITY_SURFACE.addUnit(new GeneralUnit(43.9418,"oz/in\u00b2")); + UNITS_DENSITY_SURFACE.addUnit(new GeneralUnit(0.30515173,"oz/ft\u00b2")); + UNITS_DENSITY_SURFACE.addUnit(new GeneralUnit(4.88243,"lb/ft\u00b2")); + UNITS_DENSITY_SURFACE.setDefaultUnit(1); + + UNITS_DENSITY_LINE = new UnitGroup(); + UNITS_DENSITY_LINE.addUnit(new GeneralUnit(0.001,"g/m")); + UNITS_DENSITY_LINE.addUnit(new GeneralUnit(1,"kg/m")); + UNITS_DENSITY_LINE.addUnit(new GeneralUnit(0.0930102,"oz/ft")); + + UNITS_FORCE = new UnitGroup(); + UNITS_FORCE.addUnit(new GeneralUnit(1,"N")); + UNITS_FORCE.addUnit(new GeneralUnit(4.448222,"lbf")); + UNITS_FORCE.addUnit(new GeneralUnit(9.80665,"kgf")); + + UNITS_IMPULSE = new UnitGroup(); + UNITS_IMPULSE.addUnit(new GeneralUnit(1,"Ns")); + UNITS_IMPULSE.addUnit(new GeneralUnit(4.448222, "lbf\u00b7s")); + + UNITS_TIME_STEP = new UnitGroup(); + UNITS_TIME_STEP.addUnit(new FixedPrecisionUnit("ms", 1, 0.001)); + UNITS_TIME_STEP.addUnit(new FixedPrecisionUnit("s", 0.01)); + UNITS_TIME_STEP.setDefaultUnit(1); + + UNITS_SHORT_TIME = new UnitGroup(); + UNITS_SHORT_TIME.addUnit(new GeneralUnit(1,"s")); + + UNITS_FLIGHT_TIME = new UnitGroup(); + UNITS_FLIGHT_TIME.addUnit(new GeneralUnit(1,"s")); + UNITS_FLIGHT_TIME.addUnit(new GeneralUnit(60,"min")); + + UNITS_ROLL = new UnitGroup(); + UNITS_ROLL.addUnit(new GeneralUnit(1, "rad/s")); + UNITS_ROLL.addUnit(new GeneralUnit(2*Math.PI, "r/s")); + UNITS_ROLL.addUnit(new GeneralUnit(2*Math.PI/60, "rpm")); + UNITS_ROLL.setDefaultUnit(1); + + UNITS_TEMPERATURE = new UnitGroup(); + UNITS_TEMPERATURE.addUnit(new FixedPrecisionUnit("K", 1)); + UNITS_TEMPERATURE.addUnit(new TemperatureUnit(1, 273.15, "\u00b0C")); + UNITS_TEMPERATURE.addUnit(new TemperatureUnit(5.0/9.0, 459.67, "\u00b0F")); + UNITS_TEMPERATURE.setDefaultUnit(1); + + UNITS_PRESSURE = new UnitGroup(); + UNITS_PRESSURE.addUnit(new FixedPrecisionUnit("mbar", 1, 1.0e2)); + UNITS_PRESSURE.addUnit(new FixedPrecisionUnit("bar", 0.001, 1.0e5)); + UNITS_PRESSURE.addUnit(new FixedPrecisionUnit("atm", 0.001, 1.01325e5)); + UNITS_PRESSURE.addUnit(new GeneralUnit(133.322, "mmHg")); + UNITS_PRESSURE.addUnit(new GeneralUnit(3386.389, "inHg")); + UNITS_PRESSURE.addUnit(new GeneralUnit(6894.757, "psi")); + UNITS_PRESSURE.addUnit(new GeneralUnit(1, "Pa")); + + UNITS_RELATIVE = new UnitGroup(); + UNITS_RELATIVE.addUnit(new FixedPrecisionUnit("\u200b", 0.01)); + UNITS_RELATIVE.addUnit(new FixedPrecisionUnit("%", 1, 0.01)); + UNITS_RELATIVE.setDefaultUnit(1); + + + UNITS_ROUGHNESS = new UnitGroup(); + UNITS_ROUGHNESS.addUnit(new GeneralUnit(0.000001, "\u03bcm")); + UNITS_ROUGHNESS.addUnit(new GeneralUnit(0.0000254, "mil")); + + + UNITS_COEFFICIENT = new UnitGroup(); + UNITS_COEFFICIENT.addUnit(new FixedPrecisionUnit("\u200b", 0.01)); // zero-width space + + + HashMap<String,UnitGroup> map = new HashMap<String,UnitGroup>(); + map.put("NONE", UNITS_NONE); + map.put("LENGTH", UNITS_LENGTH); + map.put("MOTOR_DIMENSIONS", UNITS_MOTOR_DIMENSIONS); + map.put("DISTANCE", UNITS_DISTANCE); + map.put("VELOCITY", UNITS_VELOCITY); + map.put("ACCELERATION", UNITS_ACCELERATION); + map.put("AREA", UNITS_AREA); + map.put("STABILITY", UNITS_STABILITY); + map.put("MASS", UNITS_MASS); + map.put("ANGLE", UNITS_ANGLE); + map.put("DENSITY_BULK", UNITS_DENSITY_BULK); + map.put("DENSITY_SURFACE", UNITS_DENSITY_SURFACE); + map.put("DENSITY_LINE", UNITS_DENSITY_LINE); + map.put("FORCE", UNITS_FORCE); + map.put("IMPULSE", UNITS_IMPULSE); + map.put("TIME_STEP", UNITS_TIME_STEP); + map.put("SHORT_TIME", UNITS_SHORT_TIME); + map.put("FLIGHT_TIME", UNITS_FLIGHT_TIME); + map.put("ROLL", UNITS_ROLL); + map.put("TEMPERATURE", UNITS_TEMPERATURE); + map.put("PRESSURE", UNITS_PRESSURE); + map.put("RELATIVE", UNITS_RELATIVE); + map.put("ROUGHNESS", UNITS_ROUGHNESS); + map.put("COEFFICIENT", UNITS_COEFFICIENT); + + UNITS = Collections.unmodifiableMap(map); + } + + public static void setDefaultMetricUnits() { + UNITS_LENGTH.setDefaultUnit("cm"); + UNITS_MOTOR_DIMENSIONS.setDefaultUnit("mm"); + UNITS_DISTANCE.setDefaultUnit("m"); + UNITS_AREA.setDefaultUnit("cm\u00b2"); + UNITS_STABILITY.setDefaultUnit("cal"); + UNITS_VELOCITY.setDefaultUnit("m/s"); + UNITS_ACCELERATION.setDefaultUnit("m/s\u00b2"); + UNITS_MASS.setDefaultUnit("g"); + UNITS_ANGLE.setDefaultUnit(0); + UNITS_DENSITY_BULK.setDefaultUnit("g/cm\u00b3"); + UNITS_DENSITY_SURFACE.setDefaultUnit("g/m\u00b2"); + UNITS_DENSITY_LINE.setDefaultUnit("g/m"); + UNITS_FORCE.setDefaultUnit("N"); + UNITS_IMPULSE.setDefaultUnit("Ns"); + UNITS_TIME_STEP.setDefaultUnit("s"); + UNITS_FLIGHT_TIME.setDefaultUnit("s"); + UNITS_ROLL.setDefaultUnit("r/s"); + UNITS_TEMPERATURE.setDefaultUnit(1); + UNITS_PRESSURE.setDefaultUnit("mbar"); + UNITS_RELATIVE.setDefaultUnit("%"); + UNITS_ROUGHNESS.setDefaultUnit("\u03bcm"); + } + + public static void setDefaultImperialUnits() { + UNITS_LENGTH.setDefaultUnit("in"); + UNITS_MOTOR_DIMENSIONS.setDefaultUnit("in"); + UNITS_DISTANCE.setDefaultUnit("ft"); + UNITS_AREA.setDefaultUnit("in\u00b2"); + UNITS_STABILITY.setDefaultUnit("cal"); + UNITS_VELOCITY.setDefaultUnit("ft/s"); + UNITS_ACCELERATION.setDefaultUnit("ft/s\u00b2"); + UNITS_MASS.setDefaultUnit("oz"); + UNITS_ANGLE.setDefaultUnit(0); + UNITS_DENSITY_BULK.setDefaultUnit("oz/in\u00b3"); + UNITS_DENSITY_SURFACE.setDefaultUnit("oz/ft\u00b2"); + UNITS_DENSITY_LINE.setDefaultUnit("oz/ft"); + UNITS_FORCE.setDefaultUnit("N"); + UNITS_IMPULSE.setDefaultUnit("Ns"); + UNITS_TIME_STEP.setDefaultUnit("s"); + UNITS_FLIGHT_TIME.setDefaultUnit("s"); + UNITS_ROLL.setDefaultUnit("r/s"); + UNITS_TEMPERATURE.setDefaultUnit(2); + UNITS_PRESSURE.setDefaultUnit("mbar"); + UNITS_RELATIVE.setDefaultUnit("%"); + UNITS_ROUGHNESS.setDefaultUnit("mil"); + } + + + + public static UnitGroup stabilityUnits(Rocket rocket) { + return new StabilityUnitGroup(rocket); + } + + + public static UnitGroup stabilityUnits(Configuration config) { + return new StabilityUnitGroup(config); + } + + + ////////////////////////////////////////////////////// + + + private ArrayList<Unit> units = new ArrayList<Unit>(); + private int defaultUnit = 0; + + public int getUnitCount() { + return units.size(); + } + + public Unit getDefaultUnit() { + return units.get(defaultUnit); + } + + public int getDefaultUnitIndex() { + return defaultUnit; + } + + public void setDefaultUnit(int n) { + if (n<0 || n>=units.size()) { + throw new IllegalArgumentException("index out of range: "+n); + } + defaultUnit = n; + } + + /** + * Set the default unit based on the unit name. Does nothing if the name + * does not match any of the units. + * + * @param name the unit name (<code>null</code> ok). + * @return <code>true</code> if the the default was set, + * <code>false</code> if a matching unit was not found. + */ + public boolean setDefaultUnit(String name) { + if (name == null) + return false; + + for (int i=0; i < units.size(); i++) { + if (name.equals(units.get(i).getUnit())) { + setDefaultUnit(i); + return true; + } + } + return false; + } + + + public Unit getUnit(int n) { + return units.get(n); + } + + public int getUnitIndex(Unit u) { + return units.indexOf(u); + } + + public void addUnit(Unit u) { + units.add(u); + } + + public void addUnit(int n, Unit u) { + units.add(n,u); + } + + public void removeUnit(int n) { + units.remove(n); + } + + public boolean contains(Unit u) { + return units.contains(u); + } + + public Unit[] getUnits() { + return units.toArray(new Unit[0]); + } + + + /** + * Return the value formatted by the default unit of this group. + * It is the same as calling <code>getDefaultUnit().toString(value)</code>. + * + * @param value the SI value to format. + * @return the formatted string. + * @see Unit#toString(double) + */ + public String toString(double value) { + return this.getDefaultUnit().toString(value); + } + + + /** + * Return the value formatted by the default unit of this group including the unit. + * It is the same as calling <code>getDefaultUnit().toStringUnit(value)</code>. + * + * @param value the SI value to format. + * @return the formatted string. + * @see Unit#toStringUnit(double) + */ + public String toStringUnit(double value) { + return this.getDefaultUnit().toStringUnit(value); + } + + + private static final Pattern STRING_PATTERN = Pattern.compile("^\\s*([0-9.,-]+)(.*?)$"); + /** + * Converts a string into an SI value. If the string has one of the units in this + * group appended to it, that unit will be used in conversion. Otherwise the default + * unit will be used. If an unknown unit is specified or the value does not parse + * with <code>Double.parseDouble</code> then a <code>NumberFormatException</code> + * is thrown. + * <p> + * This method is applicable only for simple units without e.g. powers. + * + * @param str the string to parse. + * @return the SI value. + * @throws NumberFormatException if the string cannot be parsed. + */ + public double fromString(String str) { + Matcher matcher = STRING_PATTERN.matcher(str); + + if (!matcher.matches()) { + throw new NumberFormatException("string did not match required pattern"); + } + + double value = Double.parseDouble(matcher.group(1)); + String unit = matcher.group(2).trim(); + + if (unit.equals("")) { + value = this.getDefaultUnit().fromUnit(value); + } else { + int i; + for (i=0; i < units.size(); i++) { + Unit u = units.get(i); + if (unit.equalsIgnoreCase(u.getUnit())) { + value = u.fromUnit(value); + break; + } + } + if (i >= units.size()) { + throw new NumberFormatException("unknown unit "+unit); + } + } + + return value; + } + + + /////////////////////////// + + + /** + * A private class that switches the CaliberUnit to a rocket-specific CaliberUnit. + * All other methods are passed through to UNITS_STABILITY. + */ + private static class StabilityUnitGroup extends UnitGroup { + + private final CaliberUnit caliberUnit; + + + public StabilityUnitGroup(Rocket rocket) { + caliberUnit = new CaliberUnit(rocket); + } + + public StabilityUnitGroup(Configuration config) { + caliberUnit = new CaliberUnit(config); + } + + + //// Modify CaliberUnit to use local variable + + @Override + public Unit getDefaultUnit() { + return getUnit(UNITS_STABILITY.getDefaultUnitIndex()); + } + + @Override + public Unit getUnit(int n) { + Unit u = UNITS_STABILITY.getUnit(n); + if (u instanceof CaliberUnit) { + return caliberUnit; + } + return u; + } + + @Override + public int getUnitIndex(Unit u) { + if (u instanceof CaliberUnit) { + for (int i=0; i < UNITS_STABILITY.getUnitCount(); i++) { + if (UNITS_STABILITY.getUnit(i) instanceof CaliberUnit) + return i; + } + } + return UNITS_STABILITY.getUnitIndex(u); + } + + + + //// Pass on to UNITS_STABILITY + + @Override + public int getDefaultUnitIndex() { + return UNITS_STABILITY.getDefaultUnitIndex(); + } + + @Override + public void setDefaultUnit(int n) { + UNITS_STABILITY.setDefaultUnit(n); + } + + @Override + public int getUnitCount() { + return UNITS_STABILITY.getUnitCount(); + } + + + //// Unsupported methods + + @Override + public void addUnit(int n, Unit u) { + throw new UnsupportedOperationException("StabilityUnitGroup must not be modified"); + } + + @Override + public void addUnit(Unit u) { + throw new UnsupportedOperationException("StabilityUnitGroup must not be modified"); + } + + @Override + public void removeUnit(int n) { + throw new UnsupportedOperationException("StabilityUnitGroup must not be modified"); + } + } +} diff --git a/src/net/sf/openrocket/util/Analysis.java b/src/net/sf/openrocket/util/Analysis.java new file mode 100644 index 000000000..505d979f0 --- /dev/null +++ b/src/net/sf/openrocket/util/Analysis.java @@ -0,0 +1,203 @@ +package net.sf.openrocket.util; + +import static net.sf.openrocket.aerodynamics.AtmosphericConditions.GAMMA; +import static net.sf.openrocket.aerodynamics.AtmosphericConditions.R; + +import java.io.File; +import java.io.PrintStream; +import java.util.Arrays; + +import net.sf.openrocket.aerodynamics.AerodynamicCalculator; +import net.sf.openrocket.aerodynamics.AerodynamicForces; +import net.sf.openrocket.aerodynamics.AtmosphericConditions; +import net.sf.openrocket.aerodynamics.BarrowmanCalculator; +import net.sf.openrocket.aerodynamics.ExactAtmosphericConditions; +import net.sf.openrocket.aerodynamics.FlightConditions; +import net.sf.openrocket.document.OpenRocketDocument; +import net.sf.openrocket.file.GeneralRocketLoader; +import net.sf.openrocket.file.RocketLoadException; +import net.sf.openrocket.file.RocketLoader; +import net.sf.openrocket.rocketcomponent.Configuration; + +public class Analysis { + + private static final double MACH_MIN = 0.01; + private static final double MACH_MAX = 5.00001; + private static final double MACH_STEP = 0.02; + + private static final double AOA_MACH = 0.6; + private static final double AOA_MIN = 0; + private static final double AOA_MAX = 15.00001*Math.PI/180; + private static final double AOA_STEP = 0.5*Math.PI/180; + + private static final double REYNOLDS = 9.8e6; + private static final double STAG_TEMP = 330; + + + private final RocketLoader loader = new GeneralRocketLoader(); + private final AerodynamicCalculator calculator = new BarrowmanCalculator(); + + private final FlightConditions conditions; + private final double length; + + private final Configuration config; + + private final AtmosphericConditions atmosphere; + + + + private Analysis(String filename) throws RocketLoadException { + + OpenRocketDocument doc = loader.load(new File(filename)); + config = doc.getRocket().getDefaultConfiguration(); + + calculator.setConfiguration(config); + + conditions = new FlightConditions(config); + System.out.println("Children: " + Arrays.toString(config.getRocket().getChildren())); + System.out.println("Children: " + Arrays.toString(config.getRocket().getChild(0).getChildren())); + length = config.getLength(); + System.out.println("Rocket length: " + (length*1000)+"mm"); + + atmosphere = new ExactAtmosphericConditions(); + + } + + + private double computeVelocityAndAtmosphere(double mach, double reynolds, double stagTemp) { + final double temperature; + final double pressure; + + + temperature = stagTemp / (1 + (GAMMA-1)/2 * MathUtil.pow2(mach)); + + // Speed of sound + double c = 331.3 * Math.sqrt(1 + (temperature - 273.15)/273.15); + + // Free-stream velocity + double v0 = c * mach; + +// kin.visc. = (3.7291e-06 + 4.9944e-08 * temperature) / density + pressure = reynolds * (3.7291e-06 + 4.9944e-08 * temperature) * R * temperature / + (v0 * length); + + atmosphere.pressure = pressure; + atmosphere.temperature = temperature; + conditions.setAtmosphericConditions(atmosphere); + conditions.setVelocity(v0); + + if (Math.abs(conditions.getMach() - mach) > 0.001) { + System.err.println("Computed mach: "+conditions.getMach() + " requested "+mach); +// System.exit(1); + } + + return v0; + } + + + + private void computeVsMach(PrintStream stream) { + + conditions.setAOA(0); + conditions.setTheta(45*Math.PI/180); + stream.println("% Mach, Caxial, CP, , CNa, Croll"); + + for (double mach = MACH_MIN; mach <= MACH_MAX; mach += MACH_STEP) { + + computeVelocityAndAtmosphere(mach, REYNOLDS, STAG_TEMP); +// conditions.setMach(mach); + + + AerodynamicForces forces = calculator.getAerodynamicForces(0, conditions, null); + + + double Re = conditions.getVelocity() * + calculator.getConfiguration().getLength() / + conditions.getAtmosphericConditions().getKinematicViscosity(); + if (Math.abs(Re - REYNOLDS) > 1) { + throw new RuntimeException("Re="+Re); + } + stream.printf("%f, %f, %f, %f, %f\n", mach, forces.Caxial, forces.cp.x, forces.CNa, + forces.Croll); + } + + } + + + + private void computeVsAOA(PrintStream stream, double thetaDeg) { + + computeVelocityAndAtmosphere(AOA_MACH, REYNOLDS, STAG_TEMP); + conditions.setTheta(thetaDeg * Math.PI/180); + stream.println("% AOA, CP, CN, Cm at theta = "+thetaDeg); + + for (double aoa = AOA_MIN; aoa <= AOA_MAX; aoa += AOA_STEP) { + + conditions.setAOA(aoa); + AerodynamicForces forces = calculator.getAerodynamicForces(0, conditions, null); + + + double Re = conditions.getVelocity() * + calculator.getConfiguration().getLength() / + conditions.getAtmosphericConditions().getKinematicViscosity(); + if (Math.abs(Re - REYNOLDS) > 1) { + throw new RuntimeException("Re="+Re); + } + stream.printf("%f, %f, %f, %f\n", aoa*180/Math.PI, forces.cp.x, forces.CN, forces.Cm); + } + + } + + + + + public static void main(String arg[]) throws Exception { + + if (arg.length != 2) { + System.err.println("Arguments: <rocket file> <output prefix>"); + System.exit(1); + } + + Analysis a = new Analysis(arg[0]); + final String prefix = arg[1]; + + + String name; + double v0 = a.computeVelocityAndAtmosphere(0.6, 9.8e6, 322); + System.out.printf("Sanity test: mach = %.1f v=%.1f temp=%.1f pres=%.0f c=%.1f " + + "ref.length=%.1fmm\n", + a.conditions.getMach(), v0, a.atmosphere.temperature, a.atmosphere.pressure, + a.atmosphere.getMachSpeed(), a.conditions.getRefLength()*1000); + System.out.println(); + + + // CA, CP, Croll vs. Mach at AOA=0 + name = prefix + "-CA-CP-CNa-Croll-vs-Mach.csv"; + System.out.println("Computing CA, CP, CNa, Croll vs. Mach to file "+name); + a.computeVsMach(new PrintStream(name)); + + + // CN & Cm vs. AOA at M=0.6 + name = prefix + "-CP-CN-Cm-vs-AOA-0.csv"; + System.out.println("Computing CP, CN, Cm vs. AOA at theta=0 to file "+name); + a.computeVsAOA(new PrintStream(name), 0); + + // CN & Cm vs. AOA at M=0.6 + name = prefix + "-CP-CN-Cm-vs-AOA-22.5.csv"; + System.out.println("Computing CP, CN, Cm vs. AOA at theta=22.5 to file "+name); + a.computeVsAOA(new PrintStream(name), 0); + + // CN & Cm vs. AOA at M=0.6 + name = prefix + "-CP-CN-Cm-vs-AOA-45.csv"; + System.out.println("Computing CP, CN, Cm vs. AOA at theta=45 to file "+name); + a.computeVsAOA(new PrintStream(name), 0); + + + System.out.println("Done."); + } + + + + + +} diff --git a/src/net/sf/openrocket/util/Base64.java b/src/net/sf/openrocket/util/Base64.java new file mode 100644 index 000000000..fb8490a79 --- /dev/null +++ b/src/net/sf/openrocket/util/Base64.java @@ -0,0 +1,220 @@ +package net.sf.openrocket.util; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; + +public class Base64 { + + public static final int DEFAULT_CHARS_PER_LINE = 72; + + private static final char[] ALPHABET = new char[] { + 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P', + 'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f', + 'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v', + 'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/' + }; + private static final char PAD = '='; + + private static final Map<Character,Integer> REVERSE = new HashMap<Character,Integer>(); + static { + for (int i=0; i<64; i++) { + REVERSE.put(ALPHABET[i], i); + } + REVERSE.put('-', 62); + REVERSE.put('_', 63); + REVERSE.put(PAD, 0); + } + + + public static String encode(byte[] data) { + return encode(data, DEFAULT_CHARS_PER_LINE); + } + + public static String encode(byte[] data, int maxColumn) { + StringBuilder builder = new StringBuilder(); + int column = 0; + + for (int position=0; position < data.length; position+=3) { + if (column+4 > maxColumn) { + builder.append('\n'); + column = 0; + } + builder.append(encodeGroup(data, position)); + column += 4; + } + builder.append('\n'); + return builder.toString(); + } + + + + + public static byte[] decode(String data) { + byte[] array = new byte[data.length()*3/4]; + char[] block = new char[4]; + int length = 0; + + for (int position=0; position < data.length(); ) { + int p; + for (p=0; p<4 && position < data.length(); position++) { + char c = data.charAt(position); + if (!Character.isWhitespace(c)) { + block[p] = c; + p++; + } + } + + if (p==0) + break; + if (p!=4) { + throw new IllegalArgumentException("Data ended when decoding Base64, p="+p); + } + + int l = decodeGroup(block, array, length); + length += l; + if (l < 3) + break; + } + return Arrays.copyOf(array, length); + } + + + //// Helper methods + + + /** + * Encode three bytes of data into four characters. + */ + private static char[] encodeGroup(byte[] data, int position) { + char[] c = new char[] { '=','=','=','=' }; + int b1=0, b2=0, b3=0; + int length = data.length - position; + + if (length == 0) + return c; + + if (length >= 1) { + b1 = ((int)data[position])&0xFF; + } + if (length >= 2) { + b2 = ((int)data[position+1])&0xFF; + } + if (length >= 3) { + b3 = ((int)data[position+2])&0xFF; + } + + c[0] = ALPHABET[b1>>2]; + c[1] = ALPHABET[(b1 & 3)<<4 | (b2>>4)]; + if (length == 1) + return c; + c[2] = ALPHABET[(b2 & 15)<<2 | (b3>>6)]; + if (length == 2) + return c; + c[3] = ALPHABET[b3 & 0x3f]; + return c; + } + + + /** + * Decode four chars from data into 0-3 bytes of data starting at position in array. + * @return the number of bytes decoded. + */ + private static int decodeGroup(char[] data, byte[] array, int position) { + int b1, b2, b3, b4; + + try { + b1 = REVERSE.get(data[0]); + b2 = REVERSE.get(data[1]); + b3 = REVERSE.get(data[2]); + b4 = REVERSE.get(data[3]); + } catch (NullPointerException e) { + // If auto-boxing fails + throw new IllegalArgumentException("Illegal characters in the sequence to be "+ + "decoded: "+Arrays.toString(data)); + } + + array[position] = (byte)((b1 << 2) | (b2 >> 4)); + array[position+1] = (byte)((b2 << 4) | (b3 >> 2)); + array[position+2] = (byte)((b3 << 6) | (b4)); + + // Check the amount of data decoded + if (data[0] == PAD) + return 0; + if (data[1] == PAD) { + throw new IllegalArgumentException("Illegal character padding in sequence to be "+ + "decoded: "+Arrays.toString(data)); + } + if (data[2] == PAD) + return 1; + if (data[3] == PAD) + return 2; + + return 3; + } + + + + public static void main(String[] arg) { + Random rnd = new Random(); + + for (int round=0; round < 1000; round++) { + int n = rnd.nextInt(1000); + n = 100000; + + byte[] array = new byte[n]; + rnd.nextBytes(array); + + String encoded = encode(array); + + System.out.println(encoded); + System.exit(0); +// for (int i=0; i<1000; i++) { +// int pos = rnd.nextInt(encoded.length()); +// String s1 = encoded.substring(0, pos); +// String s2 = encoded.substring(pos); +// switch (rnd.nextInt(15)) { +// case 0: +// encoded = s1 + " " + s2; +// break; +// case 1: +// encoded = s1 + "\u0009" + s2; +// break; +// case 2: +// encoded = s1 + "\n" + s2; +// break; +// case 3: +// encoded = s1 + "\u000B" + s2; +// break; +// case 4: +// encoded = s1 + "\r" + s2; +// break; +// case 5: +// encoded = s1 + "\u000C" + s2; +// break; +// case 6: +// encoded = s1 + "\u001C" + s2; +// break; +// } +// } + + byte[] decoded = null; + try { + decoded = decode(encoded); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + System.err.println("Bad data:\n"+encoded); + System.exit(1); + } + + if (!Arrays.equals(array, decoded)) { + System.err.println("Data differs! n="+n); + System.exit(1); + } + System.out.println("n="+n+" ok!"); + } + } + + +} diff --git a/src/net/sf/openrocket/util/ChangeSource.java b/src/net/sf/openrocket/util/ChangeSource.java new file mode 100644 index 000000000..5d166cd54 --- /dev/null +++ b/src/net/sf/openrocket/util/ChangeSource.java @@ -0,0 +1,15 @@ +package net.sf.openrocket.util; + +import javax.swing.event.ChangeListener; + +/** + * An interface defining an object firing ChangeEvents. Why isn't this included in the Java API?? + * + * @author Sampo Niskanen <sampo.niskanen@iki.fi> + */ +public interface ChangeSource { + + public void addChangeListener(ChangeListener listener); + public void removeChangeListener(ChangeListener listener); + +} diff --git a/src/net/sf/openrocket/util/Coordinate.java b/src/net/sf/openrocket/util/Coordinate.java new file mode 100644 index 000000000..5b88f08a6 --- /dev/null +++ b/src/net/sf/openrocket/util/Coordinate.java @@ -0,0 +1,291 @@ +package net.sf.openrocket.util; + +import java.io.Serializable; + +/** + * An immutable class of weighted coordinates. The weights are non-negative. + * + * Can also be used as non-weighted coordinates with weight=0. + * + * @author Sampo Niskanen <sampo.niskanen@iki.fi> + */ +public final class Coordinate implements Serializable { + public static final Coordinate NUL = new Coordinate(0,0,0,0); + public static final Coordinate NaN = new Coordinate(Double.NaN,Double.NaN, + Double.NaN,Double.NaN); + public static final double COMPARISON_DELTA = 0.000001; + public final double x,y,z; + public final double weight; + + + private double length = -1; /* Cached when calculated */ + + + /* Count and report the number of times a Coordinate is constructed: */ +// private static int count=0; +// { +// count++; +// if ((count % 1000) == 0) { +// System.err.println("Coordinate instantiated "+count+" times"); +// } +// } + + + + public Coordinate() { + this(0,0,0,0); + } + + public Coordinate(double x) { + this(x,0,0,0); + } + + public Coordinate(double x, double y) { + this(x,y,0,0); + } + + public Coordinate(double x, double y, double z) { + this(x,y,z,0); + } + public Coordinate(double x, double y, double z, double w) { + this.x = x; + this.y = y; + this.z = z; + this.weight=w; + } + + + public boolean isWeighted() { + return (weight != 0); + } + + public boolean isNaN() { + return Double.isNaN(x) || Double.isNaN(y) || Double.isNaN(z) || Double.isNaN(weight); + } + + public Coordinate setX(double x) { + return new Coordinate(x,this.y,this.z,this.weight); + } + + public Coordinate setY(double y) { + return new Coordinate(this.x,y,this.z,this.weight); + } + + public Coordinate setZ(double z) { + return new Coordinate(this.x,this.y,z,this.weight); + } + + public Coordinate setWeight(double weight) { + return new Coordinate(this.x, this.y, this.z, weight); + } + + public Coordinate setXYZ(Coordinate c) { + return new Coordinate(c.x, c.y, c.z, this.weight); + } + + + /** + * Add the coordinate and weight of two coordinates. + * + * @param other the other <code>Coordinate</code> + * @return the sum of the coordinates + */ + public Coordinate add(Coordinate other) { + return new Coordinate(this.x+other.x, this.y+other.y, this.z+other.z, + this.weight+other.weight); + } + + public Coordinate add(double x, double y, double z) { + return new Coordinate(this.x+x, this.y+y, this.z+z, this.weight); + } + + public Coordinate add(double x, double y, double z, double weight) { + return new Coordinate(this.x+x, this.y+y, this.z+z, this.weight+weight); + } + + /** + * Subtract a Coordinate from this Coordinate. The weight of the resulting Coordinate + * is the same as of this Coordinate, the weight of the argument is ignored. + * + * @param other Coordinate to subtract from this. + * @return The result + */ + public Coordinate sub(Coordinate other) { + return new Coordinate(this.x-other.x, this.y-other.y, this.z-other.z, this.weight); + } + + /** + * Subtract the specified values from this Coordinate. The weight of the result + * is the same as the weight of this Coordinate. + * + * @param x x value to subtract + * @param y y value to subtract + * @param z z value to subtract + * @return the result. + */ + public Coordinate sub(double x, double y, double z) { + return new Coordinate(this.x - x, this.y - y, this.z - z, this.weight); + } + + + /** + * Multiply the <code>Coordinate</code> with a scalar. All coordinates and the + * weight are multiplied by the given scalar. + + * @param m Factor to multiply by. + * @return The product. + */ + public Coordinate multiply(double m) { + return new Coordinate(this.x*m, this.y*m, this.z*m, this.weight*m); + } + + /** + * Dot product of two Coordinates, taken as vectors. Equal to + * x1*x2+y1*y2+z1*z2 + * @param other Coordinate to take product with. + * @return The dot product. + */ + public double dot(Coordinate other) { + return this.x*other.x + this.y*other.y + this.z*other.z; + } + /** + * Dot product of two Coordinates. + */ + public static double dot(Coordinate v1, Coordinate v2) { + return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z; + } + + /** + * Distance from the origin to the Coordinate. + */ + public double length() { + if (length < 0) { + length = Math.sqrt(x*x+y*y+z*z); + } + return length; + } + + /** + * Square of the distance from the origin to the Coordinate. + */ + public double length2() { + return x*x+y*y+z*z; + } + + /** + * Returns a new coordinate which has the same direction from the origin as this + * coordinate but is at a distance of one. If this coordinate is the origin, + * this method throws an <code>IllegalStateException</code>. The weight of the + * coordinate is unchanged. + * + * @return the coordinate normalized to distance one of the origin. + * @throws IllegalStateException if this coordinate is the origin. + */ + public Coordinate normalize() { + double l = length(); + if (l < 0.0000001) { + throw new IllegalStateException("Cannot normalize zero coordinate"); + } + return new Coordinate(x/l, y/l, z/l, weight); + } + + + + + /** + * Weighted average of two coordinates. If either of the weights are positive, + * the result is the weighted average of the coordinates and the weight is the sum + * of the original weights. If the sum of the weights is zero (and especially if + * both of the weights are zero), the result is the unweighted average of the + * coordinates with weight zero. + * <p> + * If <code>other</code> is <code>null</code> then this <code>Coordinate</code> is + * returned. + */ + public Coordinate average(Coordinate other) { + double x,y,z,w; + + if (other == null) + return this; + + w = this.weight + other.weight; + if (Math.abs(w) < MathUtil.pow2(MathUtil.EPSILON)) { + x = (this.x+other.x)/2; + y = (this.y+other.y)/2; + z = (this.z+other.z)/2; + w = 0; + } else { + x = (this.x*this.weight + other.x*other.weight)/w; + y = (this.y*this.weight + other.y*other.weight)/w; + z = (this.z*this.weight + other.z*other.weight)/w; + } + return new Coordinate(x,y,z,w); + } + + + /** + * Tests whether the coordinates (not weight!) are the same. + * + * Compares only the (x,y,z) coordinates, NOT the weight. Coordinate comparison is + * done to the precision of COMPARISON_DELTA. + * + * @param other Coordinate to compare to. + * @return true if the coordinates are equal + */ + @Override + public boolean equals(Object other) { + if (!(other instanceof Coordinate)) + return false; + + final Coordinate c = (Coordinate)other; + return (MathUtil.equals(this.x, c.x) && + MathUtil.equals(this.y, c.y) && + MathUtil.equals(this.z, c.z)); + } + + /** + * Hash code method compatible with {@link #equals(Object)}. + */ + @Override + public int hashCode() { + return (int)((x+y+z)*100000); + } + + + @Override + public String toString() { + if (isWeighted()) + return String.format("(%.3f,%.3f,%.3f,w=%.3f)", x,y,z,weight); + else + return String.format("(%.3f,%.3f,%.3f)", x,y,z); + } + + + + public static void main(String[] arg) { + double a=1.2; + double x; + Coordinate c; + long t1, t2; + + x = 0; + t1 = System.nanoTime(); + for (int i=0; i < 100000000; i++) { + x = x + a; + } + t2 = System.nanoTime(); + System.out.println("Value: "+x); + System.out.println("Plain addition: "+ ((t2-t1+500000)/1000000) + " ms"); + + c = Coordinate.NUL; + t1 = System.nanoTime(); + for (int i=0; i < 100000000; i++) { + c = c.add(a,0,0); + } + t2 = System.nanoTime(); + System.out.println("Value: "+c.x); + System.out.println("Coordinate addition: "+ ((t2-t1+500000)/1000000) + " ms"); + + } + +} diff --git a/src/net/sf/openrocket/util/GUIUtil.java b/src/net/sf/openrocket/util/GUIUtil.java new file mode 100644 index 000000000..eb0eccfce --- /dev/null +++ b/src/net/sf/openrocket/util/GUIUtil.java @@ -0,0 +1,80 @@ +package net.sf.openrocket.util; + +import java.awt.Component; +import java.awt.KeyboardFocusManager; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.awt.event.WindowEvent; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JRootPane; +import javax.swing.KeyStroke; +import javax.swing.RootPaneContainer; +import javax.swing.SwingUtilities; + +public class GUIUtil { + + private static final KeyStroke ESCAPE = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0); + private static final String CLOSE_ACTION_KEY = "escape:WINDOW_CLOSING"; + + + /** + * Add the correct action to close a JDialog when the ESC key is pressed. + * The dialog is closed by sending is a WINDOW_CLOSING event. + * + * @param dialog the dialog for which to install the action. + */ + public static void installEscapeCloseOperation(final JDialog dialog) { + Action dispatchClosing = new AbstractAction() { + public void actionPerformed(ActionEvent event) { + dialog.dispatchEvent(new WindowEvent(dialog, WindowEvent.WINDOW_CLOSING)); + } + }; + JRootPane root = dialog.getRootPane(); + root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(ESCAPE, CLOSE_ACTION_KEY); + root.getActionMap().put(CLOSE_ACTION_KEY, dispatchClosing); + } + + + /** + * Set the given button as the default button of the frame/dialog it is in. The button + * must be first attached to the window component hierarchy. + * + * @param button the button to set as the default button. + */ + public static void setDefaultButton(JButton button) { + Window w = SwingUtilities.windowForComponent(button); + if (w == null) { + throw new IllegalArgumentException("Attach button to a window first."); + } + if (!(w instanceof RootPaneContainer)) { + throw new IllegalArgumentException("Button not attached to RootPaneContainer, w="+w); + } + ((RootPaneContainer)w).getRootPane().setDefaultButton(button); + } + + + + /** + * Change the behavior of a component so that TAB and Shift-TAB cycles the focus of + * the components. This is necessary for e.g. <code>JTextArea</code>. + * + * @param c the component to modify + */ + public static void setTabToFocusing(Component c) { + Set<KeyStroke> strokes = new HashSet<KeyStroke>(Arrays.asList(KeyStroke.getKeyStroke("pressed TAB"))); + c.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, strokes); + strokes = new HashSet<KeyStroke>(Arrays.asList(KeyStroke.getKeyStroke("shift pressed TAB"))); + c.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, strokes); + } + + +} diff --git a/src/net/sf/openrocket/util/Icons.java b/src/net/sf/openrocket/util/Icons.java new file mode 100644 index 000000000..96a32b7ba --- /dev/null +++ b/src/net/sf/openrocket/util/Icons.java @@ -0,0 +1,56 @@ +package net.sf.openrocket.util; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import javax.swing.Icon; +import javax.swing.ImageIcon; + +import net.sf.openrocket.document.Simulation; + + +public class Icons { + + /** + * Icons used for showing the status of a simulation (up to date, out of date, etc). + */ + public static final Map<Simulation.Status, Icon> SIMULATION_STATUS_ICON_MAP; + static { + HashMap<Simulation.Status, Icon> map = new HashMap<Simulation.Status, Icon>(); + map.put(Simulation.Status.NOT_SIMULATED, new ImageIcon("pix/spheres/gray-16x16.png", "Not simulated")); + map.put(Simulation.Status.UPTODATE, new ImageIcon("pix/spheres/green-16x16.png", "Up to date")); + map.put(Simulation.Status.LOADED, new ImageIcon("pix/spheres/yellow-16x16.png", "Loaded from file")); + map.put(Simulation.Status.OUTDATED, new ImageIcon("pix/spheres/red-16x16.png", "Out-of-date")); + map.put(Simulation.Status.EXTERNAL, new ImageIcon("pix/spheres/blue-16x16.png", "Imported data")); + SIMULATION_STATUS_ICON_MAP = Collections.unmodifiableMap(map); + } + + public static final Icon SIMULATION_LISTENER_OK; + public static final Icon SIMULATION_LISTENER_ERROR; + static { + SIMULATION_LISTENER_OK = SIMULATION_STATUS_ICON_MAP.get(Simulation.Status.UPTODATE); + SIMULATION_LISTENER_ERROR = SIMULATION_STATUS_ICON_MAP.get(Simulation.Status.OUTDATED); + } + + + public static final Icon FILE_NEW = new ImageIcon(ClassLoader.getSystemResource("pix/icons/document-new.png"), "New document"); + public static final Icon FILE_OPEN = new ImageIcon(ClassLoader.getSystemResource("pix/icons/document-open.png"), "Open document"); + public static final Icon FILE_SAVE = new ImageIcon(ClassLoader.getSystemResource("pix/icons/document-save.png"), "Save document"); + public static final Icon FILE_SAVE_AS = new ImageIcon(ClassLoader.getSystemResource("pix/icons/document-save-as.png"), "Save document as"); + public static final Icon FILE_CLOSE = new ImageIcon(ClassLoader.getSystemResource("pix/icons/document-close.png"), "Close document"); + public static final Icon FILE_QUIT = new ImageIcon(ClassLoader.getSystemResource("pix/icons/application-exit.png"), "Quit OpenRocket"); + + public static final Icon EDIT_UNDO = new ImageIcon(ClassLoader.getSystemResource("pix/icons/edit-undo.png"), "Undo"); + public static final Icon EDIT_REDO = new ImageIcon(ClassLoader.getSystemResource("pix/icons/edit-redo.png"), "Redo"); + public static final Icon EDIT_CUT = new ImageIcon(ClassLoader.getSystemResource("pix/icons/edit-cut.png"), "Cut"); + public static final Icon EDIT_COPY = new ImageIcon(ClassLoader.getSystemResource("pix/icons/edit-copy.png"), "Copy"); + public static final Icon EDIT_PASTE = new ImageIcon(ClassLoader.getSystemResource("pix/icons/edit-paste.png"), "Paste"); + public static final Icon EDIT_DELETE = new ImageIcon(ClassLoader.getSystemResource("pix/icons/edit-delete.png"), "Delete"); + + public static final Icon ZOOM_IN = new ImageIcon(ClassLoader.getSystemResource("pix/icons/zoom-in.png"), "Zoom in"); + public static final Icon ZOOM_OUT = new ImageIcon(ClassLoader.getSystemResource("pix/icons/zoom-out.png"), "Zoom out"); + + public static final Icon PREFERENCES = new ImageIcon(ClassLoader.getSystemResource("pix/icons/preferences.png"), "Preferences"); + +} diff --git a/src/net/sf/openrocket/util/LineStyle.java b/src/net/sf/openrocket/util/LineStyle.java new file mode 100644 index 000000000..efc946ba0 --- /dev/null +++ b/src/net/sf/openrocket/util/LineStyle.java @@ -0,0 +1,31 @@ +package net.sf.openrocket.util; + +import java.util.Arrays; + +/** + * An enumeration of line styles. The line styles are defined by an array of + * floats suitable for <code>BasicStroke</code>. + * + * @author Sampo Niskanen <sampo.niskanen@iki.fi> + */ +public enum LineStyle { + SOLID("Solid",new float[] { 10f, 0f }), + DASHED("Dashed",new float[] { 6f, 4f }), + DOTTED("Dotted",new float[] { 2f, 3f }), + DASHDOT("Dash-dotted",new float[] { 8f, 3f, 2f, 3f}) + ; + + private final String name; + private final float[] dashes; + LineStyle(String name, float[] dashes) { + this.name = name; + this.dashes = dashes; + } + public float[] getDashes() { + return Arrays.copyOf(dashes, dashes.length); + } + @Override + public String toString() { + return name; + } +} \ No newline at end of file diff --git a/src/net/sf/openrocket/util/LinearInterpolator.java b/src/net/sf/openrocket/util/LinearInterpolator.java new file mode 100644 index 000000000..00efe3a40 --- /dev/null +++ b/src/net/sf/openrocket/util/LinearInterpolator.java @@ -0,0 +1,128 @@ +package net.sf.openrocket.util; + +import java.util.Iterator; +import java.util.Map; +import java.util.TreeMap; + +public class LinearInterpolator implements Cloneable { + + private TreeMap<Double, Double> sortMap = new TreeMap<Double,Double>(); + + /** + * Construct a <code>LinearInterpolator</code> with no points. Some points must be + * added using {@link #addPoints(double[], double[])} before using the interpolator. + */ + public LinearInterpolator() { + } + + /** + * Construct a <code>LinearInterpolator</code> with the given points. + * + * @param x the x-coordinates of the points. + * @param y the y-coordinates of the points. + * @throws IllegalArgumentException if the lengths of <code>x</code> and <code>y</code> + * are not equal. + * @see #addPoints(double[], double[]) + */ + public LinearInterpolator(double[] x, double[] y) { + addPoints(x,y); + } + + + /** + * Add the point to the linear interpolation. + * + * @param x the x-coordinate of the point. + * @param y the y-coordinate of the point. + */ + public void addPoint(double x, double y) { + sortMap.put(x, y); + } + + /** + * Add the points to the linear interpolation. + * + * @param x the x-coordinates of the points. + * @param y the y-coordinates of the points. + * @throws IllegalArgumentException if the lengths of <code>x</code> and <code>y</code> + * are not equal. + */ + public void addPoints(double[] x, double[] y) { + if (x.length != y.length) { + throw new IllegalArgumentException("Array lengths do not match, x="+x.length + + " y="+y.length); + } + for (int i=0; i < x.length; i++) { + sortMap.put(x[i],y[i]); + } + } + + + + public double getValue(double x) { + Map.Entry<Double,Double> e1, e2; + double x1, x2; + double y1, y2; + + e1 = sortMap.floorEntry(x); + + if (e1 == null) { + // x smaller than any value in the set + e1 = sortMap.firstEntry(); + if (e1 == null) { + throw new IllegalStateException("No points added yet to the interpolator."); + } + return e1.getValue(); + } + + x1 = e1.getKey(); + e2 = sortMap.higherEntry(x1); + + if (e2 == null) { + // x larger than any value in the set + return e1.getValue(); + } + + x2 = e2.getKey(); + y1 = e1.getValue(); + y2 = e2.getValue(); + + return (x - x1)/(x2-x1) * (y2-y1) + y1; + } + + + public double[] getXPoints() { + double[] x = new double[sortMap.size()]; + Iterator<Double> iter = sortMap.keySet().iterator(); + for (int i=0; iter.hasNext(); i++) { + x[i] = iter.next(); + } + return x; + } + + + @SuppressWarnings("unchecked") + @Override + public LinearInterpolator clone() { + try { + LinearInterpolator other = (LinearInterpolator)super.clone(); + other.sortMap = (TreeMap<Double,Double>)this.sortMap.clone(); + return other; + } catch (CloneNotSupportedException e) { + throw new RuntimeException("CloneNotSupportedException?!",e); + } + } + + + public static void main(String[] args) { + LinearInterpolator interpolator = new LinearInterpolator( + new double[] {1, 1.5, 2, 4, 5}, + new double[] {0, 1, 0, 2, 2} + ); + + for (double x=0; x < 6; x+=0.1) { + System.out.printf("%.1f: %.2f\n", x, interpolator.getValue(x)); + } + } + +} diff --git a/src/net/sf/openrocket/util/MathUtil.java b/src/net/sf/openrocket/util/MathUtil.java new file mode 100644 index 000000000..a714f8127 --- /dev/null +++ b/src/net/sf/openrocket/util/MathUtil.java @@ -0,0 +1,217 @@ +package net.sf.openrocket.util; + +public class MathUtil { + public static final double EPSILON = 0.00000001; // 10mm^3 in m^3 + + /** + * The square of x (x^2). On Sun's JRE using this method is as fast as typing x*x. + * @param x x + * @return x^2 + */ + public static double pow2(double x) { + return x*x; + } + + /** + * The cube of x (x^3). + * @param x x + * @return x^3 + */ + public static double pow3(double x) { + return x*x*x; + } + + public static double pow4(double x) { + return (x*x)*(x*x); + } + + /** + * Clamps the value x to the range min - max. + * @param x Original value. + * @param min Minimum value to return. + * @param max Maximum value to return. + * @return The clamped value. + */ + public static double clamp(double x, double min, double max) { + if (x < min) + return min; + if (x > max) + return max; + return x; + } + + public static float clamp(float x, float min, float max) { + if (x < min) + return min; + if (x > max) + return max; + return x; + } + + public static int clamp(int x, int min, int max) { + if (x < min) + return min; + if (x > max) + return max; + return x; + } + + + /** + * Maps a value from one value range to another. + * + * @param value the value to map. + * @param fromMin the minimum of the starting range. + * @param fromMax the maximum of the starting range. + * @param toMin the minimum of the destination range. + * @param toMax the maximum of the destination range. + * @return the mapped value. + * @throws IllegalArgumentException if fromMin == fromMax, but toMin != toMax. + */ + public static double map(double value, double fromMin, double fromMax, + double toMin, double toMax) { + if (equals(toMin, toMax)) + return toMin; + if (equals(fromMin, fromMax)) { + throw new IllegalArgumentException("from range is singular an to range is not."); + } + return (value - fromMin)/(fromMax-fromMin) * (toMax - toMin) + toMin; + } + + /** + * Compute the minimum of two values. This is performed by direct comparison. + * However, if one of the values is NaN and the other is not, the non-NaN value is + * returned. + */ + public static double min(double x, double y) { + if (Double.isNaN(y)) + return x; + return (x < y) ? x : y; + } + + /** + * Compute the maximum of two values. This is performed by direct comparison. + * However, if one of the values is NaN and the other is not, the non-NaN value is + * returned. + */ + public static double max(double x, double y) { + if (Double.isNaN(x)) + return y; + return (x < y) ? y : x; + } + + /** + * Compute the minimum of three values. This is performed by direct comparison. + * However, if one of the values is NaN and the other is not, the non-NaN value is + * returned. + */ + public static double min(double x, double y, double z) { + if (x < y || Double.isNaN(y)) { + return min(x,z); + } else { + return min(y,z); + } + } + + /** + * Compute the maximum of three values. This is performed by direct comparison. + * However, if one of the values is NaN and the other is not, the non-NaN value is + * returned. + */ + public static double max(double x, double y, double z) { + if (x > y || Double.isNaN(y)) { + return max(x,z); + } else { + return max(y,z); + } + } + + /** + * Calculates the hypotenuse <code>sqrt(x^2+y^2)</code>. This method is SIGNIFICANTLY + * faster than <code>Math.hypot(x,y)</code>. + */ + public static double hypot(double x, double y) { + return Math.sqrt(x*x + y*y); + } + + /** + * Reduce the angle x to the range 0 - 2*PI. + * @param x Original angle. + * @return The equivalent angle in the range 0 ... 2*PI. + */ + public static double reduce360(double x) { + double d = Math.floor(x / (2*Math.PI)); + return x - d*2*Math.PI; + } + + /** + * Reduce the angle x to the range -PI - PI. + * + * Either -PI and PI might be returned, depending on the rounding function. + * + * @param x Original angle. + * @return The equivalent angle in the range -PI ... PI. + */ + public static double reduce180(double x) { + double d = Math.rint(x / (2*Math.PI)); + return x - d*2*Math.PI; + } + + + public static boolean equals(double a, double b) { + double absb = Math.abs(b); + + if (absb < EPSILON/2) { + // Near zero + return Math.abs(a) < EPSILON/2; + } + return Math.abs(a-b) < EPSILON*absb; + } + + public static double sign(double x) { + return (x<0) ? -1.0 : 1.0; + } + + /* Math.abs() is about 3x as fast as this: + + public static double abs(double x) { + return (x<0) ? -x : x; + } + */ + + + public static void main(String[] arg) { + double nan = Double.NaN; + System.out.println("min(5,6) = " + min(5, 6)); + System.out.println("min(5,nan) = " + min(5, nan)); + System.out.println("min(nan,6) = " + min(nan, 6)); + System.out.println("min(nan,nan) = " + min(nan, nan)); + System.out.println(); + System.out.println("max(5,6) = " + max(5, 6)); + System.out.println("max(5,nan) = " + max(5, nan)); + System.out.println("max(nan,6) = " + max(nan, 6)); + System.out.println("max(nan,nan) = " + max(nan, nan)); + System.out.println(); + System.out.println("min(5,6,7) = " + min(5, 6, 7)); + System.out.println("min(5,6,nan) = " + min(5, 6, nan)); + System.out.println("min(5,nan,7) = " + min(5, nan, 7)); + System.out.println("min(5,nan,nan) = " + min(5, nan, nan)); + System.out.println("min(nan,6,7) = " + min(nan, 6, 7)); + System.out.println("min(nan,6,nan) = " + min(nan, 6, nan)); + System.out.println("min(nan,nan,7) = " + min(nan, nan, 7)); + System.out.println("min(nan,nan,nan) = " + min(nan, nan, nan)); + System.out.println(); + System.out.println("max(5,6,7) = " + max(5, 6, 7)); + System.out.println("max(5,6,nan) = " + max(5, 6, nan)); + System.out.println("max(5,nan,7) = " + max(5, nan, 7)); + System.out.println("max(5,nan,nan) = " + max(5, nan, nan)); + System.out.println("max(nan,6,7) = " + max(nan, 6, 7)); + System.out.println("max(nan,6,nan) = " + max(nan, 6, nan)); + System.out.println("max(nan,nan,7) = " + max(nan, nan, 7)); + System.out.println("max(nan,nan,nan) = " + max(nan, nan, nan)); + System.out.println(); + + + } + +} diff --git a/src/net/sf/openrocket/util/MutableCoordinate.java b/src/net/sf/openrocket/util/MutableCoordinate.java new file mode 100644 index 000000000..02af9357b --- /dev/null +++ b/src/net/sf/openrocket/util/MutableCoordinate.java @@ -0,0 +1,312 @@ +package net.sf.openrocket.util; + +import java.io.Serializable; + +/** + * An immutable class of weighted coordinates. The weights are non-negative. + * + * Can also be used as non-weighted coordinates with weight=0. + * + * @author Sampo Niskanen <sampo.niskanen@iki.fi> + */ +public final class MutableCoordinate implements Serializable { + public static final MutableCoordinate NUL = new MutableCoordinate(0,0,0,0); + public static final MutableCoordinate NaN = new MutableCoordinate(Double.NaN,Double.NaN, + Double.NaN,Double.NaN); + public static final double COMPARISON_DELTA = 0.000001; + private double x,y,z; + private double weight; + + + /* Count and report the number of times a Coordinate is constructed: */ +// private static int count=0; +// { +// count++; +// if ((count % 1000) == 0) { +// System.out.println("Coordinate instantiated "+count+" times"); +// } +// } + + + public MutableCoordinate() { + x=0; + y=0; + z=0; + weight=0; + } + + public MutableCoordinate(double x) { + this.x = x; + this.y = 0; + this.z = 0; + weight = 0; + } + + public MutableCoordinate(double x, double y) { + this.x = x; + this.y = y; + this.z = 0; + weight = 0; + } + + public MutableCoordinate(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + weight = 0; + } + public MutableCoordinate(double x, double y, double z, double w) { + this.x = x; + this.y = y; + this.z = z; + this.weight=w; + } + + + public boolean isWeighted() { + return (weight != 0); + } + + public boolean isNaN() { + return Double.isNaN(x) || Double.isNaN(y) || Double.isNaN(z) || Double.isNaN(weight); + } + + public MutableCoordinate setX(double x) { + return new MutableCoordinate(x,this.y,this.z,this.weight); + } + + public MutableCoordinate setY(double y) { + return new MutableCoordinate(this.x,y,this.z,this.weight); + } + + public MutableCoordinate setZ(double z) { + return new MutableCoordinate(this.x,this.y,z,this.weight); + } + + public MutableCoordinate setWeight(double weight) { + return new MutableCoordinate(this.x, this.y, this.z, weight); + } + + public MutableCoordinate setXYZ(MutableCoordinate c) { + return new MutableCoordinate(c.x, c.y, c.z, this.weight); + } + + public double getX() { + return x; + } + public double getY() { + return y; + } + public double getZ() { + return z; + } + + + /** + * Add the coordinate and weight of two coordinates. + * + * @param other the other <code>Coordinate</code> + * @return the sum of the coordinates + */ + public MutableCoordinate add(MutableCoordinate other) { + this.x += other.x; + this.y += other.y; + this.z += other.z; + this.weight += other.weight; + return this; + } + + public MutableCoordinate add(double x, double y, double z) { + this.x += x; + this.y += y; + this.z += z; + return this; + } + + public MutableCoordinate add(double x, double y, double z, double weight) { + return new MutableCoordinate(this.x+x, this.y+y, this.z+z, this.weight+weight); + } + + /** + * Subtract a Coordinate from this Coordinate. The weight of the resulting Coordinate + * is the same as of this Coordinate, the weight of the argument is ignored. + * + * @param other Coordinate to subtract from this. + * @return The result + */ + public MutableCoordinate sub(MutableCoordinate other) { + return new MutableCoordinate(this.x-other.x, this.y-other.y, this.z-other.z, this.weight); + } + + /** + * Subtract the specified values from this Coordinate. The weight of the result + * is the same as the weight of this Coordinate. + * + * @param x x value to subtract + * @param y y value to subtract + * @param z z value to subtract + * @return the result. + */ + public MutableCoordinate sub(double x, double y, double z) { + return new MutableCoordinate(this.x - x, this.y - y, this.z - z, this.weight); + } + + + /** + * Multiply the <code>Coordinate</code> with a scalar. All coordinates and the + * weight are multiplied by the given scalar. + + * @param m Factor to multiply by. + * @return The product. + */ + public MutableCoordinate multiply(double m) { + return new MutableCoordinate(this.x*m, this.y*m, this.z*m, this.weight*m); + } + + /** + * Dot product of two Coordinates, taken as vectors. Equal to + * x1*x2+y1*y2+z1*z2 + * @param other Coordinate to take product with. + * @return The dot product. + */ + public double dot(MutableCoordinate other) { + return this.x*other.x + this.y*other.y + this.z*other.z; + } + /** + * Dot product of two Coordinates. + */ + public static double dot(MutableCoordinate v1, MutableCoordinate v2) { + return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z; + } + + /** + * Distance from the origin to the Coordinate. + */ + public double length() { + return Math.sqrt(x*x+y*y+z*z); + } + + /** + * Square of the distance from the origin to the Coordinate. + */ + public double length2() { + return x*x+y*y+z*z; + } + + /** + * Returns a new coordinate which has the same direction from the origin as this + * coordinate but is at a distance of one. If this coordinate is the origin, + * this method throws an <code>IllegalStateException</code>. The weight of the + * coordinate is unchanged. + * + * @return the coordinate normalized to distance one of the origin. + * @throws IllegalStateException if this coordinate is the origin. + */ + public MutableCoordinate normalize() { + double l = length(); + if (l < 0.0000001) { + throw new IllegalStateException("Cannot normalize zero coordinate"); + } + return new MutableCoordinate(x/l, y/l, z/l, weight); + } + + + + + /** + * Weighted average of two coordinates. If either of the weights are positive, + * the result is the weighted average of the coordinates and the weight is the sum + * of the original weights. If the sum of the weights is zero (and especially if + * both of the weights are zero), the result is the unweighted average of the + * coordinates with weight zero. + * <p> + * If <code>other</code> is <code>null</code> then this <code>Coordinate</code> is + * returned. + */ + public MutableCoordinate average(MutableCoordinate other) { + double x,y,z,w; + + if (other == null) + return this; + + w = this.weight + other.weight; + if (MathUtil.equals(w, 0)) { + x = (this.x+other.x)/2; + y = (this.y+other.y)/2; + z = (this.z+other.z)/2; + w = 0; + } else { + x = (this.x*this.weight + other.x*other.weight)/w; + y = (this.y*this.weight + other.y*other.weight)/w; + z = (this.z*this.weight + other.z*other.weight)/w; + } + return new MutableCoordinate(x,y,z,w); + } + + + /** + * Tests whether the coordinates (not weight!) are the same. + * + * Compares only the (x,y,z) coordinates, NOT the weight. Coordinate comparison is + * done to the precision of COMPARISON_DELTA. + * + * @param other Coordinate to compare to. + * @return true if the coordinates are equal + */ + @Override + public boolean equals(Object other) { + if (!(other instanceof MutableCoordinate)) + return false; + + final MutableCoordinate c = (MutableCoordinate)other; + return (MathUtil.equals(this.x, c.x) && + MathUtil.equals(this.y, c.y) && + MathUtil.equals(this.z, c.z)); + } + + /** + * Hash code method compatible with {@link #equals(Object)}. + */ + @Override + public int hashCode() { + return (int)((x+y+z)*100000); + } + + + @Override + public String toString() { + if (isWeighted()) + return String.format("(%.3f,%.3f,%.3f,w=%.3f)", x,y,z,weight); + else + return String.format("(%.3f,%.3f,%.3f)", x,y,z); + } + + + + public static void main(String[] arg) { + double a=1.2; + double x; + MutableCoordinate c; + long t1, t2; + + x = 0; + t1 = System.nanoTime(); + for (int i=0; i < 100000000; i++) { + x = x + a; + } + t2 = System.nanoTime(); + System.out.println("Value: "+x); + System.out.println("Plain addition: "+ ((t2-t1+500000)/1000000) + " ms"); + + c = MutableCoordinate.NUL; + t1 = System.nanoTime(); + for (int i=0; i < 100000000; i++) { + c = c.add(a,0,0); + } + t2 = System.nanoTime(); + System.out.println("Value: "+c.x); + System.out.println("Coordinate addition: "+ ((t2-t1+500000)/1000000) + " ms"); + + } + +} diff --git a/src/net/sf/openrocket/util/Pair.java b/src/net/sf/openrocket/util/Pair.java new file mode 100644 index 000000000..8a4cec289 --- /dev/null +++ b/src/net/sf/openrocket/util/Pair.java @@ -0,0 +1,22 @@ +package net.sf.openrocket.util; + +public class Pair<U,V> { + + private final U u; + private final V v; + + + public Pair(U u, V v) { + this.u = u; + this.v = v; + } + + public U getU() { + return u; + } + + public V getV() { + return v; + } + +} diff --git a/src/net/sf/openrocket/util/PinkNoise.java b/src/net/sf/openrocket/util/PinkNoise.java new file mode 100644 index 000000000..572ad032c --- /dev/null +++ b/src/net/sf/openrocket/util/PinkNoise.java @@ -0,0 +1,140 @@ +package net.sf.openrocket.util; +import java.util.Random; + + +/** + * A class that provides a source of pink noise with a power spectrum density + * proportional to 1/f^alpha. The values are computed by applying an IIR filter to + * generated Gaussian random numbers. The number of poles used in the filter may be + * specified. Values as low as 3 produce good results, but using a larger number of + * poles allows lower frequencies to be amplified. Below the cutoff frequency the + * power spectrum density if constant. + * <p> + * The IIR filter use by this class is presented by N. Jeremy Kasdin, Proceedings of + * the IEEE, Vol. 83, No. 5, May 1995, p. 822. + * + * @author Sampo Niskanen <sampo.niskanen@iki.fi> + */ +public class PinkNoise { + private final int poles; + private final double[] multipliers; + + private final double[] values; + private final Random rnd; + + + /** + * Generate pink noise with alpha=1.0 using a five-pole IIR. + */ + public PinkNoise() { + this(1.0, 5, new Random()); + } + + + /** + * Generate a specific pink noise using a five-pole IIR. + * + * @param alpha the exponent of the pink noise, 1/f^alpha. + */ + public PinkNoise(double alpha) { + this(alpha, 5, new Random()); + } + + + /** + * Generate pink noise specifying alpha and the number of poles. The larger the + * number of poles, the lower are the lowest frequency components that are amplified. + * + * @param alpha the exponent of the pink noise, 1/f^alpha. + * @param poles the number of poles to use. + */ + public PinkNoise(double alpha, int poles) { + this(alpha, poles, new Random()); + } + + + /** + * Generate pink noise specifying alpha, the number of poles and the randomness source. + * + * @param alpha the exponent of the pink noise, 1/f^alpha. + * @param poles the number of poles to use. + * @param random the randomness source. + */ + public PinkNoise(double alpha, int poles, Random random) { + this.rnd = random; + this.poles = poles; + this.multipliers = new double[poles]; + this.values = new double[poles]; + + double a = 1; + for (int i=0; i < poles; i++) { + a = (i - alpha/2) * a / (i+1); + multipliers[i] = a; + } + + // Fill the history with random values + for (int i=0; i < 5*poles; i++) + this.nextValue(); + } + + + + public double nextValue() { + double x = rnd.nextGaussian(); +// double x = rnd.nextDouble()-0.5; + + for (int i=0; i < poles; i++) { + x -= multipliers[i] * values[i]; + } + System.arraycopy(values, 0, values, 1, values.length-1); + values[0] = x; + + return x; + } + + + public static void main(String[] arg) { + + PinkNoise source; + + source = new PinkNoise(1.0, 100); + double std = 0; + for (int i=0; i < 1000000; i++) { + + } + + +// int n = 5000000; +// double avgavg=0; +// double avgstd = 0; +// double[] val = new double[n]; +// +// for (int j=0; j < 10; j++) { +// double avg=0, std=0; +// source = new PinkNoise(5.0/3.0, 2); +// +// for (int i=0; i < n; i++) { +// val[i] = source.nextValue(); +// avg += val[i]; +// } +// avg /= n; +// for (int i=0; i < n; i++) { +// std += (val[i]-avg)*(val[i]-avg); +// } +// std /= n; +// std = Math.sqrt(std); +// +// System.out.println("avg:"+avg+" stddev:"+std); +// avgavg += avg; +// avgstd += std; +// } +// avgavg /= 10; +// avgstd /= 10; +// System.out.println("Average avg:"+avgavg+" std:"+avgstd); +// + // Two poles: + + } + + +} diff --git a/src/net/sf/openrocket/util/PolyInterpolator.java b/src/net/sf/openrocket/util/PolyInterpolator.java new file mode 100644 index 000000000..f97d45c43 --- /dev/null +++ b/src/net/sf/openrocket/util/PolyInterpolator.java @@ -0,0 +1,262 @@ +package net.sf.openrocket.util; + +import java.util.Arrays; + +/** + * A class for polynomial interpolation. The interpolation constraints can be specified + * either as function values or values of the n'th derivative of the function. + * Using an interpolation consists of three steps: + * <p> + * 1. constructing a <code>PolyInterpolator</code> using the interpolation x coordinates <br> + * 2. generating the interpolation polynomial using the function and derivative values <br> + * 3. evaluating the polynomial at the desired point + * <p> + * The constructor takes an array of double arrays. The first array defines x coordinate + * values for the function values, the second array x coordinate values for the function's + * derivatives, the third array for second derivatives and so on. Constructing the + * <code>PolyInterpolator</code> is relatively slow, O(n^3) where n is the order of the + * polynomial. (It contains calculation of the inverse of an n x n matrix.) + * <p> + * Generating the interpolation polynomial is performed by the method + * {@link #interpolator(double...)}, which takes as an argument the function and + * derivative values. This operation takes O(n^2) time. + * <p> + * Finally, evaluating the polynomial at different positions takes O(n) time. + * + * @author Sampo Niskanen <sampo.niskanen@iki.fi> + */ +public class PolyInterpolator { + + // Order of the polynomial + private final int count; + + private final double[][] interpolationMatrix; + + + /** + * Construct a polynomial interpolation generator. All arguments to the constructor + * are the x coordinates of the interpolated function. The first array correspond to + * the function values, the second to function derivatives, the third to second + * derivatives and so forth. The order of the polynomial is automatically calculated + * from the total number of constraints. + * <p> + * The construction takes O(n^3) time. + * + * @param points an array of constraints, the first corresponding to function value + * constraints, the second to derivative constraints etc. + */ + public PolyInterpolator(double[] ... points) { + int count = 0; + for (int i=0; i < points.length; i++) { + count += points[i].length; + } + if (count == 0) { + throw new IllegalArgumentException("No interpolation points defined."); + } + + this.count = count; + + int[] mul = new int[count]; + Arrays.fill(mul, 1); + + double[][] matrix = new double[count][count]; + int row = 0; + for (int j=0; j < points.length; j++) { + + for (int i=0; i < points[j].length; i++) { + double x = 1; + for (int col = count-1-j; col>= 0; col--) { + matrix[row][col] = x*mul[col]; + x *= points[j][i]; + } + row++; + } + + for (int i=0; i < count; i++) { + mul[i] *= (count-i-j-1); + } + } + assert(row == count); + + interpolationMatrix = inverse(matrix); + } + + + /** + * Generates an interpolation polynomial. The arguments supplied to this method + * are the function values, derivatives, second derivatives etc. in the order + * specified in the constructor (i.e. values first, then derivatives etc). + * <p> + * This method takes O(n^2) time. + * + * @param values the function values, derivatives etc. at positions defined in the + * constructor. + * @return the coefficients of the interpolation polynomial, the highest order + * term first and the constant last. + */ + public double[] interpolator(double... values) { + if (values.length != count) { + throw new IllegalArgumentException("Wrong number of arguments "+values.length+ + " expected "+count); + } + + double[] ret = new double[count]; + + for (int j=0; j < count; j++) { + for (int i=0; i < count; i++) { + ret[j] += interpolationMatrix[j][i] * values[i]; + } + } + + return ret; + } + + + /** + * Interpolate the given values at the point <code>x</code>. This is equivalent + * to generating an interpolation polynomial and evaluating the polynomial at the + * specified point. + * + * @param x point at which to evaluate the interpolation polynomial. + * @param values the function, derivatives etc. at position defined in the + * constructor. + * @return the value of the interpolation. + */ + public double interpolate(double x, double... values) { + return eval(x, interpolator(values)); + } + + + /** + * Evaluate a polynomial at the specified point <code>x</code>. The coefficients are + * assumed to have the highest order coefficient first and the constant term last. + * + * @param x position at which to evaluate the polynomial. + * @param coefficients polynomial coefficients, highest term first and constant last. + * @return the value of the polynomial. + */ + public static double eval(double x, double[] coefficients) { + double v = 1; + double result = 0; + for (int i = coefficients.length-1; i >= 0; i--) { + result += coefficients[i] * v; + v *= x; + } + return result; + } + + + + + private static double[][] inverse(double[][] matrix) { + int n = matrix.length; + + double x[][] = new double[n][n]; + double b[][] = new double[n][n]; + int index[] = new int[n]; + for (int i=0; i<n; ++i) + b[i][i] = 1; + + // Transform the matrix into an upper triangle + gaussian(matrix, index); + + // Update the matrix b[i][j] with the ratios stored + for (int i=0; i<n-1; ++i) + for (int j=i+1; j<n; ++j) + for (int k=0; k<n; ++k) + b[index[j]][k] -= matrix[index[j]][i]*b[index[i]][k]; + + // Perform backward substitutions + for (int i=0; i<n; ++i) { + x[n-1][i] = b[index[n-1]][i]/matrix[index[n-1]][n-1]; + for (int j=n-2; j>=0; --j) { + x[j][i] = b[index[j]][i]; + for (int k=j+1; k<n; ++k) { + x[j][i] -= matrix[index[j]][k]*x[k][i]; + } + x[j][i] /= matrix[index[j]][j]; + } + } + return x; + } + + private static void gaussian(double a[][], + int index[]) { + int n = index.length; + double c[] = new double[n]; + + // Initialize the index + for (int i=0; i<n; ++i) index[i] = i; + + // Find the rescaling factors, one from each row + for (int i=0; i<n; ++i) { + double c1 = 0; + for (int j=0; j<n; ++j) { + double c0 = Math.abs(a[i][j]); + if (c0 > c1) c1 = c0; + } + c[i] = c1; + } + + // Search the pivoting element from each column + int k = 0; + for (int j=0; j<n-1; ++j) { + double pi1 = 0; + for (int i=j; i<n; ++i) { + double pi0 = Math.abs(a[index[i]][j]); + pi0 /= c[index[i]]; + if (pi0 > pi1) { + pi1 = pi0; + k = i; + } + } + + // Interchange rows according to the pivoting order + int itmp = index[j]; + index[j] = index[k]; + index[k] = itmp; + for (int i=j+1; i<n; ++i) { + double pj = a[index[i]][j]/a[index[j]][j]; + + // Record pivoting ratios below the diagonal + a[index[i]][j] = pj; + + // Modify other elements accordingly + for (int l=j+1; l<n; ++l) + a[index[i]][l] -= pj*a[index[j]][l]; + } + } + } + + + + + public static void main(String[] arg) { + + PolyInterpolator p0 = new PolyInterpolator( + new double[] {0.6, 1.1}, + new double[] {0.6, 1.1} + ); + double[] r0 = p0.interpolator(1.5, 1.6, 2, -3); + + PolyInterpolator p1 = new PolyInterpolator( + new double[] {0.6, 1.1}, + new double[] {0.6, 1.1}, + new double[] {0.6} + ); + double[] r1 = p1.interpolator(1.5, 1.6, 2, -3, 0); + + PolyInterpolator p2 = new PolyInterpolator( + new double[] {0.6, 1.1}, + new double[] {0.6, 1.1}, + new double[] {0.6, 1.1} + ); + double[] r2 = p2.interpolator(1.5, 1.6, 2, -3, 0, 0); + + + for (double x=0.6; x <= 1.11; x += 0.01) { + System.out.println(x + " " + eval(x,r0) + " " + eval(x,r1) + " " + eval(x,r2)); + } + + } +} diff --git a/src/net/sf/openrocket/util/Prefs.java b/src/net/sf/openrocket/util/Prefs.java new file mode 100644 index 000000000..dbb31c286 --- /dev/null +++ b/src/net/sf/openrocket/util/Prefs.java @@ -0,0 +1,457 @@ +package net.sf.openrocket.util; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Point; +import java.awt.Toolkit; +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import java.util.prefs.BackingStoreException; +import java.util.prefs.Preferences; + +import net.sf.openrocket.database.Databases; +import net.sf.openrocket.document.Simulation; +import net.sf.openrocket.material.Material; +import net.sf.openrocket.rocketcomponent.BodyComponent; +import net.sf.openrocket.rocketcomponent.FinSet; +import net.sf.openrocket.rocketcomponent.InternalComponent; +import net.sf.openrocket.rocketcomponent.LaunchLug; +import net.sf.openrocket.rocketcomponent.MassObject; +import net.sf.openrocket.rocketcomponent.RecoveryDevice; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.simulation.RK4Simulator; +import net.sf.openrocket.simulation.SimulationConditions; +import net.sf.openrocket.unit.UnitGroup; + + +public class Prefs { + + /** + * Whether to use the debug-node instead of the normal node. + */ + public static final boolean DEBUG = false; + + /** + * Whether to clear all preferences at application startup. This has an effect only + * if DEBUG is true. + */ + public static final boolean CLEARPREFS = true; + + /** + * The node name to use in the Java preferences storage. + */ + public static final String NODENAME = (DEBUG?"OpenRocket-debug":"OpenRocket"); + + + + private static final String VERSION = "0.9.0"; + + + public static final String BODY_COMPONENT_INSERT_POSITION_KEY = "BodyComponentInsertPosition"; + + + public static final String CONFIRM_DELETE_SIMULATION = "ConfirmDeleteSimulation"; + + + /** + * Node to this application's preferences. + */ + public static final Preferences NODE; + + + static { + Preferences root = Preferences.userRoot(); + if (DEBUG && CLEARPREFS) { + try { + if (root.nodeExists(NODENAME)) { + root.node(NODENAME).removeNode(); + } + } catch (BackingStoreException e) { + throw new RuntimeException("Unable to clear preference node",e); + } + } + NODE = root.node(NODENAME); + } + + + + + ///////// Default component attributes + + private static final HashMap<Class<?>,String> DEFAULT_COLORS = + new HashMap<Class<?>,String>(); + static { + DEFAULT_COLORS.put(BodyComponent.class, "0,0,240"); + DEFAULT_COLORS.put(FinSet.class, "0,0,200"); + DEFAULT_COLORS.put(LaunchLug.class, "0,0,180"); + DEFAULT_COLORS.put(InternalComponent.class, "170,0,100"); + DEFAULT_COLORS.put(MassObject.class, "0,0,0"); + DEFAULT_COLORS.put(RecoveryDevice.class, "255,0,0"); + } + + + private static final HashMap<Class<?>,String> DEFAULT_LINE_STYLES = + new HashMap<Class<?>,String>(); + static { + DEFAULT_LINE_STYLES.put(RocketComponent.class, LineStyle.SOLID.name()); + DEFAULT_LINE_STYLES.put(MassObject.class, LineStyle.DASHED.name()); + } + + + private static final Material DEFAULT_LINE_MATERIAL = + Databases.findMaterial(Material.Type.LINE, "Elastic cord (round 2mm, 1/16 in)", 0.0018); + private static final Material DEFAULT_SURFACE_MATERIAL = + Databases.findMaterial(Material.Type.SURFACE, "Ripstop nylon", 0.067); + private static final Material DEFAULT_BULK_MATERIAL = + Databases.findMaterial(Material.Type.BULK, "Cardboard", 680); + + + ////////////////////// + + + public static String getVersion() { + return VERSION; + } + + + + public static void storeVersion() { + NODE.put("OpenRocketVersion", getVersion()); + } + + + /** + * Returns a limited-range integer value from the preferences. If the value + * in the preferences is negative or greater than max, then the default value + * is returned. + * + * @param key The preference to retrieve. + * @param max Maximum allowed value for the choice. + * @param def Default value. + * @return The preference value. + */ + public static int getChoise(String key, int max, int def) { + int v = NODE.getInt(key, def); + if ((v<0) || (v>max)) + return def; + return v; + } + + + /** + * Helper method that puts an integer choice value into the preferences. + * + * @param key the preference key. + * @param value the value to store. + */ + public static void putChoise(String key, int value) { + NODE.putInt(key, value); + storeVersion(); + } + + + + public static String getString(String key, String def) { + return NODE.get(key, def); + } + + public static void putString(String key, String value) { + NODE.put(key, value); + storeVersion(); + } + + + + + ////////////////// + + public static File getDefaultDirectory() { + String file = NODE.get("defaultDirectory", null); + if (file == null) + return null; + return new File(file); + } + + public static void setDefaultDirectory(File dir) { + String d; + if (dir == null) { + d = null; + } else { + d = dir.getAbsolutePath(); + } + NODE.put("defaultDirectory", d); + storeVersion(); + } + + + + public static Color getDefaultColor(Class<? extends RocketComponent> c) { + String color = get("componentColors", c, DEFAULT_COLORS); + if (color == null) + return Color.BLACK; + + String[] rgb = color.split(","); + if (rgb.length==3) { + try { + int red = MathUtil.clamp(Integer.parseInt(rgb[0]),0,255); + int green = MathUtil.clamp(Integer.parseInt(rgb[1]),0,255); + int blue = MathUtil.clamp(Integer.parseInt(rgb[2]),0,255); + return new Color(red,green,blue); + } catch (NumberFormatException ignore) { } + } + + return Color.BLACK; + } + + public static void setDefaultColor(Class<? extends RocketComponent> c, Color color) { + if (color==null) + return; + String string = color.getRed() + "," + color.getGreen() + "," + color.getBlue(); + set("componentColors", c, string); + } + + public static Color getMotorBorderColor() { + // TODO: MEDIUM: Motor color (settable?) + return new Color(0,0,0,200); + } + + + public static Color getMotorFillColor() { + // TODO: MEDIUM: Motor fill color (settable?) + return new Color(0,0,0,100); + } + + + public static LineStyle getDefaultLineStyle(Class<? extends RocketComponent> c) { + String value = get("componentStyle", c, DEFAULT_LINE_STYLES); + try { + return LineStyle.valueOf(value); + } catch (Exception e) { + return LineStyle.SOLID; + } + } + + public static void setDefaultLineStyle(Class<? extends RocketComponent> c, + LineStyle style) { + if (style == null) + return; + set("componentStyle", c, style.name()); + } + + + /** + * Return the DPI setting of the monitor. This is either the setting provided + * by the system or a user-specified DPI setting. + * + * @return the DPI setting to use. + */ + public static double getDPI() { + int dpi = NODE.getInt("DPI", 0); // Tenths of a dpi + + if (dpi < 10) { + dpi = Toolkit.getDefaultToolkit().getScreenResolution()*10; + } + if (dpi < 10) + dpi = 960; + + return ((double)dpi)/10.0; + } + + + public static double getDefaultMach() { + // TODO: HIGH: implement custom default mach number + return 0.3; + } + + + + + public static Material getDefaultComponentMaterial( + Class<? extends RocketComponent> componentClass, + Material.Type type) { + + String material = get("componentMaterials", componentClass, null); + if (material != null) { + try { + Material m = Material.fromStorableString(material); + if (m.getType() == type) + return m; + } catch (IllegalArgumentException ignore) { } + } + + switch (type) { + case LINE: + return DEFAULT_LINE_MATERIAL; + case SURFACE: + return DEFAULT_SURFACE_MATERIAL; + case BULK: + return DEFAULT_BULK_MATERIAL; + } + throw new IllegalArgumentException("Unknown material type: "+type); + } + + public static void setDefaultComponentMaterial( + Class<? extends RocketComponent> componentClass, Material material) { + + set("componentMaterials", componentClass, + material==null ? null : material.toStorableString()); + } + + + public static int getMaxThreadCount() { + return Runtime.getRuntime().availableProcessors(); + } + + + + public static Point getWindowPosition(Class<?> c) { + int x, y; + String pref = NODE.node("windows").get("position." + c.getCanonicalName(), null); + + if (pref == null) + return null; + + if (pref.indexOf(',')<0) + return null; + + try { + x = Integer.parseInt(pref.substring(0,pref.indexOf(','))); + y = Integer.parseInt(pref.substring(pref.indexOf(',')+1)); + } catch (NumberFormatException e) { + return null; + } + return new Point(x,y); + } + + public static void setWindowPosition(Class<?> c, Point p) { + NODE.node("windows").put("position." + c.getCanonicalName(), "" + p.x + "," + p.y); + storeVersion(); + } + + + + + public static Dimension getWindowSize(Class<?> c) { + int x, y; + String pref = NODE.node("windows").get("size." + c.getCanonicalName(), null); + + if (pref == null) + return null; + + if (pref.indexOf(',')<0) + return null; + + try { + x = Integer.parseInt(pref.substring(0,pref.indexOf(','))); + y = Integer.parseInt(pref.substring(pref.indexOf(',')+1)); + } catch (NumberFormatException e) { + return null; + } + return new Dimension(x,y); + } + + public static void setWindowSize(Class<?> c, Dimension d) { + NODE.node("windows").put("size." + c.getCanonicalName(), "" + d.width + "," + d.height); + storeVersion(); + } + + + //// Background flight data computation + + public static boolean computeFlightInBackground() { + return NODE.getBoolean("backgroundFlight", true); + } + + public static Simulation getBackgroundSimulation(Rocket rocket) { + Simulation s = new Simulation(rocket); + SimulationConditions cond = s.getConditions(); + + cond.setTimeStep(RK4Simulator.RECOMMENDED_TIME_STEP*2); + cond.setWindSpeedAverage(1.0); + cond.setWindSpeedDeviation(0.1); + cond.setLaunchRodLength(5); + return s; + } + + + + + ///////// Default unit storage + + public static void loadDefaultUnits() { + Preferences prefs = NODE.node("units"); + try { + + for (String key: prefs.keys()) { + UnitGroup group = UnitGroup.UNITS.get(key); + if (group == null) + continue; + + group.setDefaultUnit(prefs.get(key, null)); + } + + } catch (BackingStoreException e) { + System.err.println("BackingStoreException:"); + e.printStackTrace(); + } + } + + public static void storeDefaultUnits() { + Preferences prefs = NODE.node("units"); + + for (String key: UnitGroup.UNITS.keySet()) { + UnitGroup group = UnitGroup.UNITS.get(key); + if (group == null || group.getUnitCount() < 2) + continue; + + prefs.put(key, group.getDefaultUnit().getUnit()); + } + } + + + + //// Helper methods + + private static String get(String directory, + Class<? extends RocketComponent> componentClass, + Map<Class<?>, String> defaultMap) { + + // Search preferences + Class<?> c = componentClass; + Preferences prefs = NODE.node(directory); + while (c!=null && RocketComponent.class.isAssignableFrom(c)) { + String value = prefs.get(c.getSimpleName(), null); + if (value != null) + return value; + c = c.getSuperclass(); + } + + if (defaultMap == null) + return null; + + // Search defaults + c = componentClass; + while (RocketComponent.class.isAssignableFrom(c)) { + String value = defaultMap.get(c); + if (value != null) + return value; + c = c.getSuperclass(); + } + + return null; + } + + + private static void set(String directory, Class<? extends RocketComponent> componentClass, + String value) { + Preferences prefs = NODE.node(directory); + if (value == null) + prefs.remove(componentClass.getSimpleName()); + else + prefs.put(componentClass.getSimpleName(), value); + storeVersion(); + } + +} diff --git a/src/net/sf/openrocket/util/Quaternion.java b/src/net/sf/openrocket/util/Quaternion.java new file mode 100644 index 000000000..3c457caba --- /dev/null +++ b/src/net/sf/openrocket/util/Quaternion.java @@ -0,0 +1,292 @@ +package net.sf.openrocket.util; + + +public class Quaternion implements Cloneable { + + protected double w, x, y, z; + protected int modCount = 0; + + public Quaternion() { + this(1,0,0,0); + } + + public Quaternion(double w, double x, double y, double z) { + this.w = w; + this.x = x; + this.y = y; + this.z = z; + } + + + public static Quaternion rotation(Coordinate rotation) { + double length = rotation.length(); + if (length < 0.000001) { + return new Quaternion(1,0,0,0); + } + double sin = Math.sin(length/2); + double cos = Math.cos(length/2); + return new Quaternion(cos, + sin*rotation.x/length, sin*rotation.y/length, sin*rotation.z/length); + } + + public static Quaternion rotation(Coordinate axis, double angle) { + Coordinate a = axis.normalize(); + double sin = Math.sin(angle); + double cos = Math.cos(angle); + return new Quaternion(cos, sin*a.x, sin*a.y, sin*a.z); + } + + + public double getW() { + return w; + } + + public void setW(double w) { + this.w = w; + modCount++; + } + + public double getX() { + return x; + } + + public void setX(double x) { + this.x = x; + modCount++; + } + + public double getY() { + return y; + } + + public void setY(double y) { + this.y = y; + modCount++; + } + + public double getZ() { + return z; + } + + public void setZ(double z) { + this.z = z; + modCount++; + } + + + public void setAll(double w, double x, double y, double z) { + this.w = w; + this.x = x; + this.y = y; + this.z = z; + modCount++; + } + + + /** + * Multiply this quaternion by the other quaternion from the right side. This + * calculates the product <code>this = this * other</code>. + * + * @param other the quaternion to multiply this quaternion by. + * @return this quaternion. + */ + public Quaternion multiplyRight(Quaternion other) { + double w = (this.w*other.w - this.x*other.x - this.y*other.y - this.z*other.z); + double x = (this.w*other.x + this.x*other.w + this.y*other.z - this.z*other.y); + double y = (this.w*other.y + this.y*other.w + this.z*other.x - this.x*other.z); + double z = (this.w*other.z + this.z*other.w + this.x*other.y - this.y*other.x); + + this.w = w; + this.x = x; + this.y = y; + this.z = z; + return this; + } + + /** + * Multiply this quaternion by the other quaternion from the left side. This + * calculates the product <code>this = other * this</code>. + * + * @param other the quaternion to multiply this quaternion by. + * @return this quaternion. + */ + public Quaternion multiplyLeft(Quaternion other) { + /* other(abcd) * this(wxyz) */ + + double w = (other.w*this.w - other.x*this.x - other.y*this.y - other.z*this.z); + double x = (other.w*this.x + other.x*this.w + other.y*this.z - other.z*this.y); + double y = (other.w*this.y + other.y*this.w + other.z*this.x - other.x*this.z); + double z = (other.w*this.z + other.z*this.w + other.x*this.y - other.y*this.x); + + this.w = w; + this.x = x; + this.y = y; + this.z = z; + return this; + } + + + + + + + @Override + public Quaternion clone() { + try { + return (Quaternion) super.clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeException("CloneNotSupportedException encountered"); + } + } + + + + /** + * Normalize this quaternion. After the call the norm of the quaternion is exactly + * one. If this quaternion is the zero quaternion, throws + * <code>IllegalStateException</code>. Returns this quaternion. + * + * @return this quaternion. + * @throws IllegalStateException if the norm of this quaternion is zero. + */ + public Quaternion normalize() { + double norm = norm(); + if (norm < 0.0000001) { + throw new IllegalStateException("attempting to normalize zero-quaternion"); + } + x /= norm; + y /= norm; + z /= norm; + w /= norm; + return this; + } + + + /** + * Normalize this quaternion if the norm is more than 1ppm from one. + * + * @return this quaternion. + * @throws IllegalStateException if the norm of this quaternion is zero. + */ + public Quaternion normalizeIfNecessary() { + double n2 = norm2(); + if (n2 < 0.999999 || n2 > 1.000001) + normalize(); + return this; + } + + + + /** + * Return the norm of this quaternion. + * + * @return the norm of this quaternion sqrt(w^2 + x^2 + y^2 + z^2). + */ + public double norm() { + return Math.sqrt(x*x + y*y + z*z + w*w); + } + + /** + * Return the square of the norm of this quaternion. + * + * @return the square of the norm of this quaternion (w^2 + x^2 + y^2 + z^2). + */ + public double norm2() { + return x*x + y*y + z*z + w*w; + } + + + public Coordinate rotate(Coordinate coord) { + double a,b,c,d; + + assert(Math.abs(norm2()-1) < 0.00001) : "Quaternion not unit length: "+this; + + a = - x * coord.x - y * coord.y - z * coord.z; // w + b = w * coord.x + y * coord.z - z * coord.y; // x i + c = w * coord.y - x * coord.z + z * coord.x; // y j + d = w * coord.z + x * coord.y - y * coord.x; // z k + + assert(MathUtil.equals(a*w + b*x + c*y + d*z, 0)) : + ("Should be zero: " + (a*w - b*x - c*y - d*z) + " in " + this + " c=" + coord); + + return new Coordinate( + - a*x + b*w - c*z + d*y, + - a*y + b*z + c*w - d*x, + - a*z - b*y + c*x + d*w, + coord.weight + ); + } + + public Coordinate invRotate(Coordinate coord) { + double a,b,c,d; + + assert(Math.abs(norm2()-1) < 0.00001) : "Quaternion not unit length: "+this; + + a = + x * coord.x + y * coord.y + z * coord.z; + b = w * coord.x - y * coord.z + z * coord.y; + c = w * coord.y + x * coord.z - z * coord.x; + d = w * coord.z - x * coord.y + y * coord.x; + + assert(MathUtil.equals(a*w - b*x - c*y - d*z, 0)): + ("Should be zero: " + (a*w - b*x - c*y - d*z) + " in " + this + " c=" + coord); + + return new Coordinate( + a*x + b*w + c*z - d*y, + a*y - b*z + c*w + d*x, + a*z + b*y - c*x + d*w, + coord.weight + ); + } + + + /** + * Rotate the coordinate (0,0,1) using this quaternion. The result is returned + * as a Coordinate. This method is equivalent to calling + * <code>q.rotate(new Coordinate(0,0,1))</code> but requires only about half of the + * multiplications. + * + * @return The coordinate (0,0,1) rotated using this quaternion. + */ + public Coordinate rotateZ() { + return new Coordinate( + 2*(w*y + x*z), + 2*(y*z - w*x), + w*w - x*x - y*y + z*z + ); + } + + + @Override + public String toString() { + return String.format("Quaternion[%f,%f,%f,%f,norm=%f]",w,x,y,z,this.norm()); + } + + public static void main(String[] arg) { + + Quaternion q = new Quaternion(Math.random()-0.5,Math.random()-0.5, + Math.random()-0.5,Math.random()-0.5); + q.normalize(); + + q = new Quaternion(-0.998717,0.000000,0.050649,-0.000000); + + Coordinate coord = new Coordinate(10*(Math.random()-0.5), + 10*(Math.random()-0.5), 10*(Math.random()-0.5)); + + System.out.println("Quaternion: "+q); + System.out.println("Coordinate: "+coord); + coord = q.invRotate(coord); + System.out.println("Rotated: "+ coord); + coord = q.rotate(coord); + System.out.println("Back: "+coord); + +// Coordinate c = new Coordinate(0,1,0); +// Coordinate rot = new Coordinate(Math.PI/4,0,0); +// +// System.out.println("Before: "+c); +// c = rotation(rot).invRotate(c); +// System.out.println("After: "+c); + + + } + +} diff --git a/src/net/sf/openrocket/util/QuaternionMultiply.java b/src/net/sf/openrocket/util/QuaternionMultiply.java new file mode 100644 index 000000000..099ecf8d7 --- /dev/null +++ b/src/net/sf/openrocket/util/QuaternionMultiply.java @@ -0,0 +1,90 @@ +package net.sf.openrocket.util; + +public class QuaternionMultiply { + + private static class Value { + public int sign = 1; + public String value; + + public Value multiply(Value other) { + Value result = new Value(); + result.sign = this.sign * other.sign; + if (this.value.compareTo(other.value) < 0) + result.value = this.value + "*" + other.value; + else + result.value = other.value + "*" + this.value; + return result; + } + @Override + public String toString() { + String s; + + if (sign < 0) + s = "-"; + else + s = "+"; + + if (sign == 0) + s += " 0"; + else + s += " " + value; + + return s; + } + } + + + + private static Value[] multiply(Value[] first, Value[] second) { + return null; + } + + + public static void main(String[] arg) { + if (arg.length % 4 != 0 || arg.length < 4) { + System.out.println("Must have modulo 4 args, at least 4"); + return; + } + + Value[][] values = new Value[arg.length/4][4]; + + for (int i=0; i<arg.length; i++) { + Value value = new Value(); + + if (arg[i].equals("")) { + value.sign = 0; + } else { + if (arg[i].startsWith("-")) { + value.sign = -1; + value.value = arg[i].substring(1); + } else if (arg[i].startsWith("+")) { + value.sign = 1; + value.value = arg[i].substring(1); + } else { + value.sign = 1; + value.value = arg[i]; + } + } + + values[i/4][i%4] = value; + } + + System.out.println("Multiplying:"); + for (int i=0; i < values.length; i++) { + print(values[i]); + } + System.out.println("Result:"); + + Value[] result = values[0]; + for (int i=1; i < values.length; i++) { + result = multiply(result, values[i]); + } + print(result); + } + + private static void print(Value[] q) { + System.out.println(" " + q[0] + " " + q[1] + " i " + q[2] + " j " + q[3] + " k"); + } + +} + diff --git a/src/net/sf/openrocket/util/Reflection.java b/src/net/sf/openrocket/util/Reflection.java new file mode 100644 index 000000000..26c00beff --- /dev/null +++ b/src/net/sf/openrocket/util/Reflection.java @@ -0,0 +1,162 @@ +package net.sf.openrocket.util; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +import net.sf.openrocket.rocketcomponent.RocketComponent; + + +public class Reflection { + + private static final String ROCKETCOMPONENT_PACKAGE = "net.sf.openrocket.rocketcomponent"; + + /** + * Simple wrapper class that converts the Method.invoke() exceptions into suitable + * RuntimeExceptions. + * + * @author Sampo Niskanen <sampo.niskanen@iki.fi> + */ + public static class Method { + private final java.lang.reflect.Method method; + public Method(java.lang.reflect.Method m) { + method = m; + } + /** + * Same as Method.invoke(), but the possible exceptions are wrapped into + * RuntimeExceptions. + */ + public Object invoke(Object obj, Object... args) { + try { + return method.invoke(obj, args); + } catch (IllegalArgumentException e) { + throw new RuntimeException("Error while invoking method '"+method+"'. "+ + "Please report this as a bug.",e); + } catch (IllegalAccessException e) { + throw new RuntimeException("Error while invoking method '"+method+"'. "+ + "Please report this as a bug.",e); + } catch (InvocationTargetException e) { + throw new RuntimeException("Error while invoking method '"+method+"'. "+ + "Please report this as a bug.",e); + } + } + /** + * Invoke static method. Equivalent to invoke(null, args...). + */ + public Object invokeStatic(Object... args) { + return invoke(null,args); + } + /** + * Same as Method.toString(). + */ + @Override + public String toString() { + return method.toString(); + } + } + + + /** + * Throws an exception if method not found. + */ + public static Reflection.Method findMethodStatic( + Class<? extends RocketComponent> componentClass, + String method, Class<?>... params) { + Reflection.Method m = findMethod(ROCKETCOMPONENT_PACKAGE, componentClass, + "", method, params); + if (m == null) { + throw new RuntimeException("Could not find method for componentClass=" + +componentClass+" method="+method); + } + return m; + } + + + + public static Reflection.Method findMethod(String pack, RocketComponent component, + String method, Class<?>...params) { + return findMethod(pack,component.getClass(),"",method,params); + } + + + public static Reflection.Method findMethod(String pack, RocketComponent component, + String suffix, String method, Class<?>... params) { + return findMethod(pack, component.getClass(), suffix, method, params); + } + + + public static Reflection.Method findMethod(String pack, + Class<? extends RocketComponent> componentClass, + String suffix, String method, Class<?>... params) { + Class<?> currentclass; + String name; + + currentclass = componentClass; + while ((currentclass != null) && (currentclass != Object.class)) { + name = currentclass.getCanonicalName(); + if (name.lastIndexOf('.')>=0) + name = name.substring(name.lastIndexOf(".")+1); + name = pack + "." + name + suffix; + + try { + Class<?> c = Class.forName(name); + java.lang.reflect.Method m = c.getMethod(method,params); + return new Reflection.Method(m); + } catch (ClassNotFoundException ignore) { + } catch (NoSuchMethodException ignore) { + } + + currentclass = currentclass.getSuperclass(); + } + return null; + } + + + public static Object construct(String pack, RocketComponent component, String suffix, + Object... params) { + + Class<?> currentclass; + String name; + + currentclass = component.getClass(); + while ((currentclass != null) && (currentclass != Object.class)) { + name = currentclass.getCanonicalName(); + if (name.lastIndexOf('.')>=0) + name = name.substring(name.lastIndexOf(".")+1); + name = pack + "." + name + suffix; + + try { + Class<?> c = Class.forName(name); + Class<?>[] paramClasses = new Class<?>[params.length]; + for (int i=0; i < params.length; i++) { + paramClasses[i] = params[i].getClass(); + } + + // Constructors must be searched manually. Why?! + main: for (Constructor<?> constructor: c.getConstructors()) { + Class<?>[] parameterTypes = constructor.getParameterTypes(); + if (params.length != parameterTypes.length) + continue; + for (int i=0; i < params.length; i++) { + if (!parameterTypes[i].isInstance(params[i])) + continue main; + } + // Matching constructor found + return constructor.newInstance(params); + } + } catch (ClassNotFoundException ignore) { + } catch (IllegalArgumentException e) { + throw new RuntimeException("Construction of "+name+" failed",e); + } catch (InstantiationException e) { + throw new RuntimeException("Construction of "+name+" failed",e); + } catch (IllegalAccessException e) { + throw new RuntimeException("Construction of "+name+" failed",e); + } catch (InvocationTargetException e) { + throw new RuntimeException("Construction of "+name+" failed",e); + } + + currentclass = currentclass.getSuperclass(); + } + throw new RuntimeException("Suitable constructor for component "+component+ + " not found"); + } +} diff --git a/src/net/sf/openrocket/util/Rotation2D.java b/src/net/sf/openrocket/util/Rotation2D.java new file mode 100644 index 000000000..f8f063e8d --- /dev/null +++ b/src/net/sf/openrocket/util/Rotation2D.java @@ -0,0 +1,58 @@ +package net.sf.openrocket.util; + +public class Rotation2D { + + public static final Rotation2D ID = new Rotation2D(0.0, 1.0); + + public final double sin, cos; + + + public Rotation2D(double angle) { + this(Math.sin(angle), Math.cos(angle)); + } + + public Rotation2D(double sin, double cos) { + this.sin = sin; + this.cos = cos; + } + + public Coordinate rotateX(Coordinate c) { + return new Coordinate(c.x, cos*c.y - sin*c.z, cos*c.z + sin*c.y, c.weight); + } + + public Coordinate rotateY(Coordinate c) { + return new Coordinate(cos*c.x + sin*c.z, c.y, cos*c.z - sin*c.x, c.weight); + } + + public Coordinate rotateZ(Coordinate c) { + return new Coordinate(cos*c.x - sin*c.y, cos*c.y + sin*c.x, c.z, c.weight); + } + + + public Coordinate invRotateX(Coordinate c) { + return new Coordinate(c.x, cos*c.y + sin*c.z, cos*c.z - sin*c.y, c.weight); + } + + public Coordinate invRotateY(Coordinate c) { + return new Coordinate(cos*c.x - sin*c.z, c.y, cos*c.z + sin*c.x, c.weight); + } + + public Coordinate invRotateZ(Coordinate c) { + return new Coordinate(cos*c.x + sin*c.y, cos*c.y - sin*c.x, c.z, c.weight); + } + + + + public static void main(String arg[]) { + Coordinate c = new Coordinate(1,1,1,2.5); + Rotation2D rot = new Rotation2D(Math.PI/4); + + System.out.println("X: "+rot.rotateX(c)); + System.out.println("Y: "+rot.rotateY(c)); + System.out.println("Z: "+rot.rotateZ(c)); + System.out.println("invX: "+rot.invRotateX(c)); + System.out.println("invY: "+rot.invRotateY(c)); + System.out.println("invZ: "+rot.invRotateZ(c)); + } + +} diff --git a/src/net/sf/openrocket/util/Test.java b/src/net/sf/openrocket/util/Test.java new file mode 100644 index 000000000..8f039555c --- /dev/null +++ b/src/net/sf/openrocket/util/Test.java @@ -0,0 +1,411 @@ +package net.sf.openrocket.util; + +import net.sf.openrocket.database.Databases; +import net.sf.openrocket.material.Material; +import net.sf.openrocket.rocketcomponent.BodyTube; +import net.sf.openrocket.rocketcomponent.Bulkhead; +import net.sf.openrocket.rocketcomponent.CenteringRing; +import net.sf.openrocket.rocketcomponent.FreeformFinSet; +import net.sf.openrocket.rocketcomponent.InnerTube; +import net.sf.openrocket.rocketcomponent.LaunchLug; +import net.sf.openrocket.rocketcomponent.MassComponent; +import net.sf.openrocket.rocketcomponent.Motor; +import net.sf.openrocket.rocketcomponent.NoseCone; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.rocketcomponent.Stage; +import net.sf.openrocket.rocketcomponent.Transition; +import net.sf.openrocket.rocketcomponent.TrapezoidFinSet; +import net.sf.openrocket.rocketcomponent.TubeCoupler; +import net.sf.openrocket.rocketcomponent.FinSet.CrossSection; +import net.sf.openrocket.rocketcomponent.RocketComponent.Position; + +public class Test { + + public static double noseconeLength=0.10,noseconeRadius=0.01; + public static double bodytubeLength=0.20,bodytubeRadius=0.01,bodytubeThickness=0.001; + + public static int finCount=3; + public static double finRootChord=0.04,finTipChord=0.05,finSweep=0.01,finThickness=0.003, finHeight=0.03; + + public static double materialDensity=1000; // kg/m3 + + + public static Rocket makeRocket() { + Rocket rocket; + Stage stage,stage2; + NoseCone nosecone; + BodyTube bodytube, bt2; + Transition transition; + TrapezoidFinSet finset; + + rocket = new Rocket(); + stage = new Stage(); + stage.setName("Stage1"); + stage2 = new Stage(); + stage2.setName("Stage2"); + nosecone = new NoseCone(Transition.Shape.ELLIPSOID,noseconeLength,noseconeRadius); + bodytube = new BodyTube(bodytubeLength,bodytubeRadius,bodytubeThickness); + transition = new Transition(); + bt2 = new BodyTube(bodytubeLength,bodytubeRadius*2,bodytubeThickness); + bt2.setMotorMount(true); + + finset = new TrapezoidFinSet(finCount,finRootChord,finTipChord,finSweep,finHeight); + + + // Stage construction + rocket.addChild(stage); + rocket.addChild(stage2); + + + // Component construction + stage.addChild(nosecone); + + stage.addChild(bodytube); + + + stage2.addChild(transition); + + stage2.addChild(bt2); + + bodytube.addChild(finset); + + + rocket.getDefaultConfiguration().setAllStages(); + + return rocket; + } + + + public static Rocket makeSmallFlyable() { + Rocket rocket; + Stage stage; + NoseCone nosecone; + BodyTube bodytube; + TrapezoidFinSet finset; + + rocket = new Rocket(); + stage = new Stage(); + stage.setName("Stage1"); + + nosecone = new NoseCone(Transition.Shape.ELLIPSOID,noseconeLength,noseconeRadius); + bodytube = new BodyTube(bodytubeLength,bodytubeRadius,bodytubeThickness); + + finset = new TrapezoidFinSet(finCount,finRootChord,finTipChord,finSweep,finHeight); + + + // Stage construction + rocket.addChild(stage); + + + // Component construction + stage.addChild(nosecone); + stage.addChild(bodytube); + + bodytube.addChild(finset); + + Material material = Prefs.getDefaultComponentMaterial(null, Material.Type.BULK); + nosecone.setMaterial(material); + bodytube.setMaterial(material); + finset.setMaterial(material); + + String id = rocket.newMotorConfigurationID(); + bodytube.setMotorMount(true); + + for (Motor m: Databases.MOTOR) { + if (m.getDesignation().equals("B4")) { + bodytube.setMotor(id, m); + break; + } + } + bodytube.setMotorOverhang(0.005); + rocket.getDefaultConfiguration().setMotorConfigurationID(id); + + rocket.getDefaultConfiguration().setAllStages(); + + + return rocket; + } + + + public static Rocket makeBigBlue() { + Rocket rocket; + Stage stage; + NoseCone nosecone; + BodyTube bodytube; + FreeformFinSet finset; + MassComponent mcomp; + + rocket = new Rocket(); + stage = new Stage(); + stage.setName("Stage1"); + + nosecone = new NoseCone(Transition.Shape.ELLIPSOID,0.105,0.033); + nosecone.setThickness(0.001); + bodytube = new BodyTube(0.69,0.033,0.001); + + finset = new FreeformFinSet(); + finset.setPoints(new Coordinate[] { + new Coordinate(0, 0), + new Coordinate(0.115, 0.072), + new Coordinate(0.255, 0.072), + new Coordinate(0.255, 0.037), + new Coordinate(0.150, 0) + }); + finset.setThickness(0.003); + finset.setFinCount(4); + + finset.setCantAngle(0*Math.PI/180); + System.err.println("Fin cant angle: "+(finset.getCantAngle() * 180/Math.PI)); + + mcomp = new MassComponent(0.2,0.03,0.045 + 0.060); + mcomp.setRelativePosition(Position.TOP); + mcomp.setPositionValue(0); + + // Stage construction + rocket.addChild(stage); + rocket.setPerfectFinish(false); + + + // Component construction + stage.addChild(nosecone); + stage.addChild(bodytube); + + bodytube.addChild(finset); + + bodytube.addChild(mcomp); + +// Material material = new Material("Test material", 500); +// nosecone.setMaterial(material); +// bodytube.setMaterial(material); +// finset.setMaterial(material); + + String id = rocket.newMotorConfigurationID(); + bodytube.setMotorMount(true); + + for (Motor m: Databases.MOTOR) { + if (m.getDesignation().equals("F12J")) { + bodytube.setMotor(id, m); + break; + } + } + bodytube.setMotorOverhang(0.005); + rocket.getDefaultConfiguration().setMotorConfigurationID(id); + + rocket.getDefaultConfiguration().setAllStages(); + + + return rocket; + } + + + + public static Rocket makeIsoHaisu() { + Rocket rocket; + Stage stage; + NoseCone nosecone; + BodyTube tube1, tube2, tube3; + TrapezoidFinSet finset; + TrapezoidFinSet auxfinset; + MassComponent mcomp; + + final double R = 0.07; + + rocket = new Rocket(); + stage = new Stage(); + stage.setName("Stage1"); + + nosecone = new NoseCone(Transition.Shape.OGIVE,0.53,R); + nosecone.setThickness(0.005); + nosecone.setMassOverridden(true); + nosecone.setOverrideMass(0.588); + stage.addChild(nosecone); + + tube1 = new BodyTube(0.505,R,0.005); + tube1.setMassOverridden(true); + tube1.setOverrideMass(0.366); + stage.addChild(tube1); + + tube2 = new BodyTube(0.605,R,0.005); + tube2.setMassOverridden(true); + tube2.setOverrideMass(0.427); + stage.addChild(tube2); + + tube3 = new BodyTube(1.065,R,0.005); + tube3.setMassOverridden(true); + tube3.setOverrideMass(0.730); + stage.addChild(tube3); + + + LaunchLug lug = new LaunchLug(); + tube1.addChild(lug); + + TubeCoupler coupler = new TubeCoupler(); + coupler.setOuterRadiusAutomatic(true); + coupler.setThickness(0.005); + coupler.setLength(0.28); + coupler.setMassOverridden(true); + coupler.setOverrideMass(0.360); + coupler.setRelativePosition(Position.BOTTOM); + coupler.setPositionValue(-0.14); + tube1.addChild(coupler); + + + // Parachute + MassComponent mass = new MassComponent(0.05, 0.05, 0.280); + mass.setRelativePosition(Position.TOP); + mass.setPositionValue(0.2); + tube1.addChild(mass); + + // Cord + mass = new MassComponent(0.05, 0.05, 0.125); + mass.setRelativePosition(Position.TOP); + mass.setPositionValue(0.2); + tube1.addChild(mass); + + // Payload + mass = new MassComponent(0.40, R, 1.500); + mass.setRelativePosition(Position.TOP); + mass.setPositionValue(0.25); + tube1.addChild(mass); + + + auxfinset = new TrapezoidFinSet(); + auxfinset.setName("CONTROL"); + auxfinset.setFinCount(2); + auxfinset.setRootChord(0.05); + auxfinset.setTipChord(0.05); + auxfinset.setHeight(0.10); + auxfinset.setSweep(0); + auxfinset.setThickness(0.008); + auxfinset.setCrossSection(CrossSection.AIRFOIL); + auxfinset.setRelativePosition(Position.TOP); + auxfinset.setPositionValue(0.28); + auxfinset.setBaseRotation(Math.PI/2); + tube1.addChild(auxfinset); + + + + + coupler = new TubeCoupler(); + coupler.setOuterRadiusAutomatic(true); + coupler.setLength(0.28); + coupler.setRelativePosition(Position.TOP); + coupler.setPositionValue(0.47); + coupler.setMassOverridden(true); + coupler.setOverrideMass(0.360); + tube2.addChild(coupler); + + + + // Parachute + mass = new MassComponent(0.1, 0.05, 0.028); + mass.setRelativePosition(Position.TOP); + mass.setPositionValue(0.14); + tube2.addChild(mass); + + Bulkhead bulk = new Bulkhead(); + bulk.setOuterRadiusAutomatic(true); + bulk.setMassOverridden(true); + bulk.setOverrideMass(0.050); + bulk.setRelativePosition(Position.TOP); + bulk.setPositionValue(0.27); + tube2.addChild(bulk); + + // Chord + mass = new MassComponent(0.1, 0.05, 0.125); + mass.setRelativePosition(Position.TOP); + mass.setPositionValue(0.19); + tube2.addChild(mass); + + + + InnerTube inner = new InnerTube(); + inner.setOuterRadius(0.08/2); + inner.setInnerRadius(0.0762/2); + inner.setLength(0.86); + inner.setMassOverridden(true); + inner.setOverrideMass(0.388); + tube3.addChild(inner); + + + CenteringRing center = new CenteringRing(); + center.setInnerRadiusAutomatic(true); + center.setOuterRadiusAutomatic(true); + center.setLength(0.005); + center.setMassOverridden(true); + center.setOverrideMass(0.038); + center.setRelativePosition(Position.BOTTOM); + center.setPositionValue(0); + tube3.addChild(center); + + + center = new CenteringRing(); + center.setInnerRadiusAutomatic(true); + center.setOuterRadiusAutomatic(true); + center.setLength(0.005); + center.setMassOverridden(true); + center.setOverrideMass(0.038); + center.setRelativePosition(Position.TOP); + center.setPositionValue(0.28); + tube3.addChild(center); + + + center = new CenteringRing(); + center.setInnerRadiusAutomatic(true); + center.setOuterRadiusAutomatic(true); + center.setLength(0.005); + center.setMassOverridden(true); + center.setOverrideMass(0.038); + center.setRelativePosition(Position.TOP); + center.setPositionValue(0.83); + tube3.addChild(center); + + + + + + finset = new TrapezoidFinSet(); + finset.setRootChord(0.495); + finset.setTipChord(0.1); + finset.setHeight(0.185); + finset.setThickness(0.005); + finset.setSweep(0.3); + finset.setRelativePosition(Position.BOTTOM); + finset.setPositionValue(-0.03); + finset.setBaseRotation(Math.PI/2); + tube3.addChild(finset); + + + finset.setCantAngle(0*Math.PI/180); + System.err.println("Fin cant angle: "+(finset.getCantAngle() * 180/Math.PI)); + + + // Stage construction + rocket.addChild(stage); + rocket.setPerfectFinish(false); + + + + String id = rocket.newMotorConfigurationID(); + tube3.setMotorMount(true); + + for (Motor m: Databases.MOTOR) { + if (m.getDesignation().equals("L540")) { + tube3.setMotor(id, m); + break; + } + } + tube3.setMotorOverhang(0.02); + rocket.getDefaultConfiguration().setMotorConfigurationID(id); + +// tube3.setIgnitionEvent(MotorMount.IgnitionEvent.NEVER); + + rocket.getDefaultConfiguration().setAllStages(); + + + return rocket; + } + + + +} diff --git a/src/net/sf/openrocket/util/Transformation.java b/src/net/sf/openrocket/util/Transformation.java new file mode 100644 index 000000000..d263c42ca --- /dev/null +++ b/src/net/sf/openrocket/util/Transformation.java @@ -0,0 +1,263 @@ +package net.sf.openrocket.util; + +import java.util.*; + +/** + * Defines an affine transformation of the form A*x+c, where x and c are Coordinates and + * A is a 3x3 matrix. + * + * The Transformations are immutable. All modification methods return a new transformation. + * + * @author Sampo Niskanen <sampo.niskanen@iki.fi> + */ + +public class Transformation implements java.io.Serializable { + + + public static final Transformation IDENTITY = + new Transformation(); + + public static final Transformation PROJECT_XY = + new Transformation(new double[][]{{1,0,0},{0,1,0},{0,0,0}}); + public static final Transformation PROJECT_YZ = + new Transformation(new double[][]{{0,0,0},{0,1,0},{0,0,1}}); + public static final Transformation PROJECT_XZ = + new Transformation(new double[][]{{1,0,0},{0,0,0},{0,0,1}}); + + private static final int X = 0; + private static final int Y = 1; + private static final int Z = 2; + + private final Coordinate translate; + private final double[][] rotation = new double[3][3]; + + /** + * Create identity transformation. + */ + public Transformation() { + translate = new Coordinate(0,0,0); + rotation[X][X]=1; + rotation[Y][Y]=1; + rotation[Z][Z]=1; + } + + /** + * Create transformation with only translation. + * @param x Translation in x-axis. + * @param y Translation in y-axis. + * @param z Translation in z-axis. + */ + public Transformation(double x,double y,double z) { + translate = new Coordinate(x,y,z); + rotation[X][X]=1; + rotation[Y][Y]=1; + rotation[Z][Z]=1; + } + + /** + * Create transformation with only translation. + * @param translation The translation term. + */ + public Transformation(Coordinate translation) { + this.translate = translation; + rotation[X][X]=1; + rotation[Y][Y]=1; + rotation[Z][Z]=1; + } + + /** + * Create transformation with given rotation matrix and translation. + * @param rotation + * @param translation + */ + public Transformation(double[][] rotation, Coordinate translation) { + for (int i=0; i<3; i++) + for (int j=0; j<3; j++) + this.rotation[i][j] = rotation[i][j]; + this.translate = translation; + } + + + /** + * Create transformation with given rotation matrix and translation. + * @param rotation + * @param translation + */ + public Transformation(double[][] rotation) { + for (int i=0; i<3; i++) + for (int j=0; j<3; j++) + this.rotation[i][j] = rotation[i][j]; + this.translate = Coordinate.NUL; + } + + + + + + /** + * Transform a coordinate according to this transformation. + * + * @param orig the coordinate to transform. + * @return the result. + */ + public Coordinate transform(Coordinate orig) { + final double x,y,z; + + x = rotation[X][X]*orig.x + rotation[X][Y]*orig.y + rotation[X][Z]*orig.z + translate.x; + y = rotation[Y][X]*orig.x + rotation[Y][Y]*orig.y + rotation[Y][Z]*orig.z + translate.y; + z = rotation[Z][X]*orig.x + rotation[Z][Y]*orig.y + rotation[Z][Z]*orig.z + translate.z; + + return new Coordinate(x,y,z,orig.weight); + } + + + /** + * Transform an array of coordinates. The transformed coordinates are stored + * in the same array, and the array is returned. + * + * @param orig the coordinates to transform. + * @return <code>orig</code>, with the coordinates transformed. + */ + public Coordinate[] transform(Coordinate[] orig) { + for (int i=0; i < orig.length; i++) { + orig[i] = transform(orig[i]); + } + return orig; + } + + /** + * Transforms all coordinates in a Collection. The original coordinate elements are + * removed from the set and replaced with the transformed ones. The Collection given + * must implement the .clear() and .addAll() methods. + * + * @param set Collection of coordinates to transform. + */ + public void transform(Collection<Coordinate> set) { + ArrayList<Coordinate> temp = new ArrayList<Coordinate>(set.size()); + Iterator<Coordinate> iter = set.iterator(); + while (iter.hasNext()) + temp.add(this.transform(iter.next())); + set.clear(); + set.addAll(temp); + } + + /** + * Applies only the linear transformation A*x + * @param orig Coordinate to transform. + */ + public Coordinate linearTransform(Coordinate orig) { + final double x,y,z; + + x = rotation[X][X]*orig.x + rotation[X][Y]*orig.y + rotation[X][Z]*orig.z; + y = rotation[Y][X]*orig.x + rotation[Y][Y]*orig.y + rotation[Y][Z]*orig.z; + z = rotation[Z][X]*orig.x + rotation[Z][Y]*orig.y + rotation[Z][Z]*orig.z; + + return new Coordinate(x,y,z,orig.weight); + } + + /** + * Applies the given transformation before this tranformation. The resulting + * transformation result.transform(c) will equal this.transform(other.transform(c)). + * + * @param other Transformation to apply + * @return The new transformation + */ + public Transformation applyTransformation(Transformation other) { + // other = Ax+b + // this = Cx+d + // C(Ax+b)+d = CAx + Cb+d + + // Translational portion + Transformation combined = new Transformation( + this.linearTransform(other.translate).add(this.translate) + ); + + // Linear portion + for (int i=0; i<3; i++) { + final double x,y,z; + x = rotation[i][X]; + y = rotation[i][Y]; + z = rotation[i][Z]; + combined.rotation[i][X] = + x*other.rotation[X][X] + y*other.rotation[Y][X] + z*other.rotation[Z][X]; + combined.rotation[i][Y] = + x*other.rotation[X][Y] + y*other.rotation[Y][Y] + z*other.rotation[Z][Y]; + combined.rotation[i][Z] = + x*other.rotation[X][Z] + y*other.rotation[Y][Z] + z*other.rotation[Z][Z]; + } + return combined; + } + + + /** + * Rotate around x-axis a given angle. + * @param theta The angle to rotate in radians. + * @return The transformation. + */ + public static Transformation rotate_x(double theta) { + return new Transformation(new double[][]{ + {1,0,0}, + {0,Math.cos(theta),-Math.sin(theta)}, + {0,Math.sin(theta),Math.cos(theta)}}); + } + + /** + * Rotate around y-axis a given angle. + * @param theta The angle to rotate in radians. + * @return The transformation. + */ + public static Transformation rotate_y(double theta) { + return new Transformation(new double[][]{ + {Math.cos(theta),0,Math.sin(theta)}, + {0,1,0}, + {-Math.sin(theta),0,Math.cos(theta)}}); + } + + /** + * Rotate around z-axis a given angle. + * @param theta The angle to rotate in radians. + * @return The transformation. + */ + public static Transformation rotate_z(double theta) { + return new Transformation(new double[][]{ + {Math.cos(theta),-Math.sin(theta),0}, + {Math.sin(theta),Math.cos(theta),0}, + {0,0,1}}); + } + + + + public void print(String... str) { + for (String s: str) { + System.out.println(s); + } + System.out.printf("[%3.2f %3.2f %3.2f] [%3.2f]\n", + rotation[X][X],rotation[X][Y],rotation[X][Z],translate.x); + System.out.printf("[%3.2f %3.2f %3.2f] + [%3.2f]\n", + rotation[Y][X],rotation[Y][Y],rotation[Y][Z],translate.y); + System.out.printf("[%3.2f %3.2f %3.2f] [%3.2f]\n", + rotation[Z][X],rotation[Z][Y],rotation[Z][Z],translate.z); + System.out.println(); + } + + + public static void main(String[] arg) { + Transformation t; + + t = new Transformation(); + t.print("Empty"); + t = new Transformation(1,2,3); + t.print("1,2,3"); + t = new Transformation(new Coordinate(2,3,4)); + t.print("coord 2,3 4"); + + t = Transformation.rotate_y(0.01); + t.print("rotate_y 0.01"); + + t = new Transformation(-1,0,0); + t = t.applyTransformation(Transformation.rotate_y(0.01)); + t = t.applyTransformation(new Transformation(1,0,0)); + t.print("shift-rotate-shift"); + } + +}