Revert "Merge pull request #20 from cthunter01/propagator"
This reverts commit 6280d9fb0184275843a8f4406c7293e41e65a639, reversing changes made to 3c9c8b8c6a2b2e7430ff09efdc2cc0c1996b16ca.
109
.gitignore
vendored
@ -1,76 +1,45 @@
|
||||
# This file is used to ignore files which are generated
|
||||
# ----------------------------------------------------------------------------
|
||||
# Prerequisites
|
||||
*.d
|
||||
|
||||
build/
|
||||
|
||||
*~
|
||||
*.autosave
|
||||
*.a
|
||||
*.core
|
||||
*.moc
|
||||
# Compiled Object files
|
||||
*.slo
|
||||
*.lo
|
||||
*.o
|
||||
*.obj
|
||||
*.orig
|
||||
*.rej
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
|
||||
# Compiled Dynamic libraries
|
||||
*.so
|
||||
*.so.*
|
||||
*_pch.h.cpp
|
||||
*_resource.rc
|
||||
*.qm
|
||||
.#*
|
||||
*.*#
|
||||
core
|
||||
!core/
|
||||
tags
|
||||
.DS_Store
|
||||
.directory
|
||||
*.debug
|
||||
Makefile*
|
||||
*.prl
|
||||
*.app
|
||||
moc_*.cpp
|
||||
ui_*.h
|
||||
qrc_*.cpp
|
||||
Thumbs.db
|
||||
*.res
|
||||
*.rc
|
||||
/.qmake.cache
|
||||
/.qmake.stash
|
||||
|
||||
# qtcreator generated files
|
||||
*.pro.user*
|
||||
CMakeLists.txt.user*
|
||||
|
||||
# xemacs temporary files
|
||||
*.flc
|
||||
|
||||
# Vim temporary files
|
||||
.*.swp
|
||||
|
||||
# Visual Studio generated files
|
||||
*.ib_pdb_index
|
||||
*.idb
|
||||
*.ilk
|
||||
*.pdb
|
||||
*.sln
|
||||
*.suo
|
||||
*.vcproj
|
||||
*vcproj.*.*.user
|
||||
*.ncb
|
||||
*.sdf
|
||||
*.opensdf
|
||||
*.vcxproj
|
||||
*vcxproj.*
|
||||
|
||||
# MinGW generated files
|
||||
*.Debug
|
||||
*.Release
|
||||
|
||||
# Python byte code
|
||||
*.pyc
|
||||
|
||||
# Binaries
|
||||
# --------
|
||||
*.dylib
|
||||
*.dll
|
||||
*.exe
|
||||
|
||||
# Fortran module files
|
||||
*.mod
|
||||
*.smod
|
||||
|
||||
# Compiled Static libraries
|
||||
*.lai
|
||||
*.la
|
||||
*.a
|
||||
*.lib
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
|
||||
# Build files and executable
|
||||
build/
|
||||
|
||||
# Doxygen
|
||||
docs/doxygen/*
|
||||
|
||||
# IDE
|
||||
*.swp
|
||||
qtrocket.pro.user
|
||||
.qmake.stash
|
||||
CMakeLists.txt.user
|
||||
|
||||
|
141
CMakeLists.txt
@ -1,32 +1,122 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
project(qtrocket VERSION 0.1 LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
enable_testing()
|
||||
|
||||
include(FetchContent)
|
||||
|
||||
# Google Test framework
|
||||
FetchContent_Declare(googletest
|
||||
GIT_REPOSITORY https://github.com/google/googletest
|
||||
GIT_TAG v1.13.0)
|
||||
if(WIN32)
|
||||
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
|
||||
endif()
|
||||
FetchContent_MakeAvailable(googletest)
|
||||
|
||||
# fmtlib dependency
|
||||
#FetchContent_Declare(fmt
|
||||
# GIT_REPOSITORY https://github.com/fmtlib/fmt
|
||||
# GIT_TAG 9.1.0)
|
||||
#FetchContent_MakeAvailable(fmt)
|
||||
|
||||
# jsoncpp dependency
|
||||
FetchContent_Declare(jsoncpp
|
||||
GIT_REPOSITORY https://github.com/open-source-parsers/jsoncpp
|
||||
GIT_TAG 1.9.5)
|
||||
set(JSONCPP_WITH_TESTS OFF)
|
||||
set(JSONCPP_WITH_EXAMPLE OFF)
|
||||
set(JSONCPP_WITH_PKGCONFIG_SUPPORT OFF)
|
||||
set(JSONCPP_BUILD_SHARED_LIBS OFF)
|
||||
set(JSONCPP_BUILD_OBJECT_LIBS OFF)
|
||||
set(JSONCPP_BUILD_STATIC_LIBS ON)
|
||||
FetchContent_MakeAvailable(jsoncpp)
|
||||
|
||||
# curl dependency
|
||||
FetchContent_Declare(CURL
|
||||
GIT_REPOSITORY https://github.com/curl/curl
|
||||
GIT_TAG curl-8_0_1)
|
||||
set(BUILD_CURL_EXE OFF)
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
set(HTTP_ONLY ON)
|
||||
set(SSL_ENABLED ON)
|
||||
if(WIN32)
|
||||
set(CURL_USE_SCHANNEL ON)
|
||||
endif()
|
||||
FetchContent_MakeAvailable(CURL)
|
||||
|
||||
# eigen dependency
|
||||
FetchContent_Declare(Eigen
|
||||
GIT_REPOSITORY https://gitlab.com/libeigen/eigen
|
||||
GIT_TAG 3.4.0)
|
||||
FetchContent_MakeAvailable(Eigen)
|
||||
|
||||
# boost dependency
|
||||
|
||||
FetchContent_Declare(Boost
|
||||
GIT_REPOSITORY https://github.com/boostorg/boost
|
||||
GIT_TAG boost-1.84.0)
|
||||
set(BOOST_INCLUDE_LIBRARIES property_tree)
|
||||
FetchContent_MakeAvailable(Boost)
|
||||
# Add qtrocket subdirectories. These are components that will be linked in
|
||||
|
||||
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets LinguistTools)
|
||||
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets LinguistTools)
|
||||
if(WIN32)
|
||||
set(CMAKE_PREFIX_PATH $ENV{QTDIR})
|
||||
# include_directories("C:\\boost\\boost_1_82_0\\")
|
||||
# find_package(Qt6Core REQUIRED)
|
||||
# find_package(Qt6Widgets REQUIRED)
|
||||
endif()
|
||||
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets LinguistTools PrintSupport)
|
||||
find_package(Qt6 REQUIRED COMPONENTS Widgets LinguistTools PrintSupport)
|
||||
#find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets LinguistTools PrintSupport)
|
||||
#find_package(CURL)
|
||||
#find_package(fmt)
|
||||
|
||||
set(TS_FILES qtrocket_en_US.ts)
|
||||
|
||||
include_directories(${PROJECT_SOURCE_DIR})
|
||||
|
||||
set(PROJECT_SOURCES
|
||||
main.cpp
|
||||
MainWindow.cpp
|
||||
MainWindow.h
|
||||
MainWindow.ui
|
||||
QtRocket.cpp
|
||||
QtRocket.h
|
||||
gui/AboutWindow.cpp
|
||||
gui/AboutWindow.h
|
||||
gui/AboutWindow.ui
|
||||
gui/AnalysisWindow.cpp
|
||||
gui/AnalysisWindow.h
|
||||
gui/AnalysisWindow.ui
|
||||
gui/MainWindow.cpp
|
||||
gui/MainWindow.h
|
||||
gui/MainWindow.ui
|
||||
gui/RocketTreeView.cpp
|
||||
gui/RocketTreeView.h
|
||||
gui/SimOptionsWindow.cpp
|
||||
gui/SimOptionsWindow.h
|
||||
gui/SimOptionsWindow.ui
|
||||
gui/ThrustCurveMotorSelector.cpp
|
||||
gui/ThrustCurveMotorSelector.h
|
||||
gui/ThrustCurveMotorSelector.ui
|
||||
gui/qcustomplot.cpp
|
||||
gui/qcustomplot.h
|
||||
${TS_FILES}
|
||||
)
|
||||
|
||||
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
|
||||
qt_add_executable(qtrocket
|
||||
qtrocket.qrc
|
||||
MANUAL_FINALIZATION
|
||||
${PROJECT_SOURCES}
|
||||
Logger.cpp Logger.h RK4Solver.h
|
||||
)
|
||||
# Define target properties for Android with Qt 6 as:
|
||||
# set_property(TARGET qtrocket APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
|
||||
@ -47,31 +137,38 @@ else()
|
||||
)
|
||||
endif()
|
||||
|
||||
qt5_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES})
|
||||
endif()
|
||||
|
||||
target_link_libraries(qtrocket PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)
|
||||
add_subdirectory(utils)
|
||||
add_subdirectory(sim)
|
||||
add_subdirectory(model)
|
||||
|
||||
#target_link_libraries(qtrocket PRIVATE
|
||||
# Qt6::Widgets
|
||||
# Qt6::PrintSupport
|
||||
# libcurl
|
||||
# jsoncpp_static
|
||||
# fmt::fmt-header-only
|
||||
# eigen)
|
||||
|
||||
target_link_libraries(qtrocket PRIVATE
|
||||
Qt6::Widgets
|
||||
Qt6::PrintSupport
|
||||
utils
|
||||
sim
|
||||
model)
|
||||
|
||||
# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
|
||||
# If you are developing for iOS or macOS you should consider setting an
|
||||
# explicit, fixed bundle identifier manually though.
|
||||
if(${QT_VERSION} VERSION_LESS 6.1.0)
|
||||
set(BUNDLE_ID_OPTION MACOSX_BUNDLE_GUI_IDENTIFIER com.example.qtrocket)
|
||||
endif()
|
||||
set_target_properties(qtrocket PROPERTIES
|
||||
${BUNDLE_ID_OPTION}
|
||||
MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com
|
||||
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
|
||||
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
|
||||
MACOSX_BUNDLE TRUE
|
||||
WIN32_EXECUTABLE TRUE
|
||||
)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
install(TARGETS qtrocket
|
||||
BUNDLE DESTINATION .
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
)
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
|
||||
if(QT_VERSION_MAJOR EQUAL 6)
|
||||
qt_finalize_executable(qtrocket)
|
||||
|
13
HELPWANTED
Normal file
@ -0,0 +1,13 @@
|
||||
If you want to help out and are looking for something to do, thank you! QtRocket is still in the early stages of development (as of 2023-04-24), but there are some
|
||||
targets of development already that would be nice to have.
|
||||
|
||||
Here are some items that QtRocket needs help with and would probably take me a lot more time than someone already familiar:
|
||||
|
||||
* Windows build. I am not nearly as familiar with Windows development as I am with Linux/BSD, so if you can help in getting QtRocket to build and run in Windows that would be great!
|
||||
I think it just needs a build of libcurl, libjson-cpp, and fmtlib to link against, and the Windows build of QtCreator can take care of the rest.
|
||||
Having the source code to these libraries in a third-party subdirectory of the project that can be built along with the project may be the cleanest approach.
|
||||
That way correct builds can be made on each platform without needing to track pre-built library versions, since those would be built and (statically) linked automatically.
|
||||
|
||||
* MacOS build. Same as with Windows. I don't own a Mac to actually try this out on, but would love to support that platform.
|
||||
|
||||
* Linux and xBSD builds. Testing on multiple distributions and BSDs is welcome. Fixing or reporting issues with a particular distribution/BSD would be welcome.
|
674
LICENSE
Normal file
@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://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 <https://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
|
||||
<https://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
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
@ -1,14 +0,0 @@
|
||||
#include "MainWindow.h"
|
||||
#include "./ui_MainWindow.h"
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent)
|
||||
: QMainWindow(parent)
|
||||
, ui(new Ui::MainWindow)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow()
|
||||
{
|
||||
delete ui;
|
||||
}
|
23
MainWindow.h
@ -1,23 +0,0 @@
|
||||
#ifndef MAINWINDOW_H
|
||||
#define MAINWINDOW_H
|
||||
|
||||
#include <QMainWindow>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
namespace Ui {
|
||||
class MainWindow;
|
||||
}
|
||||
QT_END_NAMESPACE
|
||||
|
||||
class MainWindow : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MainWindow(QWidget *parent = nullptr);
|
||||
~MainWindow();
|
||||
|
||||
private:
|
||||
Ui::MainWindow *ui;
|
||||
};
|
||||
#endif // MAINWINDOW_H
|
@ -1,31 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MainWindow</class>
|
||||
<widget class="QMainWindow" name="MainWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>800</width>
|
||||
<height>600</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>MainWindow</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget"/>
|
||||
<widget class="QMenuBar" name="menubar">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>800</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QStatusBar" name="statusbar"/>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
130
QtRocket.cpp
Normal file
@ -0,0 +1,130 @@
|
||||
/// \cond
|
||||
// C headers
|
||||
// C++ headers
|
||||
#include <thread>
|
||||
|
||||
// 3rd party headers
|
||||
#include <QApplication>
|
||||
#include <QLocale>
|
||||
#include <QTranslator>
|
||||
/// \endcond
|
||||
|
||||
// qtrocket headers
|
||||
#include "QtRocket.h"
|
||||
#include "utils/Logger.h"
|
||||
#include "gui/MainWindow.h"
|
||||
|
||||
// Initialize static member data
|
||||
QtRocket* QtRocket::instance = nullptr;
|
||||
std::mutex QtRocket::mtx;
|
||||
bool QtRocket::initialized = false;
|
||||
|
||||
|
||||
// The gui worker thread
|
||||
void guiWorker(int argc, char* argv[], int& ret)
|
||||
{
|
||||
utils::Logger* logger = utils::Logger::getInstance();
|
||||
logger->info("Starting QApplication");
|
||||
QApplication a(argc, argv);
|
||||
a.setWindowIcon(QIcon(":/qtrocket.png"));
|
||||
|
||||
// Start translation component.
|
||||
// TODO: Only support US English at the moment. Anyone want to help translate?
|
||||
QTranslator translator;
|
||||
const QStringList uiLanguages = QLocale::system().uiLanguages();
|
||||
for (const QString &locale : uiLanguages)
|
||||
{
|
||||
const QString baseName = "qtrocket_" + QLocale(locale).name();
|
||||
if (translator.load(":/i18n/" + baseName))
|
||||
{
|
||||
a.installTranslator(&translator);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Go!
|
||||
MainWindow w(QtRocket::getInstance());
|
||||
logger->debug("Showing MainWindow");
|
||||
w.show();
|
||||
ret = a.exec();
|
||||
|
||||
}
|
||||
|
||||
QtRocket* QtRocket::getInstance()
|
||||
{
|
||||
if(!initialized)
|
||||
{
|
||||
init();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
void QtRocket::init()
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(mtx);
|
||||
if(!initialized)
|
||||
{
|
||||
utils::Logger::getInstance()->debug("Instantiating new QtRocket");
|
||||
instance = new QtRocket();
|
||||
initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
QtRocket::QtRocket()
|
||||
{
|
||||
logger = utils::Logger::getInstance();
|
||||
running = false;
|
||||
|
||||
// Need to set some sane defaults for the Environment
|
||||
// The default constructor for Environment will do that for us, so just use that
|
||||
setEnvironment(std::make_shared<sim::Environment>());
|
||||
|
||||
rocket.first =
|
||||
std::make_shared<model::RocketModel>();
|
||||
|
||||
rocket.second =
|
||||
std::make_shared<sim::Propagator>(rocket.first);
|
||||
|
||||
motorDatabase = std::make_shared<utils::MotorModelDatabase>();
|
||||
|
||||
logger->debug("Initial states vector size: " + std::to_string(states.capacity()) );
|
||||
// Reserve at least 1024 spaces for StateData
|
||||
if(states.capacity() < 1024)
|
||||
{
|
||||
states.reserve(1024);
|
||||
}
|
||||
logger->debug("New states vector size: " + std::to_string(states.capacity()) );
|
||||
}
|
||||
|
||||
int QtRocket::run(int argc, char* argv[])
|
||||
{
|
||||
// Really should only start this thread once
|
||||
if(!running)
|
||||
{
|
||||
running = true;
|
||||
int ret = 0;
|
||||
std::thread guiThread(guiWorker, argc, argv, std::ref(ret));
|
||||
guiThread.join();
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void QtRocket::launchRocket()
|
||||
{
|
||||
// initialize the propagator
|
||||
rocket.first->clearStates();
|
||||
rocket.second->setCurrentTime(0.0);
|
||||
|
||||
// start the rocket motor
|
||||
rocket.first->launch();
|
||||
|
||||
// run the propagator until it terminates
|
||||
rocket.second->runUntilTerminate();
|
||||
}
|
||||
|
||||
void QtRocket::addMotorModels(std::vector<model::MotorModel>& m)
|
||||
{
|
||||
motorDatabase->addMotorModels(m);
|
||||
// TODO: Now clear any duplicates?
|
||||
}
|
96
QtRocket.h
Normal file
@ -0,0 +1,96 @@
|
||||
#ifndef QTROCKET_H
|
||||
#define QTROCKET_H
|
||||
|
||||
/// \cond
|
||||
// C headers
|
||||
// C++ headers
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
|
||||
// 3rd party headers
|
||||
/// \endcond
|
||||
|
||||
// qtrocket headers
|
||||
#include "model/MotorModel.h"
|
||||
#include "model/RocketModel.h"
|
||||
#include "sim/Environment.h"
|
||||
#include "sim/Propagator.h"
|
||||
#include "utils/Logger.h"
|
||||
#include "utils/MotorModelDatabase.h"
|
||||
#include "utils/math/MathTypes.h"
|
||||
|
||||
/**
|
||||
* @brief The QtRocket class is the master controller for the QtRocket application.
|
||||
* It is the singleton that controls the interaction of the various components of
|
||||
* the QtRocket program
|
||||
*/
|
||||
class QtRocket
|
||||
{
|
||||
public:
|
||||
static QtRocket* getInstance();
|
||||
|
||||
utils::Logger* getLogger() { return logger; }
|
||||
|
||||
// This will return when the main window returns;
|
||||
// If called multiple times, subsequent calls, will simply
|
||||
// immediately return with value 0
|
||||
int run(int argc, char* argv[]);
|
||||
|
||||
void runSim();
|
||||
|
||||
|
||||
std::shared_ptr<sim::Environment> getEnvironment() { return environment; }
|
||||
void setTimeStep(double t) { rocket.second->setTimeStep(t); }
|
||||
std::shared_ptr<model::RocketModel> getRocket() { return rocket.first; }
|
||||
|
||||
std::shared_ptr<utils::MotorModelDatabase> getMotorDatabase() { return motorDatabase; }
|
||||
|
||||
void addMotorModels(std::vector<model::MotorModel>& m);
|
||||
|
||||
void addRocket(std::shared_ptr<model::RocketModel> r) { rocket.first = r; rocket.second = std::make_shared<sim::Propagator>(r); }
|
||||
|
||||
void setEnvironment(std::shared_ptr<sim::Environment> e) { environment = e; }
|
||||
|
||||
void launchRocket();
|
||||
/**
|
||||
* @brief getStates returns a vector of time/state pairs generated during launch()
|
||||
* @return vector of pairs of doubles, where the first value is a time and the second a state vector
|
||||
*/
|
||||
const std::vector<std::pair<double, StateData>>& getStates() const { return rocket.first->getStates(); }
|
||||
|
||||
/**
|
||||
* @brief setInitialState sets the initial state of the Rocket.
|
||||
* @param initState initial state vector (x, y, z, xDot, yDot, zDot, pitch, yaw, roll, pitchDot, yawDot, rollDot)
|
||||
*/
|
||||
void setInitialState(const StateData& initState) { rocket.first->setInitialState(initState); }
|
||||
|
||||
private:
|
||||
QtRocket();
|
||||
|
||||
static void init();
|
||||
|
||||
std::atomic_bool running;
|
||||
static bool initialized;
|
||||
static std::mutex mtx;
|
||||
static QtRocket* instance;
|
||||
|
||||
utils::Logger* logger;
|
||||
|
||||
using Rocket = std::pair<std::shared_ptr<model::RocketModel>, std::shared_ptr<sim::Propagator>>;
|
||||
Rocket rocket;
|
||||
|
||||
std::shared_ptr<sim::Environment> environment;
|
||||
std::shared_ptr<utils::MotorModelDatabase> motorDatabase;
|
||||
|
||||
// Launch site
|
||||
// ECEF coordinates
|
||||
Vector3 launchSitePosition{0.0, 0.0, 0.0};
|
||||
|
||||
// Table of state data
|
||||
std::vector<StateData> states;
|
||||
|
||||
};
|
||||
|
||||
#endif // QTROCKET_H
|
2
README.md
Normal file
@ -0,0 +1,2 @@
|
||||
# qtrocket
|
||||
An open source model Rocket Simulator written in C++ and Qt Toolkit, coming soon
|
9008
data/Aerotech.rse
Executable file
27
gui/AboutWindow.cpp
Normal file
@ -0,0 +1,27 @@
|
||||
#include "AboutWindow.h"
|
||||
|
||||
#include "ui_AboutWindow.h"
|
||||
|
||||
AboutWindow::AboutWindow(QWidget *parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::AboutWindow)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
setWindowTitle(QString("About QtRocket"));
|
||||
|
||||
connect(ui->okButton,
|
||||
SIGNAL(clicked()),
|
||||
this,
|
||||
SLOT(onButton_okButton_clicked()));
|
||||
}
|
||||
|
||||
AboutWindow::~AboutWindow()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void AboutWindow::onButton_okButton_clicked()
|
||||
{
|
||||
this->close();
|
||||
}
|
38
gui/AboutWindow.h
Normal file
@ -0,0 +1,38 @@
|
||||
#ifndef ABOUTWINDOW_H
|
||||
#define ABOUTWINDOW_H
|
||||
|
||||
/// \cond
|
||||
// C headers
|
||||
// C++ headers
|
||||
// 3rd party headers
|
||||
#include <QDialog>
|
||||
/// \endcond
|
||||
|
||||
// qtrocket headers
|
||||
|
||||
namespace Ui {
|
||||
class AboutWindow;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief The AboutWindow class
|
||||
*
|
||||
* The AboutWindow just displays some copyright information.
|
||||
*
|
||||
*/
|
||||
class AboutWindow : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit AboutWindow(QWidget *parent = nullptr);
|
||||
~AboutWindow();
|
||||
|
||||
private slots:
|
||||
void onButton_okButton_clicked();
|
||||
|
||||
private:
|
||||
Ui::AboutWindow *ui;
|
||||
};
|
||||
|
||||
#endif // ABOUTWINDOW_H
|
48
gui/AboutWindow.ui
Normal file
@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>AboutWindow</class>
|
||||
<widget class="QDialog" name="AboutWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Dialog</string>
|
||||
</property>
|
||||
<widget class="QPlainTextEdit" name="plainTextEdit">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>80</x>
|
||||
<y>60</y>
|
||||
<width>261</width>
|
||||
<height>151</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="plainText">
|
||||
<string>Copyright (c) 2023 by Travis Hunter</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="okButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>250</x>
|
||||
<y>240</y>
|
||||
<width>80</width>
|
||||
<height>25</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>OK</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
125
gui/AnalysisWindow.cpp
Normal file
@ -0,0 +1,125 @@
|
||||
#include "AnalysisWindow.h"
|
||||
#include "ui_AnalysisWindow.h"
|
||||
|
||||
#include "QtRocket.h"
|
||||
#include "model/MotorModel.h"
|
||||
#include "model/ThrustCurve.h"
|
||||
|
||||
AnalysisWindow::AnalysisWindow(QWidget *parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::AnalysisWindow)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
this->setWindowModality(Qt::NonModal);
|
||||
this->hide();
|
||||
this->show();
|
||||
|
||||
connect(ui->plotAltitudeBtn,
|
||||
SIGNAL(clicked()),
|
||||
this,
|
||||
SLOT(onButton_plotAltitude_clicked()));
|
||||
connect(ui->plotVelocityBtn,
|
||||
SIGNAL(clicked()),
|
||||
this,
|
||||
SLOT(onButton_plotVelocity_clicked()));
|
||||
connect(ui->plotMotorCurveBtn,
|
||||
SIGNAL(clicked()),this,
|
||||
SLOT(onButton_plotMotorCurve_clicked()));
|
||||
|
||||
}
|
||||
|
||||
AnalysisWindow::~AnalysisWindow()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void AnalysisWindow::onButton_plotAltitude_clicked()
|
||||
{
|
||||
QtRocket* qtRocket = QtRocket::getInstance();
|
||||
const std::vector<std::pair<double, StateData>>& res = qtRocket->getStates();
|
||||
auto& plot = ui->plotWidget;
|
||||
plot->clearGraphs();
|
||||
plot->setInteraction(QCP::iRangeDrag, true);
|
||||
plot->setInteraction(QCP::iRangeZoom, true);
|
||||
// generate some data:
|
||||
QVector<double> tData(res.size()), zData(res.size());
|
||||
for (int i = 0; i < tData.size(); ++i)
|
||||
{
|
||||
tData[i] = res[i].first;
|
||||
zData[i] = res[i].second.position[2];
|
||||
}
|
||||
// create graph and assign data to it:
|
||||
plot->addGraph();
|
||||
plot->graph(0)->setData(tData, zData);
|
||||
// give the axes some labels:
|
||||
plot->xAxis->setLabel("time");
|
||||
plot->yAxis->setLabel("Z");
|
||||
// set axes ranges, so we see all data:
|
||||
plot->xAxis->setRange(*std::min_element(std::begin(tData), std::end(tData)), *std::max_element(std::begin(tData), std::end(tData)));
|
||||
plot->yAxis->setRange(*std::min_element(std::begin(zData), std::end(zData)), *std::max_element(std::begin(zData), std::end(zData)));
|
||||
plot->replot();
|
||||
}
|
||||
|
||||
void AnalysisWindow::onButton_plotVelocity_clicked()
|
||||
{
|
||||
QtRocket* qtRocket = QtRocket::getInstance();
|
||||
const std::vector<std::pair<double, StateData>>& res = qtRocket->getStates();
|
||||
auto& plot = ui->plotWidget;
|
||||
plot->clearGraphs();
|
||||
plot->setInteraction(QCP::iRangeDrag, true);
|
||||
plot->setInteraction(QCP::iRangeZoom, true);
|
||||
|
||||
// generate some data:
|
||||
QVector<double> tData(res.size()), zData(res.size());
|
||||
for (int i = 0; i < tData.size(); ++i)
|
||||
{
|
||||
tData[i] = res[i].first;
|
||||
zData[i] = res[i].second.velocity[2];
|
||||
}
|
||||
// create graph and assign data to it:
|
||||
plot->addGraph();
|
||||
plot->graph(0)->setData(tData, zData);
|
||||
// give the axes some labels:
|
||||
plot->xAxis->setLabel("time");
|
||||
plot->yAxis->setLabel("Z Velocity");
|
||||
// set axes ranges, so we see all data:
|
||||
plot->xAxis->setRange(*std::min_element(std::begin(tData), std::end(tData)), *std::max_element(std::begin(tData), std::end(tData)));
|
||||
plot->yAxis->setRange(*std::min_element(std::begin(zData), std::end(zData)), *std::max_element(std::begin(zData), std::end(zData)));
|
||||
plot->replot();
|
||||
|
||||
}
|
||||
|
||||
void AnalysisWindow::onButton_plotMotorCurve_clicked()
|
||||
{
|
||||
std::shared_ptr<model::RocketModel> rocket = QtRocket::getInstance()->getRocket();
|
||||
model::MotorModel motor = rocket->getMotorModel();
|
||||
ThrustCurve tc = motor.getThrustCurve();
|
||||
|
||||
|
||||
const std::vector<std::pair<double, double>>& res = tc.getThrustCurveData();
|
||||
auto& plot = ui->plotWidget;
|
||||
plot->clearGraphs();
|
||||
plot->setInteraction(QCP::iRangeDrag, true);
|
||||
plot->setInteraction(QCP::iRangeZoom, true);
|
||||
|
||||
// generate some data:
|
||||
QVector<double> tData(res.size());
|
||||
QVector<double> fData(res.size());
|
||||
for (int i = 0; i < tData.size(); ++i)
|
||||
{
|
||||
tData[i] = res[i].first;
|
||||
fData[i] = res[i].second;
|
||||
}
|
||||
// create graph and assign data to it:
|
||||
plot->addGraph();
|
||||
plot->graph(0)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, 5));
|
||||
plot->graph(0)->setData(tData, fData);
|
||||
// give the axes some labels:
|
||||
plot->xAxis->setLabel("time");
|
||||
plot->yAxis->setLabel("Thrust (N)");
|
||||
// set axes ranges, so we see all data:
|
||||
plot->xAxis->setRange(*std::min_element(std::begin(tData), std::end(tData)), *std::max_element(std::begin(tData), std::end(tData)));
|
||||
plot->yAxis->setRange(*std::min_element(std::begin(fData), std::end(fData)), *std::max_element(std::begin(fData), std::end(fData)));
|
||||
plot->replot();
|
||||
|
||||
}
|
49
gui/AnalysisWindow.h
Normal file
@ -0,0 +1,49 @@
|
||||
#ifndef ANALYSISWINDOW_H
|
||||
#define ANALYSISWINDOW_H
|
||||
|
||||
/// \cond
|
||||
|
||||
// C
|
||||
// C++
|
||||
// 3rd party
|
||||
#include <QDialog>
|
||||
/// \endcond
|
||||
|
||||
// qtrocket headers
|
||||
|
||||
namespace Ui {
|
||||
class AnalysisWindow;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief The AnalysisWindow class.
|
||||
*
|
||||
* The Analysis Window class shows a plot of rocket state data. This allows visual inspection of
|
||||
* flight data such as altitude vs. time.
|
||||
*/
|
||||
class AnalysisWindow : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief AnalysisWindow constructor.
|
||||
* @param parent Parent widget
|
||||
*
|
||||
* @note The constructor will make a call to QtRocket and grab the current Rocket model
|
||||
* and automatically plot altitude vs time
|
||||
*/
|
||||
explicit AnalysisWindow(QWidget *parent = nullptr);
|
||||
~AnalysisWindow();
|
||||
|
||||
private slots:
|
||||
|
||||
void onButton_plotAltitude_clicked();
|
||||
void onButton_plotVelocity_clicked();
|
||||
void onButton_plotMotorCurve_clicked();
|
||||
|
||||
private:
|
||||
Ui::AnalysisWindow *ui;
|
||||
};
|
||||
|
||||
#endif // ANALYSISWINDOW_H
|
95
gui/AnalysisWindow.ui
Normal file
@ -0,0 +1,95 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>AnalysisWindow</class>
|
||||
<widget class="QDialog" name="AnalysisWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>989</width>
|
||||
<height>821</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Dialog</string>
|
||||
</property>
|
||||
<widget class="QCustomPlot" name="plotWidget" native="true">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>149</x>
|
||||
<y>29</y>
|
||||
<width>831</width>
|
||||
<height>541</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="plotAltitudeBtn">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>50</y>
|
||||
<width>121</width>
|
||||
<height>36</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Plot Altitude</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="plotVelocityBtn">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>110</y>
|
||||
<width>121</width>
|
||||
<height>36</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Plot Velocity</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="plotAtmosphereBtn">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>170</y>
|
||||
<width>121</width>
|
||||
<height>36</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Plot Atmosphere</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="plotMotorCurveBtn">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>230</y>
|
||||
<width>121</width>
|
||||
<height>36</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Motor Curve</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>QCustomPlot</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>gui/qcustomplot.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
<slots>
|
||||
<slot>plotAltitude()</slot>
|
||||
</slots>
|
||||
</ui>
|
207
gui/MainWindow.cpp
Normal file
@ -0,0 +1,207 @@
|
||||
|
||||
/// \cond
|
||||
// C headers
|
||||
// C++ headers
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
// 3rd party headers
|
||||
#include <QFileDialog>
|
||||
|
||||
/// \endcond
|
||||
|
||||
|
||||
// qtrocket headers
|
||||
#include "ui_MainWindow.h"
|
||||
|
||||
#include "gui/AboutWindow.h"
|
||||
#include "gui/AnalysisWindow.h"
|
||||
#include "gui/MainWindow.h"
|
||||
#include "gui/ThrustCurveMotorSelector.h"
|
||||
#include "gui/SimOptionsWindow.h"
|
||||
#include "model/RocketModel.h"
|
||||
#include "utils/RSEDatabaseLoader.h"
|
||||
|
||||
|
||||
|
||||
MainWindow::MainWindow(QtRocket* _qtRocket, QWidget *parent)
|
||||
: QMainWindow(parent),
|
||||
ui(new Ui::MainWindow),
|
||||
qtRocket(_qtRocket)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
////////////////////////////////
|
||||
// Menu signal/slot connections
|
||||
////////////////////////////////
|
||||
|
||||
// File Menu Actions
|
||||
connect(ui->actionQuit,
|
||||
SIGNAL(triggered()),
|
||||
this,
|
||||
SLOT(onMenu_File_Quit_triggered()));
|
||||
|
||||
// Edit Menu Actions
|
||||
connect(ui->actionSimulation_Options,
|
||||
SIGNAL(triggered()),
|
||||
this,
|
||||
SLOT(onMenu_Edit_SimulationOptions_triggered()));
|
||||
|
||||
// Tools Menu Actions
|
||||
connect(ui->actionSaveMotorDatabase,
|
||||
SIGNAL(triggered()),
|
||||
this,
|
||||
SLOT(onMenu_Tools_SaveMotorDatabase()));
|
||||
|
||||
// Help Menu Actions
|
||||
connect(ui->actionAbout,
|
||||
SIGNAL(triggered()),
|
||||
this,
|
||||
SLOT(onMenu_Help_About_triggered()));
|
||||
|
||||
////////////////////////////////
|
||||
// Button signal/slot connections
|
||||
////////////////////////////////
|
||||
connect(ui->calculateTrajectory_btn,
|
||||
SIGNAL(clicked()),
|
||||
this,
|
||||
SLOT(onButton_calculateTrajectory_clicked()));
|
||||
|
||||
connect(ui->loadRSE_btn,
|
||||
SIGNAL(clicked()),
|
||||
this,
|
||||
SLOT(onButton_loadRSE_button_clicked()));
|
||||
|
||||
connect(ui->setMotor_btn,
|
||||
SIGNAL(clicked()),
|
||||
this,
|
||||
SLOT(onButton_setMotor_clicked()));
|
||||
|
||||
connect(ui->getTCMotorData_btn,
|
||||
SIGNAL(clicked()),
|
||||
this,
|
||||
SLOT(onButton_getTCMotorData_clicked()));
|
||||
|
||||
ui->calculateTrajectory_btn->setDisabled(true);
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
|
||||
void MainWindow::onMenu_Help_About_triggered()
|
||||
{
|
||||
AboutWindow about;
|
||||
about.setModal(true);
|
||||
about.exec();
|
||||
|
||||
}
|
||||
|
||||
void MainWindow::onMenu_Tools_SaveMotorDatabase()
|
||||
{
|
||||
qtRocket->getMotorDatabase()->saveMotorDatabase("qtrocket_motors.qmd");
|
||||
}
|
||||
|
||||
|
||||
void MainWindow::onButton_calculateTrajectory_clicked()
|
||||
{
|
||||
// Get the initial conditions
|
||||
double initialVelocity =
|
||||
ui->rocketPartButtons->findChild<QLineEdit*>(QString("initialVelocity"))->text().toDouble();
|
||||
|
||||
double mass =
|
||||
ui->rocketPartButtons->findChild<QLineEdit*>(QString("mass"))->text().toDouble();
|
||||
|
||||
double initialAngle =
|
||||
ui->rocketPartButtons->findChild<QLineEdit*>(QString("initialAngle"))->text().toDouble();
|
||||
|
||||
double dragCoeff =
|
||||
ui->rocketPartButtons->findChild<QLineEdit*>(QString("dragCoeff"))->text().toDouble();
|
||||
|
||||
double initialVelocityX = initialVelocity * std::cos(initialAngle / 57.2958);
|
||||
double initialVelocityZ = initialVelocity * std::sin(initialAngle / 57.2958);
|
||||
//std::vector<double> initialState = {0.0, 0.0, 0.0, initialVelocityX, 0.0, initialVelocityZ, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
|
||||
StateData initialState;
|
||||
initialState.position = {0.0, 0.0, 0.0};
|
||||
initialState.velocity = {initialVelocityX, 0.0, initialVelocityZ};
|
||||
auto rocket = QtRocket::getInstance()->getRocket();
|
||||
rocket->setMass(mass);
|
||||
rocket->setDragCoefficient(dragCoeff);
|
||||
|
||||
qtRocket->setInitialState(initialState);
|
||||
qtRocket->launchRocket();
|
||||
|
||||
AnalysisWindow aWindow;
|
||||
aWindow.setModal(false);
|
||||
aWindow.exec();
|
||||
|
||||
}
|
||||
|
||||
void MainWindow::onButton_loadRSE_button_clicked()
|
||||
{
|
||||
QString rseFile = QFileDialog::getOpenFileName(this,
|
||||
tr("Import RSE Database File"),
|
||||
"/home",
|
||||
tr("Rocksim Engine Files (*.rse)"));
|
||||
|
||||
if(!rseFile.isEmpty())
|
||||
{
|
||||
rseDatabase.reset(new utils::RSEDatabaseLoader(rseFile.toStdString()));
|
||||
|
||||
ui->rocketPartButtons->findChild<QLineEdit*>(QString("databaseFileLine"))->setText(rseFile);
|
||||
|
||||
QComboBox* engineSelector =
|
||||
ui->rocketPartButtons->findChild<QComboBox*>(QString("engineSelectorComboBox"));
|
||||
|
||||
const std::vector<model::MotorModel>& motors = rseDatabase->getMotors();
|
||||
for(const auto& motor : motors)
|
||||
{
|
||||
std::cout << "Adding: " << motor.data.commonName << std::endl;
|
||||
engineSelector->addItem(QString(motor.data.commonName.c_str()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void MainWindow::onButton_getTCMotorData_clicked()
|
||||
{
|
||||
ThrustCurveMotorSelector window;
|
||||
window.setModal(false);
|
||||
window.exec();
|
||||
|
||||
}
|
||||
|
||||
|
||||
void MainWindow::onMenu_Edit_SimulationOptions_triggered()
|
||||
{
|
||||
if(!simOptionsWindow)
|
||||
{
|
||||
simOptionsWindow = new SimOptionsWindow(this);
|
||||
}
|
||||
simOptionsWindow->show();
|
||||
|
||||
}
|
||||
|
||||
|
||||
void MainWindow::onButton_setMotor_clicked()
|
||||
{
|
||||
QString motorName = ui->engineSelectorComboBox->currentText();
|
||||
model::MotorModel mm = rseDatabase->getMotorModelByName(motorName.toStdString());
|
||||
QtRocket::getInstance()->getRocket()->setMotorModel(mm);
|
||||
|
||||
// Now that we have a motor selected, we can enable the calculateTrajectory button
|
||||
ui->calculateTrajectory_btn->setDisabled(false);
|
||||
|
||||
/// TODO: Figure out if this is the right place to populate the motor database
|
||||
/// or from RSEDatabaseLoader where it currently is populated.
|
||||
//QtRocket::getInstance()->addMotorModels(rseDatabase->getMotors());
|
||||
|
||||
}
|
||||
|
||||
void MainWindow::onMenu_File_Quit_triggered()
|
||||
{
|
||||
this->close();
|
||||
}
|
63
gui/MainWindow.h
Normal file
@ -0,0 +1,63 @@
|
||||
#ifndef MAINWINDOW_H
|
||||
#define MAINWINDOW_H
|
||||
|
||||
/// \cond
|
||||
// C headers
|
||||
// C++ headers
|
||||
#include <memory>
|
||||
// 3rd Party headers
|
||||
#include <QMainWindow>
|
||||
/// \endcond
|
||||
|
||||
// qtrocket headers
|
||||
#include "QtRocket.h"
|
||||
|
||||
#include "gui/SimOptionsWindow.h"
|
||||
#include "utils/RSEDatabaseLoader.h"
|
||||
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
namespace Ui { class MainWindow; }
|
||||
QT_END_NAMESPACE
|
||||
|
||||
/**
|
||||
* @brief The MainWindow class
|
||||
*
|
||||
* The MainWindow class holds the primary GUI window of the application. All user interactions
|
||||
* with QtRocket begin with interactions in this window. This window can spawn other windows.
|
||||
*/
|
||||
|
||||
class MainWindow : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MainWindow(QtRocket* _qtRocket, QWidget *parent = nullptr);
|
||||
~MainWindow();
|
||||
|
||||
private slots:
|
||||
|
||||
void onMenu_Help_About_triggered();
|
||||
|
||||
void onButton_calculateTrajectory_clicked();
|
||||
|
||||
void onButton_loadRSE_button_clicked();
|
||||
|
||||
void onButton_getTCMotorData_clicked();
|
||||
|
||||
void onMenu_Edit_SimulationOptions_triggered();
|
||||
|
||||
void onButton_setMotor_clicked();
|
||||
|
||||
void onMenu_File_Quit_triggered();
|
||||
|
||||
void onMenu_Tools_SaveMotorDatabase();
|
||||
|
||||
private:
|
||||
Ui::MainWindow* ui;
|
||||
QtRocket* qtRocket;
|
||||
|
||||
SimOptionsWindow* simOptionsWindow{nullptr};
|
||||
std::unique_ptr<utils::RSEDatabaseLoader> rseDatabase;
|
||||
};
|
||||
#endif // MAINWINDOW_H
|
372
gui/MainWindow.ui
Normal file
@ -0,0 +1,372 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MainWindow</class>
|
||||
<widget class="QMainWindow" name="MainWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1031</width>
|
||||
<height>694</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>QtRocket</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset resource="../qtrocket.qrc">
|
||||
<normaloff>:/images/resources/rocket64.png</normaloff>:/images/resources/rocket64.png</iconset>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<widget class="QSplitter" name="splitter_2">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>6</x>
|
||||
<y>6</y>
|
||||
<width>1021</width>
|
||||
<height>631</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="childrenCollapsible">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QSplitter" name="splitter">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="childrenCollapsible">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="RocketTreeView" name="rocketTreeView">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QWidget" name="rocketPartButtons" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>2</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<widget class="QPushButton" name="calculateTrajectory_btn">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>240</x>
|
||||
<y>440</y>
|
||||
<width>191</width>
|
||||
<height>25</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Calculate Trajectory</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QWidget" name="layoutWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>260</x>
|
||||
<y>80</y>
|
||||
<width>161</width>
|
||||
<height>196</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="initialAngle">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>90.0</string>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="mass">
|
||||
<property name="text">
|
||||
<string>1.0</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Cd</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Angle</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="initialVelocity">
|
||||
<property name="text">
|
||||
<string>5.0</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="dragCoeff">
|
||||
<property name="text">
|
||||
<string>0.5</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>mass</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Initial Velocity</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLineEdit" name="timeStep">
|
||||
<property name="text">
|
||||
<string>0.01</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Time Step</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="gridLayoutWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>90</x>
|
||||
<y>290</y>
|
||||
<width>421</width>
|
||||
<height>80</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QPushButton" name="loadRSE_btn">
|
||||
<property name="text">
|
||||
<string>Load RSE Database File</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="databaseFileLine">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QComboBox" name="engineSelectorComboBox"/>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="setMotor_btn">
|
||||
<property name="text">
|
||||
<string>Set Motor</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="getTCMotorData_btn">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>240</x>
|
||||
<y>390</y>
|
||||
<width>201</width>
|
||||
<height>25</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Get Thrustcurve Motor Data</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QCustomPlot" name="plotWindow" native="true"/>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QMenuBar" name="menubar">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1031</width>
|
||||
<height>32</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuFile">
|
||||
<property name="title">
|
||||
<string>File</string>
|
||||
</property>
|
||||
<addaction name="actionNew"/>
|
||||
<addaction name="actionOpen"/>
|
||||
<addaction name="actionSave"/>
|
||||
<addaction name="actionSave_As"/>
|
||||
<addaction name="actionClose"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionQuit"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuEdit">
|
||||
<property name="title">
|
||||
<string>Edit</string>
|
||||
</property>
|
||||
<addaction name="actionSimulation_Options"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuTools">
|
||||
<property name="title">
|
||||
<string>Tools</string>
|
||||
</property>
|
||||
<addaction name="actionSaveMotorDatabase"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuHelp">
|
||||
<property name="title">
|
||||
<string>Help</string>
|
||||
</property>
|
||||
<addaction name="actionAbout"/>
|
||||
</widget>
|
||||
<addaction name="menuFile"/>
|
||||
<addaction name="menuEdit"/>
|
||||
<addaction name="menuTools"/>
|
||||
<addaction name="menuHelp"/>
|
||||
</widget>
|
||||
<widget class="QStatusBar" name="statusbar"/>
|
||||
<action name="actionAbout">
|
||||
<property name="icon">
|
||||
<iconset theme="help-about">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>About</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionNew">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="document-new">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>New</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionOpen">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="document-open">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Open</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionSave">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="document-save">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Save</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionClose">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Close</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionQuit">
|
||||
<property name="icon">
|
||||
<iconset theme="application-exit">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Quit</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionSave_As">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="document-save-as">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Save As</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionSimulation_Options">
|
||||
<property name="text">
|
||||
<string>Simulation Options</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionSaveMotorDatabase">
|
||||
<property name="text">
|
||||
<string>Save Motor Database</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>RocketTreeView</class>
|
||||
<extends>QTreeView</extends>
|
||||
<header>gui/RocketTreeView.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>QCustomPlot</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>gui/qcustomplot.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="../qtrocket.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
6
gui/RocketTreeView.cpp
Normal file
@ -0,0 +1,6 @@
|
||||
#include "RocketTreeView.h"
|
||||
|
||||
RocketTreeView::RocketTreeView(QWidget* parent) : QTreeView(parent)
|
||||
{
|
||||
|
||||
}
|
28
gui/RocketTreeView.h
Normal file
@ -0,0 +1,28 @@
|
||||
#ifndef ROCKETTREEVIEW_H
|
||||
#define ROCKETTREEVIEW_H
|
||||
|
||||
/// \cond
|
||||
// C headers
|
||||
// C++ headers
|
||||
// 3rd party headers
|
||||
#include <QTreeView>
|
||||
/// \endcond
|
||||
|
||||
// qtrocket headers
|
||||
|
||||
/**
|
||||
* @brief RocketTreeView basically just renames QTreeView with a specific
|
||||
* memorable name.
|
||||
*
|
||||
* The purpose is to provide an exploded view of the components that make up the
|
||||
* rocket design, and their relationships.
|
||||
*/
|
||||
class RocketTreeView : public QTreeView
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
RocketTreeView(QWidget* parent = nullptr);
|
||||
};
|
||||
|
||||
#endif // ROCKETTREEVIEW_H
|
74
gui/SimOptionsWindow.cpp
Normal file
@ -0,0 +1,74 @@
|
||||
|
||||
/// \cond
|
||||
// C headers
|
||||
// C++ headers
|
||||
#include <memory>
|
||||
// 3rd party headers
|
||||
/// \endcond
|
||||
|
||||
// qtrocket headers
|
||||
#include "QtRocket.h"
|
||||
#include "SimOptionsWindow.h"
|
||||
#include "ui_SimOptionsWindow.h"
|
||||
|
||||
#include "sim/Environment.h"
|
||||
|
||||
SimOptionsWindow::SimOptionsWindow(QWidget *parent) :
|
||||
QMainWindow(parent),
|
||||
ui(new Ui::SimOptionsWindow)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
connect(ui->buttonBox,
|
||||
SIGNAL(rejected()),
|
||||
this,
|
||||
SLOT(on_buttonBox_rejected()));
|
||||
|
||||
connect(ui->buttonBox,
|
||||
SIGNAL(accepted()),
|
||||
this,
|
||||
SLOT(on_buttonBox_accepted()));
|
||||
|
||||
// populate the combo boxes
|
||||
|
||||
std::shared_ptr<sim::Environment> options(new sim::Environment);
|
||||
std::vector<std::string> atmosphereModels = options->getAvailableAtmosphereModels();
|
||||
std::vector<std::string> gravityModels = options->getAvailableGravityModels();
|
||||
|
||||
for(const auto& i : atmosphereModels)
|
||||
{
|
||||
ui->atmosphereModelCombo->addItem(QString::fromStdString(i));
|
||||
}
|
||||
for(const auto& i : gravityModels)
|
||||
{
|
||||
ui->gravityModelCombo->addItem(QString::fromStdString(i));
|
||||
}
|
||||
}
|
||||
|
||||
SimOptionsWindow::~SimOptionsWindow()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void SimOptionsWindow::on_buttonBox_rejected()
|
||||
{
|
||||
this->close();
|
||||
}
|
||||
|
||||
|
||||
void SimOptionsWindow::on_buttonBox_accepted()
|
||||
{
|
||||
QtRocket* qtrocket = QtRocket::getInstance();
|
||||
|
||||
std::shared_ptr<sim::Environment> environment(new sim::Environment);
|
||||
|
||||
qtrocket->setTimeStep(ui->timeStep->text().toDouble());
|
||||
environment->setGravityModel(ui->gravityModelCombo->currentText().toStdString());
|
||||
environment->setAtmosphereModel(ui->atmosphereModelCombo->currentText().toStdString());
|
||||
qtrocket->setEnvironment(environment);
|
||||
|
||||
this->close();
|
||||
|
||||
|
||||
}
|
||||
|
27
gui/SimOptionsWindow.h
Normal file
@ -0,0 +1,27 @@
|
||||
#ifndef SIMOPTIONSWINDOW_H
|
||||
#define SIMOPTIONSWINDOW_H
|
||||
|
||||
#include <QMainWindow>
|
||||
|
||||
namespace Ui {
|
||||
class SimOptionsWindow;
|
||||
}
|
||||
|
||||
class SimOptionsWindow : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SimOptionsWindow(QWidget *parent = nullptr);
|
||||
~SimOptionsWindow();
|
||||
|
||||
private slots:
|
||||
void on_buttonBox_rejected();
|
||||
|
||||
void on_buttonBox_accepted();
|
||||
|
||||
private:
|
||||
Ui::SimOptionsWindow *ui;
|
||||
};
|
||||
|
||||
#endif // SIMOPTIONSWINDOW_H
|
113
gui/SimOptionsWindow.ui
Normal file
@ -0,0 +1,113 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>SimOptionsWindow</class>
|
||||
<widget class="QMainWindow" name="SimOptionsWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>381</width>
|
||||
<height>289</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Simulation Options</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<widget class="QWidget" name="formLayoutWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>60</x>
|
||||
<y>20</y>
|
||||
<width>261</width>
|
||||
<height>156</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Time Step (s)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="timeStep">
|
||||
<property name="placeholderText">
|
||||
<string>0.01</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Atmosphere Model</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="atmosphereModelCombo"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Gravity Model</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="gravityModelCombo"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Integrator</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QComboBox" name="integratorCombo"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>200</x>
|
||||
<y>200</y>
|
||||
<width>166</width>
|
||||
<height>25</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QMenuBar" name="menubar">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>381</width>
|
||||
<height>22</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuFile">
|
||||
<property name="title">
|
||||
<string>File</string>
|
||||
</property>
|
||||
</widget>
|
||||
<addaction name="menuFile"/>
|
||||
</widget>
|
||||
<widget class="QStatusBar" name="statusbar"/>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
140
gui/ThrustCurveMotorSelector.cpp
Normal file
@ -0,0 +1,140 @@
|
||||
|
||||
/// \cond
|
||||
// C headers
|
||||
// C++ headers
|
||||
#include <algorithm>
|
||||
// 3rd party headers
|
||||
/// \endcond
|
||||
|
||||
// qtrocket headers
|
||||
#include "ThrustCurveMotorSelector.h"
|
||||
#include "ui_ThrustCurveMotorSelector.h"
|
||||
#include "QtRocket.h"
|
||||
|
||||
ThrustCurveMotorSelector::ThrustCurveMotorSelector(QWidget *parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::ThrustCurveMotorSelector),
|
||||
tcApi(new utils::ThrustCurveAPI)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
connect(ui->getMetadata,
|
||||
SIGNAL(clicked()),
|
||||
this,
|
||||
SLOT(onButton_getMetadata_clicked()));
|
||||
|
||||
connect(ui->searchButton,
|
||||
SIGNAL(clicked()),
|
||||
this,
|
||||
SLOT(onButton_searchButton_clicked()));
|
||||
|
||||
connect(ui->setMotor,
|
||||
SIGNAL(clicked()),
|
||||
this,
|
||||
SLOT(onButton_setMotor_clicked()));
|
||||
|
||||
|
||||
this->setWindowModality(Qt::NonModal);
|
||||
this->hide();
|
||||
this->show();
|
||||
}
|
||||
|
||||
ThrustCurveMotorSelector::~ThrustCurveMotorSelector()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void ThrustCurveMotorSelector::onButton_getMetadata_clicked()
|
||||
{
|
||||
// When the user clicks "Get Metadata", we want to pull in Metadata from thrustcurve.org
|
||||
// and populate the Manufacturer, Diameter, and Impulse Class combo boxes
|
||||
|
||||
utils::ThrustcurveMetadata metadata = tcApi->getMetadata();
|
||||
|
||||
for(const auto& i : metadata.diameters)
|
||||
{
|
||||
ui->diameter->addItem(QString::number(i));
|
||||
}
|
||||
|
||||
for(const auto& i : metadata.manufacturers)
|
||||
{
|
||||
ui->manufacturer->addItem(QString::fromStdString(i.first));
|
||||
}
|
||||
for(const auto& i : metadata.impulseClasses)
|
||||
{
|
||||
ui->impulseClass->addItem(QString::fromStdString(i));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ThrustCurveMotorSelector::onButton_searchButton_clicked()
|
||||
{
|
||||
|
||||
//double diameter = ui->diameter->
|
||||
|
||||
std::string diameter = ui->diameter->currentText().toStdString();
|
||||
std::string manufacturer = ui->manufacturer->currentText().toStdString();
|
||||
std::string impulseClass = ui->impulseClass->currentText().toStdString();
|
||||
|
||||
utils::SearchCriteria c;
|
||||
c.addCriteria("diameter", diameter);
|
||||
c.addCriteria("manufacturer", manufacturer);
|
||||
c.addCriteria("impulseClass", impulseClass);
|
||||
|
||||
std::vector<model::MotorModel> motors = tcApi->searchMotors(c);
|
||||
std::copy(std::begin(motors), std::end(motors), std::back_inserter(motorModels));
|
||||
|
||||
for(const auto& i : motors)
|
||||
{
|
||||
ui->motorSelection->addItem(QString::fromStdString(i.data.commonName));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
void ThrustCurveMotorSelector::onButton_setMotor_clicked()
|
||||
{
|
||||
//asdf
|
||||
std::string commonName = ui->motorSelection->currentText().toStdString();
|
||||
|
||||
// get motor
|
||||
|
||||
model::MotorModel mm = *std::find_if(
|
||||
std::begin(motorModels),
|
||||
std::end(motorModels),
|
||||
[&commonName](const auto& item)
|
||||
{
|
||||
return item.data.commonName == commonName;
|
||||
});
|
||||
|
||||
ThrustCurve tc = tcApi->getMotorData(mm.data.motorIdTC).getThrustCurve();
|
||||
mm.addThrustCurve(tc);
|
||||
QtRocket::getInstance()->getRocket()->setMotorModel(mm);
|
||||
|
||||
const std::vector<std::pair<double, double>>& res = tc.getThrustCurveData();
|
||||
auto& plot = ui->plot;
|
||||
plot->clearGraphs();
|
||||
plot->setInteraction(QCP::iRangeDrag, true);
|
||||
plot->setInteraction(QCP::iRangeZoom, true);
|
||||
|
||||
// generate some data:
|
||||
QVector<double> tData(res.size());
|
||||
QVector<double> fData(res.size());
|
||||
for (int i = 0; i < tData.size(); ++i)
|
||||
{
|
||||
tData[i] = res[i].first;
|
||||
fData[i] = res[i].second;
|
||||
}
|
||||
// create graph and assign data to it:
|
||||
plot->addGraph();
|
||||
plot->graph(0)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, 5));
|
||||
plot->graph(0)->setData(tData, fData);
|
||||
// give the axes some labels:
|
||||
plot->xAxis->setLabel("time");
|
||||
plot->yAxis->setLabel("Thrust (N)");
|
||||
// set axes ranges, so we see all data:
|
||||
plot->xAxis->setRange(*std::min_element(std::begin(tData), std::end(tData)), *std::max_element(std::begin(tData), std::end(tData)));
|
||||
plot->yAxis->setRange(*std::min_element(std::begin(fData), std::end(fData)), *std::max_element(std::begin(fData), std::end(fData)));
|
||||
plot->replot();
|
||||
}
|
||||
|
47
gui/ThrustCurveMotorSelector.h
Normal file
@ -0,0 +1,47 @@
|
||||
#ifndef THRUSTCURVEMOTORSELECTOR_H
|
||||
#define THRUSTCURVEMOTORSELECTOR_H
|
||||
|
||||
/// \cond
|
||||
// C headers
|
||||
// C++ headers
|
||||
#include <memory>
|
||||
|
||||
// 3rd party headers
|
||||
#include <QDialog>
|
||||
/// \endcond
|
||||
|
||||
// qtrocket headers
|
||||
#include "utils/ThrustCurveAPI.h"
|
||||
#include "model/MotorModel.h"
|
||||
|
||||
namespace Ui {
|
||||
class ThrustCurveMotorSelector;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief The ThrustCurveMotorSelector class is a Window that provides an interface to Thrustcurve.org
|
||||
*/
|
||||
class ThrustCurveMotorSelector : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ThrustCurveMotorSelector(QWidget *parent = nullptr);
|
||||
~ThrustCurveMotorSelector();
|
||||
|
||||
private slots:
|
||||
void onButton_getMetadata_clicked();
|
||||
|
||||
void onButton_searchButton_clicked();
|
||||
|
||||
void onButton_setMotor_clicked();
|
||||
|
||||
private:
|
||||
Ui::ThrustCurveMotorSelector *ui;
|
||||
|
||||
std::unique_ptr<utils::ThrustCurveAPI> tcApi;
|
||||
|
||||
std::vector<model::MotorModel> motorModels;
|
||||
};
|
||||
|
||||
#endif // THRUSTCURVEMOTORSELECTOR_H
|
133
gui/ThrustCurveMotorSelector.ui
Normal file
@ -0,0 +1,133 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ThrustCurveMotorSelector</class>
|
||||
<widget class="QDialog" name="ThrustCurveMotorSelector">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>622</width>
|
||||
<height>878</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Dialog</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="formLayoutWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>170</x>
|
||||
<y>90</y>
|
||||
<width>160</width>
|
||||
<height>101</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Manufacturer</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Diameter</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Impulse Class</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="manufacturer"/>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="diameter"/>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="impulseClass"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="getMetadata">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>200</x>
|
||||
<y>40</y>
|
||||
<width>91</width>
|
||||
<height>25</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Get Metadata</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QCustomPlot" name="plot" native="true">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>320</y>
|
||||
<width>601</width>
|
||||
<height>551</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="searchButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>210</x>
|
||||
<y>210</y>
|
||||
<width>80</width>
|
||||
<height>25</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Search</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QWidget" name="formLayoutWidget_2">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>140</x>
|
||||
<y>240</y>
|
||||
<width>221</width>
|
||||
<height>41</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="motorSelection"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="setMotor">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>200</x>
|
||||
<y>290</y>
|
||||
<width>80</width>
|
||||
<height>25</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>setMotor</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>QCustomPlot</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>gui/qcustomplot.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
35532
gui/qcustomplot.cpp
Normal file
7774
gui/qcustomplot.h
Normal file
54
main.cpp
@ -1,47 +1,27 @@
|
||||
/// \cond
|
||||
// C Headers
|
||||
// C++ Headers
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
// C headers
|
||||
// C++ headers
|
||||
// 3rd party headers
|
||||
#include <QApplication>
|
||||
#include <QLocale>
|
||||
#include <QTranslator>
|
||||
/// \endcond
|
||||
|
||||
#include "MainWindow.h"
|
||||
#include "Logger.h"
|
||||
#include "RK4Solver.h"
|
||||
#include "QtRocket.h"
|
||||
#include "utils/Logger.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication a(argc, argv);
|
||||
auto* logger = Logger::getInstance();
|
||||
logger->setLogLevel(Logger::LogLevel::PERF);
|
||||
|
||||
QTranslator translator;
|
||||
const QStringList uiLanguages = QLocale::system().uiLanguages();
|
||||
for (const QString &locale : uiLanguages) {
|
||||
const QString baseName = "untitled_" + QLocale(locale).name();
|
||||
if (translator.load(":/i18n/" + baseName)) {
|
||||
a.installTranslator(&translator);
|
||||
break;
|
||||
}
|
||||
}
|
||||
logger->debug("Starting MainWindow");
|
||||
MainWindow w;
|
||||
// Instantiate logger
|
||||
utils::Logger* logger = utils::Logger::getInstance();
|
||||
logger->setLogLevel(utils::Logger::PERF_);
|
||||
logger->info("Logger instantiated at PERF level");
|
||||
// instantiate QtRocket
|
||||
logger->debug("Starting QtRocket");
|
||||
QtRocket* qtrocket = QtRocket::getInstance();
|
||||
|
||||
|
||||
|
||||
w.show();
|
||||
return a.exec();
|
||||
}
|
||||
|
||||
void test_RK4()
|
||||
{
|
||||
|
||||
auto ode = [](double& x, double& r) -> std::pair<double, double>
|
||||
{
|
||||
|
||||
}
|
||||
// Run QtRocket. This'll start the GUI thread and block until the user
|
||||
// exits the program
|
||||
logger->debug("QtRocket->run()");
|
||||
int retVal = qtrocket->run(argc, argv);
|
||||
logger->debug("Returning");
|
||||
return retVal;
|
||||
}
|
||||
|
20
model/CMakeLists.txt
Normal file
@ -0,0 +1,20 @@
|
||||
add_library(model
|
||||
MotorModel.cpp
|
||||
MotorModel.h
|
||||
MotorModelDatabase.cpp
|
||||
MotorModelDatabase.h
|
||||
Part.cpp
|
||||
Part.h
|
||||
Propagatable.cpp
|
||||
Propagatable.h
|
||||
RocketModel.cpp
|
||||
RocketModel.h
|
||||
ThrustCurve.cpp
|
||||
ThrustCurve.h
|
||||
InertiaTensors.h)
|
||||
|
||||
target_link_libraries(model PRIVATE
|
||||
utils)
|
||||
|
||||
# Unit tests
|
||||
add_subdirectory(tests)
|
70
model/InertiaTensors.h
Normal file
@ -0,0 +1,70 @@
|
||||
#ifndef INERTIATENSORS_H
|
||||
#define INERTIATENSORS_H
|
||||
|
||||
#include "utils/math/MathTypes.h"
|
||||
|
||||
namespace model
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief The InertiaTensors class provides a collection of methods to
|
||||
* deliver some common inertia tensors centered about the center of mass
|
||||
*/
|
||||
class InertiaTensors
|
||||
{
|
||||
public:
|
||||
|
||||
/**
|
||||
* @brief SolidSphere
|
||||
* @param radius (meters)
|
||||
* @return
|
||||
*/
|
||||
static Matrix3 SolidSphere(double radius)
|
||||
{
|
||||
double xx = 0.4*radius*radius;
|
||||
double yy = xx;
|
||||
double zz = xx;
|
||||
return Matrix3{{xx, 0, 0},
|
||||
{0, yy, 0},
|
||||
{0, 0, zz}};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief HollowSphere
|
||||
* @param radius (meters)
|
||||
* @return
|
||||
*/
|
||||
static Matrix3 HollowSphere(double radius)
|
||||
{
|
||||
double xx = (2.0/3.0)*radius*radius;
|
||||
double yy = xx;
|
||||
double zz = xx;
|
||||
return Matrix3{{xx, 0, 0},
|
||||
{0, yy, 0},
|
||||
{0, 0, zz}};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Tube - The longitudinal axis is the z-axis. Can also be used for a solid cylinder
|
||||
* when innerRadius = 0.0
|
||||
* @param innerRadius (meters)
|
||||
* @param outerRadius (meters)
|
||||
* @param length (meters)
|
||||
* @return
|
||||
*/
|
||||
static Matrix3 Tube(double innerRadius, double outerRadius, double length)
|
||||
{
|
||||
double xx = (1.0/12.0)*(3.0*(innerRadius*innerRadius + outerRadius*outerRadius) + length*length);
|
||||
double yy = xx;
|
||||
double zz = (1.0/2.0)*(innerRadius*innerRadius + outerRadius*outerRadius);
|
||||
return Matrix3{{xx, 0.0, 0.0},
|
||||
{0.0, yy, 0.0},
|
||||
{0.0, 0.0, zz}};
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // INERTIATENSORS_H
|
131
model/MotorModel.cpp
Normal file
@ -0,0 +1,131 @@
|
||||
|
||||
/// \cond
|
||||
// C headers
|
||||
// C++ headers
|
||||
// 3rd party headers
|
||||
/// \endcond
|
||||
|
||||
// qtrocket headers
|
||||
#include "model/MotorModel.h"
|
||||
#include "utils/math/Constants.h"
|
||||
#include "utils/math/UtilityMathFunctions.h"
|
||||
#include "utils/Logger.h"
|
||||
|
||||
namespace model
|
||||
{
|
||||
|
||||
MotorModel::MotorModel()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
MotorModel::~MotorModel()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
double MotorModel::getMass(double simTime) const
|
||||
{
|
||||
// the current mass is the emptyMass + the current prop mass
|
||||
|
||||
// If ignition hasn't occurred, return the totalMass
|
||||
if(!ignitionOccurred)
|
||||
{
|
||||
return data.totalWeight;
|
||||
}
|
||||
else if(simTime - ignitionTime <= data.burnTime)
|
||||
{
|
||||
double thrustTime = simTime - ignitionTime;
|
||||
// Find the right interval in the massCurve
|
||||
auto i = massCurve.cbegin();
|
||||
while(i->first <= thrustTime)
|
||||
{
|
||||
// If thrustTime is equal to a data point that we have, then just return
|
||||
// the mass at that time. Otherwise it fell between two points and we
|
||||
// will interpolate
|
||||
if(utils::math::floatingPointEqual(i->first, thrustTime))
|
||||
{
|
||||
// return empty mass + remaining propellant mass
|
||||
return emptyMass + i->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
i++;
|
||||
}
|
||||
}
|
||||
// linearly interpolate the propellant mass. Then return the empty mass + remaining prop mass
|
||||
double tStart = std::prev(i)->first;
|
||||
double tEnd = i->first;
|
||||
double propMassStart = std::prev(i)->second;
|
||||
double propMassEnd = i->second;
|
||||
double slope = (propMassEnd - propMassStart) / (tEnd - tStart);
|
||||
double currentMass = emptyMass + propMassStart + (thrustTime - tStart) * slope;
|
||||
utils::Logger::getInstance()->info("simTime: " + std::to_string(simTime) + ": motor mass: " + std::to_string(currentMass));
|
||||
return currentMass;
|
||||
|
||||
}
|
||||
// motor has burned out
|
||||
else
|
||||
{
|
||||
return emptyMass;
|
||||
}
|
||||
}
|
||||
|
||||
double MotorModel::getThrust(double simTime)
|
||||
{
|
||||
|
||||
if(simTime > thrust.getMaxTime() + ignitionTime)
|
||||
{
|
||||
if(!burnOutOccurred)
|
||||
{
|
||||
utils::Logger::getInstance()->info("motor burnout occurred: " + std::to_string(simTime));
|
||||
burnOutOccurred = true;
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
utils::Logger::getInstance()->info("simTime: " + std::to_string(simTime) + ": thrust: " + std::to_string(thrust.getThrust(simTime)));
|
||||
return thrust.getThrust(simTime);
|
||||
}
|
||||
|
||||
void MotorModel::setMetaData(const MetaData& md)
|
||||
{
|
||||
data = md;
|
||||
computeMassCurve();
|
||||
}
|
||||
|
||||
void MotorModel::moveMetaData(MetaData&& md)
|
||||
{
|
||||
data = std::move(md);
|
||||
computeMassCurve();
|
||||
}
|
||||
|
||||
void MotorModel::computeMassCurve()
|
||||
{
|
||||
emptyMass = data.totalWeight - data.propWeight;
|
||||
|
||||
// Calculate the Isp for the motor, as we'll need this for the computing the mass flow rate.
|
||||
// This will be the total impulse in Newton-seconds over
|
||||
// the propellant weight. The prop mass is in grams, hence the division by 1000.0 to get kg
|
||||
isp = data.totalImpulse / (utils::math::Constants::g0 * data.propWeight / 1000.0);
|
||||
|
||||
// Precompute the mass curve. Having this precomputed will ensure multiple calls to getMass()
|
||||
// or getThrust() during the same time step don't accidentally decrement the mass multiple times.
|
||||
// Having a lookup table will ensure consistent mass values, as well as speed up the simulation,
|
||||
// just at the cost of some extra space
|
||||
|
||||
// Most motor data in the RASP format has a limitation of 32 data points. We're not going to
|
||||
// match that, so we can pick whatever we want and just interpolate values. We can have 128
|
||||
// for example
|
||||
|
||||
massCurve.reserve(128);
|
||||
double timeStep = data.burnTime / 127.0;
|
||||
double t = 0.0;
|
||||
double propMass{data.propWeight};
|
||||
for(std::size_t i = 0; i < 127; ++i)
|
||||
{
|
||||
massCurve.push_back(std::make_pair(t + i*timeStep, propMass));
|
||||
propMass -= thrust.getThrust(t + i*timeStep) * timeStep * data.propWeight / data.totalImpulse;
|
||||
}
|
||||
massCurve.push_back(std::make_pair(data.burnTime, 0.0));
|
||||
}
|
||||
} // namespace model
|
424
model/MotorModel.h
Normal file
@ -0,0 +1,424 @@
|
||||
#ifndef MODEL_MOTORMODEL_H
|
||||
#define MODEL_MOTORMODEL_H
|
||||
|
||||
/// \cond
|
||||
// C headers
|
||||
// C++ headers
|
||||
#include <string>
|
||||
|
||||
// 3rd party headers
|
||||
// For boost serialization. We're using boost::serialize to save
|
||||
// and load Motor data to file. (CURRENTLY UNUSED)
|
||||
//#include <boost/archive/text_iarchive.hpp>
|
||||
//#include <boost/archive/text_oarchive.hpp>
|
||||
/// \endcond
|
||||
|
||||
// qtrocket theaders
|
||||
#include "ThrustCurve.h"
|
||||
|
||||
namespace model
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief The MotorModel class
|
||||
*
|
||||
* The MotorModel class defines a structure that holds data relating to a hobby
|
||||
* rocket motor such as the manufacturer, burn time, maximum thrust, propellant
|
||||
* weight, etc. It also holds a ThrustCurve object that contains thrust sample data
|
||||
* for that motor.
|
||||
*
|
||||
* There are several additional classes defined within the MotorModel class designed
|
||||
* to encapsulate and define several pieces of motor related data as well.
|
||||
*/
|
||||
class MotorModel
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief MotorModel constructor
|
||||
*/
|
||||
MotorModel();
|
||||
/**
|
||||
* @brief MotorModel copy constructor is defaulted
|
||||
*/
|
||||
MotorModel(const MotorModel&) = default;
|
||||
/**
|
||||
* @brief MotorModel move constructor is defaulted
|
||||
*/
|
||||
MotorModel(MotorModel&&) = default;
|
||||
~MotorModel();
|
||||
|
||||
/**
|
||||
* @brief Copy assignment operator is defaulted
|
||||
* @return Copy of MotorModel
|
||||
*/
|
||||
MotorModel& operator=(const MotorModel&) = default;
|
||||
|
||||
/**
|
||||
* @brief Move assignment operator is defaulted
|
||||
* @return Moved MotorModel
|
||||
*/
|
||||
MotorModel& operator=(MotorModel&&) = default;
|
||||
|
||||
/**
|
||||
* @brief The AVAILABILITY enum class identifies whether a motor is
|
||||
* out of production, or still available
|
||||
*/
|
||||
enum class AVAILABILITY
|
||||
{
|
||||
REGULAR, /// available
|
||||
OOP /// Out of Production
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief The MOTORMANUFACTURER enum class identifies the motor
|
||||
* manufacturer
|
||||
*/
|
||||
enum class MOTORMANUFACTURER
|
||||
{
|
||||
AEROTECH,
|
||||
AMW,
|
||||
APOGEE,
|
||||
CESARONI,
|
||||
CONTRAIL,
|
||||
ESTES,
|
||||
HYPERTEK,
|
||||
KLIMA,
|
||||
LOKI,
|
||||
QUEST,
|
||||
UNKNOWN
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The CERTORG enum class identifies the Certification Organization
|
||||
* that certified the motor
|
||||
*/
|
||||
enum class CERTORG
|
||||
{
|
||||
AMRS, /// Australian Model Rocket Society
|
||||
CAR, /// Canadian Association of Rocketry
|
||||
NAR, /// National Association of Rocketry
|
||||
TRA, /// Tripoli
|
||||
UNC, /// Uncertified
|
||||
UNK /// Unknown Certification
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The MOTORTYPE enum class identifies the motor type, either
|
||||
* Single-Use, Reload, or Hybrid
|
||||
*/
|
||||
enum class MOTORTYPE
|
||||
{
|
||||
SU,
|
||||
RELOAD,
|
||||
HYBRID
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The MotorAvailability struct wraps the AVAILABILITY enum and
|
||||
* provides a helper function to return a string representation
|
||||
* of the AVAILABILITY enum.
|
||||
*/
|
||||
struct MotorAvailability
|
||||
{
|
||||
MotorAvailability(const AVAILABILITY& a) : availability(a) {}
|
||||
MotorAvailability(const MotorAvailability&) = default;
|
||||
MotorAvailability(MotorAvailability&&) = default;
|
||||
MotorAvailability() : MotorAvailability(AVAILABILITY::REGULAR) {}
|
||||
|
||||
MotorAvailability& operator=(const MotorAvailability&) = default;
|
||||
MotorAvailability& operator=(MotorAvailability&&) = default;
|
||||
|
||||
AVAILABILITY availability{AVAILABILITY::REGULAR};
|
||||
/**
|
||||
* @brief str Returns a string representation of AVAILABILITY enum
|
||||
* @return string representation
|
||||
*/
|
||||
std::string str() const
|
||||
{
|
||||
if(availability == AVAILABILITY::REGULAR)
|
||||
return std::string("regular");
|
||||
else
|
||||
return std::string("OOP");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief toEnum returns AVAILABILITY enum from string name
|
||||
* @param name Name of enum
|
||||
* @return AVAILABILITY enum corresponding to name
|
||||
*/
|
||||
static AVAILABILITY toEnum(const std::string& name)
|
||||
{
|
||||
if(name == "regular")
|
||||
return AVAILABILITY::REGULAR;
|
||||
else
|
||||
return AVAILABILITY::OOP;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The CertOrg struct wraps the CERTORG enum and
|
||||
* provides a helper function to return a string representation
|
||||
* of the CERTORG enum.
|
||||
*/
|
||||
struct CertOrg
|
||||
{
|
||||
CertOrg(const CERTORG& c) : org(c) {}
|
||||
CertOrg(const CertOrg&) = default;
|
||||
CertOrg(CertOrg&&) = default;
|
||||
CertOrg() : CertOrg(CERTORG::UNC) {}
|
||||
|
||||
CertOrg& operator=(const CertOrg&) = default;
|
||||
CertOrg& operator=(CertOrg&&) = default;
|
||||
|
||||
CERTORG org{CERTORG::UNC};
|
||||
/**
|
||||
* @brief str Returns a string representation of CERTORG enum
|
||||
* @return string representation
|
||||
*/
|
||||
std::string str() const
|
||||
{
|
||||
if(org == CERTORG::AMRS)
|
||||
return std::string("Austrialian Model Rocket Society Inc.");
|
||||
else if(org == CERTORG::CAR)
|
||||
return std::string("Canadian Association of Rocketry");
|
||||
else if(org == CERTORG::NAR)
|
||||
return std::string("National Association of Rocketry");
|
||||
else if(org == CERTORG::TRA)
|
||||
return std::string("Tripoli Rocketry Association, Inc.");
|
||||
else if(org == CERTORG::UNC)
|
||||
return std::string("Uncertified");
|
||||
else // UNK - Unknown
|
||||
return std::string("Unkown");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief toEnum returns CERTORG enum corresponding to name
|
||||
* @param name Name of enumeration
|
||||
* @return enumeration value corresponding to name
|
||||
*/
|
||||
static CERTORG toEnum(const std::string& name)
|
||||
{
|
||||
if(name == "AMRS")
|
||||
return CERTORG::AMRS;
|
||||
else if(name == "CAR")
|
||||
return CERTORG::CAR;
|
||||
else if(name == "NAR")
|
||||
return CERTORG::NAR;
|
||||
else if(name == "TRA")
|
||||
return CERTORG::TRA;
|
||||
else if(name == "UNC")
|
||||
return CERTORG::UNC;
|
||||
else // Unknown
|
||||
return CERTORG::UNK;
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The MotorType struct wraps the MOTORTYPE enum and
|
||||
* provides a helper function to return a string representation
|
||||
* of the MOTORTYPE enum.
|
||||
*/
|
||||
struct MotorType
|
||||
{
|
||||
MotorType(const MOTORTYPE& t) : type(t) {}
|
||||
MotorType(const MotorType&) = default;
|
||||
MotorType(MotorType&&) = default;
|
||||
MotorType() : MotorType(MOTORTYPE::SU) {}
|
||||
|
||||
MotorType& operator=(const MotorType&) = default;
|
||||
MotorType& operator=(MotorType&&) = default;
|
||||
|
||||
MOTORTYPE type;
|
||||
/**
|
||||
* @brief str Returns a string representation of MOTORTYPE enum
|
||||
* @return string representation
|
||||
*/
|
||||
std::string str() const
|
||||
{
|
||||
if(type == MOTORTYPE::SU)
|
||||
return std::string("Single Use");
|
||||
else if(type == MOTORTYPE::RELOAD)
|
||||
return std::string("Reload");
|
||||
else
|
||||
return std::string("Hybrid");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief toEnum returns enumeration corresponding to name
|
||||
* @param name Name of enumeration
|
||||
* @return enumeration corresponding to name
|
||||
*/
|
||||
static MOTORTYPE toEnum(const std::string& name)
|
||||
{
|
||||
if(name == "SU" ||
|
||||
name == "Single Use" ||
|
||||
name == "single-use")
|
||||
return MOTORTYPE::SU;
|
||||
else if(name == "reload" ||
|
||||
name == "Reload" ||
|
||||
name == "reloadable")
|
||||
return MOTORTYPE::RELOAD;
|
||||
else // It's a hybrid
|
||||
return MOTORTYPE::HYBRID;
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The MotorManufacturer struct wraps the MOTORMANUFACTURER enum and
|
||||
* provides a helper function to return a string representation
|
||||
* of the MOTORMANUFACTURER enum.
|
||||
*/
|
||||
struct MotorManufacturer
|
||||
{
|
||||
MotorManufacturer(const MOTORMANUFACTURER& m) : manufacturer(m) {}
|
||||
MotorManufacturer() : manufacturer(MOTORMANUFACTURER::UNKNOWN) {}
|
||||
MotorManufacturer(const MotorManufacturer&) = default;
|
||||
MotorManufacturer(MotorManufacturer&&) = default;
|
||||
|
||||
MotorManufacturer& operator=(const MotorManufacturer&) = default;
|
||||
MotorManufacturer& operator=(MotorManufacturer&&) = default;
|
||||
|
||||
MOTORMANUFACTURER manufacturer;
|
||||
/**
|
||||
* @brief str Returns a string representation of MOTORMANUFACTURER enum
|
||||
* @return string representation
|
||||
*/
|
||||
std::string str() const
|
||||
{
|
||||
switch(manufacturer)
|
||||
{
|
||||
case MOTORMANUFACTURER::AEROTECH:
|
||||
return std::string("AeroTech");
|
||||
case MOTORMANUFACTURER::AMW:
|
||||
return std::string("AMW");
|
||||
case MOTORMANUFACTURER::CESARONI:
|
||||
return std::string("Cesaroni");
|
||||
case MOTORMANUFACTURER::ESTES:
|
||||
return std::string("Estes");
|
||||
case MOTORMANUFACTURER::LOKI:
|
||||
return std::string("Loki");
|
||||
case MOTORMANUFACTURER::APOGEE:
|
||||
return std::string("Apogee");
|
||||
case MOTORMANUFACTURER::CONTRAIL:
|
||||
return std::string("Contrail");
|
||||
case MOTORMANUFACTURER::HYPERTEK:
|
||||
return std::string("Hypertek");
|
||||
case MOTORMANUFACTURER::KLIMA:
|
||||
return std::string("Klima");
|
||||
case MOTORMANUFACTURER::QUEST:
|
||||
return std::string("Quest");
|
||||
case MOTORMANUFACTURER::UNKNOWN:
|
||||
default:
|
||||
return std::string("Unknown");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief toEnum returns MOTORMANUFACTURER enum value corresponding to a name
|
||||
* @param name Name of enumeration
|
||||
* @return enumeration corresponding to name
|
||||
*/
|
||||
static MOTORMANUFACTURER toEnum(const std::string& name)
|
||||
{
|
||||
if(name == "AeroTech" ||
|
||||
name == "Aerotech")
|
||||
return MOTORMANUFACTURER::AEROTECH;
|
||||
else if(name == "AMW" ||
|
||||
name == "Animal Motor Works")
|
||||
return MOTORMANUFACTURER::AMW;
|
||||
else if(name == "Cesaroni" ||
|
||||
name == "Cesaroni Technology Inc.")
|
||||
return MOTORMANUFACTURER::CESARONI;
|
||||
else if(name == "Estes" ||
|
||||
name == "Estes Industries, Inc.")
|
||||
return MOTORMANUFACTURER::ESTES;
|
||||
else if(name == "Loki")
|
||||
return MOTORMANUFACTURER::LOKI;
|
||||
else if(name == "Apogee")
|
||||
return MOTORMANUFACTURER::APOGEE;
|
||||
else if(name == "Contrail")
|
||||
return MOTORMANUFACTURER::CONTRAIL;
|
||||
else if(name == "Hypertek")
|
||||
return MOTORMANUFACTURER::HYPERTEK;
|
||||
else if(name == "Klima")
|
||||
return MOTORMANUFACTURER::QUEST;
|
||||
else if(name == "Quest")
|
||||
return MOTORMANUFACTURER::KLIMA;
|
||||
else
|
||||
return MOTORMANUFACTURER::UNKNOWN;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/// TODO: make these MotorModel members private. Public just for testing
|
||||
//private:
|
||||
|
||||
struct MetaData
|
||||
{
|
||||
MetaData() = default;
|
||||
~MetaData() = default;
|
||||
MetaData(const MetaData&) = default;
|
||||
MetaData(MetaData&&) = default;
|
||||
MetaData& operator=(const MetaData&) = default;
|
||||
MetaData& operator=(MetaData&&) = default;
|
||||
|
||||
MotorAvailability availability{AVAILABILITY::REGULAR}; /// Motor Availability
|
||||
double avgThrust{0.0}; /// Average thrust in Newtons
|
||||
double burnTime{0.0}; /// Burn time in seconds
|
||||
CertOrg certOrg{CERTORG::UNC}; /// The certification organization, defaults to Uncertified
|
||||
std::string commonName{""}; /// Common name, e.g. A8 or J615
|
||||
// int dataFiles
|
||||
std::vector<int> delays; /// 1000 delay means no ejection charge
|
||||
std::string designation{""}; /// Other name, usually includes prop type, e.g. H110W
|
||||
double diameter{0}; /// motor diameter in mm
|
||||
std::string impulseClass; /// Motor letter, e.g. 'A', 'B', '1/2A', 'M', etc
|
||||
std::string infoUrl{""}; /// TODO: ???
|
||||
double length{0.0}; /// motor length in mm
|
||||
MotorManufacturer manufacturer{MOTORMANUFACTURER::UNKNOWN}; /// Motor Manufacturer
|
||||
|
||||
double maxThrust{0.0}; /// Max thrust in Newtons
|
||||
std::string motorIdTC{""}; /// 24 character hex string used by thrustcurve.org to ID a motor
|
||||
std::string propType{""}; /// Propellant type, e.g. black powder
|
||||
double propWeight{0.0}; /// Propellant weight in grams
|
||||
bool sparky{false}; /// true if the motor is "sparky", false otherwise
|
||||
double totalImpulse{0.0}; /// Total impulse in Newton-seconds
|
||||
double totalWeight{0.0}; /// Total weight in grams
|
||||
MotorType type{MOTORTYPE::SU}; /// Motor type, e.g. single-use, reload, or hybrid
|
||||
std::string lastUpdated{""}; /// Date last updated on ThrustCurve.org
|
||||
};
|
||||
|
||||
double getMass(double simTime) const;
|
||||
double getThrust(double simTime);
|
||||
|
||||
void setMetaData(const MetaData& md);
|
||||
void moveMetaData(MetaData&& md);
|
||||
|
||||
void startMotor(double startTime) { ignitionOccurred = true; ignitionTime = startTime; }
|
||||
|
||||
void addThrustCurve(const ThrustCurve& tc) { thrust = tc; }
|
||||
|
||||
const ThrustCurve& getThrustCurve() const { return thrust; }
|
||||
|
||||
// Thrust parameters
|
||||
MetaData data;
|
||||
private:
|
||||
bool ignitionOccurred{false};
|
||||
bool burnOutOccurred{false};
|
||||
double emptyMass;
|
||||
double isp;
|
||||
double maxTime;
|
||||
double ignitionTime;
|
||||
ThrustCurve thrust; /// The measured motor thrust curve
|
||||
|
||||
std::vector<std::pair<double, double>> massCurve;
|
||||
|
||||
void computeMassCurve();
|
||||
|
||||
};
|
||||
|
||||
} // namespace model
|
||||
|
||||
#endif // MODEL_MOTORMODEL_H
|
6
model/MotorModelDatabase.cpp
Normal file
@ -0,0 +1,6 @@
|
||||
#include "MotorModelDatabase.h"
|
||||
|
||||
MotorModelDatabase::MotorModelDatabase()
|
||||
{
|
||||
|
||||
}
|
53
model/MotorModelDatabase.h
Normal file
@ -0,0 +1,53 @@
|
||||
#ifndef MOTORMODELDATABASE_H
|
||||
#define MOTORMODELDATABASE_H
|
||||
|
||||
/// \cond
|
||||
// C headers
|
||||
// C++ headers
|
||||
#include <vector>
|
||||
// 3rd party headers
|
||||
/// \endcond
|
||||
|
||||
// qtrocket headers
|
||||
#include "model/MotorModel.h"
|
||||
|
||||
/**
|
||||
* @brief The MotorModelDatabase class provides a storage and search mechanism for
|
||||
* MotorModels
|
||||
*/
|
||||
class MotorModelDatabase
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief MotorModelDatabase constructor
|
||||
*/
|
||||
MotorModelDatabase();
|
||||
|
||||
/**
|
||||
* @brief MotorModelDatabase destructor is defaulted
|
||||
*/
|
||||
~MotorModelDatabase() = default;
|
||||
|
||||
/**
|
||||
* @brief findMotorsByManufacturer returns a vector of MotorModel from a given
|
||||
* manufacturer
|
||||
* @param manufacturer The manufacturer to search for
|
||||
* @return vector of MotorModels from a given manufacturer
|
||||
*/
|
||||
std::vector<model::MotorModel> findMotorsByManufacturer(const std::string& manufacturer);
|
||||
|
||||
/**
|
||||
* @brief findMotersByImpulseClass returns a vector of MotorModels with a given
|
||||
* impulse class
|
||||
* @param imClass Impulse class to search for
|
||||
* @return vector of MotorModels with a given Impulse class
|
||||
*/
|
||||
std::vector<model::MotorModel> findMotersByImpulseClass(const std::string& imClass);
|
||||
|
||||
private:
|
||||
|
||||
std::vector<model::MotorModel> motors;
|
||||
|
||||
};
|
||||
|
||||
#endif // MOTORMODELDATABASE_H
|
109
model/Part.cpp
Normal file
@ -0,0 +1,109 @@
|
||||
#include "Part.h"
|
||||
#include "utils/Logger.h"
|
||||
|
||||
namespace model
|
||||
{
|
||||
|
||||
Part::Part(const std::string& n,
|
||||
const Matrix3& I,
|
||||
double m,
|
||||
const Vector3& centerMass)
|
||||
: parent(nullptr),
|
||||
name(n),
|
||||
inertiaTensor(I),
|
||||
compositeInertiaTensor(I),
|
||||
mass(m),
|
||||
compositeMass(m),
|
||||
cm(centerMass),
|
||||
needsRecomputing(false),
|
||||
childParts()
|
||||
{ }
|
||||
|
||||
Part::~Part()
|
||||
{}
|
||||
|
||||
Part::Part(const Part& orig)
|
||||
: parent(orig.parent),
|
||||
name(orig.name),
|
||||
inertiaTensor(orig.inertiaTensor),
|
||||
compositeInertiaTensor(orig.compositeInertiaTensor),
|
||||
mass(orig.mass),
|
||||
compositeMass(orig.compositeMass),
|
||||
cm(orig.cm),
|
||||
needsRecomputing(orig.needsRecomputing),
|
||||
childParts()
|
||||
{
|
||||
|
||||
// We are copying the whole tree. If the part we're copying itself has child
|
||||
// parts, we are also copying all of them! This may be inefficient and not what
|
||||
// is desired, but it is less likely to lead to weird bugs with the same part
|
||||
// appearing in multiple locations of the rocket
|
||||
utils::Logger::getInstance()->debug("Calling model::Part copy constructor. Recursively copying all child parts. Check Part names for uniqueness");
|
||||
|
||||
|
||||
for(const auto& i : orig.childParts)
|
||||
{
|
||||
Part& x = *std::get<0>(i);
|
||||
std::shared_ptr<Part> tempPart = std::make_shared<Part>(x);
|
||||
childParts.emplace_back(tempPart, std::get<1>(i));;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
double Part::getChildMasses(double t)
|
||||
{
|
||||
double childMasses{0.0};
|
||||
for(const auto& i : childParts)
|
||||
{
|
||||
childMasses += std::get<0>(i)->getMass(t);
|
||||
}
|
||||
return childMasses;
|
||||
|
||||
}
|
||||
|
||||
void Part::addChildPart(const Part& childPart, Vector3 position)
|
||||
{
|
||||
|
||||
double childMass = childPart.compositeMass;
|
||||
Matrix3 childInertiaTensor = childPart.compositeInertiaTensor;
|
||||
std::shared_ptr<Part> newChild = std::make_shared<Part>(childPart);
|
||||
// Set the parent pointer
|
||||
newChild->parent = this;
|
||||
|
||||
// Recompute inertia tensor
|
||||
|
||||
childInertiaTensor += childMass * ( position.dot(position) * Matrix3::Identity() - position*position.transpose());
|
||||
|
||||
compositeInertiaTensor += childInertiaTensor;
|
||||
compositeMass += childMass;
|
||||
|
||||
childParts.emplace_back(std::move(newChild), std::move(position));
|
||||
|
||||
if(parent)
|
||||
{
|
||||
parent->markAsNeedsRecomputing();
|
||||
parent->recomputeInertiaTensor();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Part::recomputeInertiaTensor()
|
||||
{
|
||||
if(!needsRecomputing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// recompute the whole composite inertia tensor
|
||||
// Reset the composite inertia tensor
|
||||
compositeInertiaTensor = inertiaTensor;
|
||||
compositeMass = mass;
|
||||
for(auto& [child, pos] : childParts)
|
||||
{
|
||||
child->recomputeInertiaTensor();
|
||||
compositeInertiaTensor += child->compositeInertiaTensor + child->compositeMass * ( pos.dot(pos) * Matrix3::Identity() - pos*pos.transpose());
|
||||
compositeMass += child->compositeMass;
|
||||
}
|
||||
needsRecomputing = false;
|
||||
}
|
||||
|
||||
} // namespace model
|
136
model/Part.h
Normal file
@ -0,0 +1,136 @@
|
||||
#ifndef MODEL_PART_H
|
||||
#define MODEL_PART_H
|
||||
|
||||
/// \cond
|
||||
// C headers
|
||||
// C++ headers
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
// 3rd party headers
|
||||
/// \endcond
|
||||
|
||||
// qtrocket headers
|
||||
#include "utils/math/MathTypes.h"
|
||||
|
||||
namespace model
|
||||
{
|
||||
|
||||
class Part
|
||||
{
|
||||
public:
|
||||
Part(const std::string& name,
|
||||
const Matrix3& I,
|
||||
double m,
|
||||
const Vector3& centerMass);
|
||||
|
||||
virtual ~Part();
|
||||
|
||||
Part(const Part&);
|
||||
|
||||
Part& operator=(Part other)
|
||||
{
|
||||
if(this != &other)
|
||||
{
|
||||
std::swap(parent, other.parent);
|
||||
std::swap(name, other.name);
|
||||
std::swap(inertiaTensor, other.inertiaTensor);
|
||||
std::swap(compositeInertiaTensor, other.compositeInertiaTensor);
|
||||
std::swap(mass, other.mass);
|
||||
std::swap(compositeMass, other.compositeMass);
|
||||
std::swap(cm, other.cm);
|
||||
std::swap(needsRecomputing, other.needsRecomputing);
|
||||
std::swap(childParts, other.childParts);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
Part& operator=(Part&& other)
|
||||
{
|
||||
parent = std::move(other.parent);
|
||||
name = std::move(other.name);
|
||||
inertiaTensor = std::move(other.inertiaTensor);
|
||||
compositeInertiaTensor = std::move(other.compositeInertiaTensor);
|
||||
mass = std::move(other.mass);
|
||||
compositeMass = std::move(other.compositeMass);
|
||||
cm = std::move(other.cm);
|
||||
needsRecomputing = std::move(other.needsRecomputing);
|
||||
childParts = std::move(other.childParts);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void setMass(double m) { mass = m; }
|
||||
|
||||
// Set the inertia tensor
|
||||
void setI(const Matrix3& I) { inertiaTensor = I; }
|
||||
Matrix3 getI() { return inertiaTensor; }
|
||||
Matrix3 getCompositeI() { return compositeInertiaTensor; }
|
||||
|
||||
void setCm(const Vector3& x) { cm = x; }
|
||||
// Special version of setCM that assumes the cm lies along the body x-axis
|
||||
void setCm(double x) { cm = {x, 0.0, 0.0}; }
|
||||
|
||||
double getMass(double t)
|
||||
{
|
||||
return mass;
|
||||
}
|
||||
|
||||
double getCompositeMass(double t)
|
||||
{
|
||||
return compositeMass;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Add a child part to this part.
|
||||
*
|
||||
* @param childPart Child part to add
|
||||
* @param position Relative position of the child part's center-of-mass w.r.t the
|
||||
* parent's center of mass
|
||||
*/
|
||||
void addChildPart(const Part& childPart, Vector3 position);
|
||||
|
||||
/**
|
||||
* @brief Recomputes the inertia tensor. If the change is due to the change in inertia
|
||||
* of a child part, an optional name of the child part can be given to
|
||||
* only recompute that change rather than recompute all child inertia
|
||||
* tensors
|
||||
*
|
||||
* @param name Optional name of the child part to recompute. If empty, it will
|
||||
* recompute all child inertia tensors
|
||||
*/
|
||||
//void recomputeInertiaTensor(std::string name = "");
|
||||
void recomputeInertiaTensor();
|
||||
private:
|
||||
|
||||
// This is a pointer to the parent Part, if it has one. Purpose is to be able to
|
||||
// tell the parent if it needs to recompute anything if this part changes. e.g.
|
||||
// if a change to this part's inertia tensor occurs, the parent needs to recompute
|
||||
// it's total inertia tensor.
|
||||
Part* parent{nullptr};
|
||||
|
||||
std::string name;
|
||||
|
||||
double getChildMasses(double t);
|
||||
void markAsNeedsRecomputing()
|
||||
{ needsRecomputing = true; if(parent) { parent->markAsNeedsRecomputing(); }}
|
||||
|
||||
// Because a part is both a simple part and the composite of itself with all of it's children,
|
||||
// we will keep track of this object's inertia tensor (without children), and the composite
|
||||
// one with all of it's children attached
|
||||
Matrix3 inertiaTensor; // moment of inertia tensor with respect to the part's center of mass and
|
||||
Matrix3 compositeInertiaTensor;
|
||||
double mass; // The moment of inertia tensor also has this, so don't double compute
|
||||
double compositeMass; // The mass of this part along with all attached parts
|
||||
|
||||
Vector3 cm; // center of mass wrt middle of component
|
||||
|
||||
bool needsRecomputing{false};
|
||||
|
||||
/// @brief child parts and the relative positions of their center of mass w.r.t.
|
||||
/// the center of mass of this part
|
||||
std::vector<std::tuple<std::shared_ptr<Part>, Vector3>> childParts;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // MODEL_PART_H
|
0
model/Propagatable.cpp
Normal file
58
model/Propagatable.h
Normal file
@ -0,0 +1,58 @@
|
||||
#ifndef MODEL_PROPAGATABLE_H
|
||||
#define MODEL_PROPAGATABLE_H
|
||||
|
||||
/// \cond
|
||||
// C headers
|
||||
// C++ headers
|
||||
#include <utility>
|
||||
// 3rd party headers
|
||||
/// \endcond
|
||||
|
||||
// qtrocket headers
|
||||
#include "sim/Aero.h"
|
||||
#include "sim/StateData.h"
|
||||
#include "utils/math/MathTypes.h"
|
||||
|
||||
namespace model
|
||||
{
|
||||
|
||||
class Propagatable
|
||||
{
|
||||
public:
|
||||
Propagatable() {}
|
||||
virtual ~Propagatable() {}
|
||||
|
||||
virtual Vector3 getForces(double t) = 0;
|
||||
virtual Vector3 getTorques(double t) = 0;
|
||||
|
||||
virtual double getMass(double t) = 0;
|
||||
virtual Matrix3 getInertiaTensor(double t) = 0;
|
||||
|
||||
virtual bool terminateCondition(double t) = 0;
|
||||
|
||||
void setCurrentState(const StateData& st) { currentState = st; }
|
||||
const StateData& getCurrentState() { return currentState; }
|
||||
|
||||
const StateData& getInitialState() { return initialState; }
|
||||
void setInitialState(const StateData& init) { initialState = init; }
|
||||
|
||||
void appendState(double t, const StateData& st) { states.emplace_back(t, st); }
|
||||
|
||||
const std::vector<std::pair<double, StateData>>& getStates() { return states; }
|
||||
|
||||
void clearStates() { states.clear(); }
|
||||
|
||||
protected:
|
||||
|
||||
sim::Aero aeroData;
|
||||
|
||||
StateData initialState;
|
||||
StateData currentState;
|
||||
StateData nextState;
|
||||
|
||||
std::vector<std::pair<double, StateData>> states;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // MODEL_PROPAGATABLE_H
|
79
model/RocketModel.cpp
Normal file
@ -0,0 +1,79 @@
|
||||
|
||||
// qtrocket headers
|
||||
#include "RocketModel.h"
|
||||
#include "QtRocket.h"
|
||||
#include "InertiaTensors.h"
|
||||
|
||||
namespace model
|
||||
{
|
||||
|
||||
RocketModel::RocketModel()
|
||||
: topPart("NoseCone", InertiaTensors::SolidSphere(1.0), 1.0, {0.0, 0.0, 1.0})
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
double RocketModel::getMass(double t)
|
||||
{
|
||||
double mass = mm.getMass(t);
|
||||
mass += topPart.getCompositeMass(t);
|
||||
return mass;
|
||||
}
|
||||
|
||||
Matrix3 RocketModel::getInertiaTensor(double)
|
||||
{
|
||||
return topPart.getCompositeI();
|
||||
}
|
||||
|
||||
bool RocketModel::terminateCondition(double)
|
||||
{
|
||||
// Terminate propagation when the z coordinate drops below zero
|
||||
if(currentState.position[2] < 0)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
Vector3 RocketModel::getForces(double t)
|
||||
{
|
||||
// Get thrust
|
||||
// Assume that thrust is always through the center of mass and in the rocket's Z-axis
|
||||
Vector3 forces{0.0, 0.0, mm.getThrust(t)};
|
||||
|
||||
|
||||
// Get gravity
|
||||
auto gravityModel = QtRocket::getInstance()->getEnvironment()->getGravityModel();
|
||||
|
||||
Vector3 gravity = gravityModel->getAccel(currentState.position)*getMass(t);
|
||||
|
||||
forces += gravity;
|
||||
|
||||
// Calculate aero forces
|
||||
|
||||
|
||||
return forces;
|
||||
}
|
||||
|
||||
Vector3 RocketModel::getTorques(double t)
|
||||
{
|
||||
return Vector3{0.0, 0.0, 0.0};
|
||||
|
||||
}
|
||||
|
||||
double RocketModel::getThrust(double t)
|
||||
{
|
||||
return mm.getThrust(t);
|
||||
}
|
||||
|
||||
void RocketModel::launch()
|
||||
{
|
||||
mm.startMotor(0.0);
|
||||
}
|
||||
|
||||
void RocketModel::setMotorModel(const model::MotorModel& motor)
|
||||
{
|
||||
mm = motor;
|
||||
}
|
||||
|
||||
} // namespace model
|
116
model/RocketModel.h
Normal file
@ -0,0 +1,116 @@
|
||||
#ifndef ROCKETMODEL_H
|
||||
#define ROCKETMODEL_H
|
||||
|
||||
/// \cond
|
||||
// C headers
|
||||
// C++ headers
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility> // std::move
|
||||
|
||||
// 3rd party headers
|
||||
/// \endcond
|
||||
|
||||
// qtrocket headers
|
||||
#include "model/Part.h"
|
||||
#include "sim/Propagator.h"
|
||||
#include "model/MotorModel.h"
|
||||
|
||||
#include "model/Propagatable.h"
|
||||
// Not yet
|
||||
//#include "model/Stage.h"
|
||||
|
||||
namespace model
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief The Rocket class holds all rocket components
|
||||
*
|
||||
*/
|
||||
class RocketModel : public Propagatable
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Rocket class constructor
|
||||
*/
|
||||
RocketModel();
|
||||
|
||||
/**
|
||||
* @brief Rocket class destructor
|
||||
*
|
||||
*/
|
||||
virtual ~RocketModel() {}
|
||||
|
||||
/**
|
||||
* @brief launch Propagates the Rocket object until termination,
|
||||
* normally when altitude crosses from positive to negative
|
||||
*/
|
||||
void launch();
|
||||
|
||||
Vector3 getForces(double t) override;
|
||||
Vector3 getTorques(double t) override;
|
||||
/**
|
||||
* @brief getMass returns current rocket mass
|
||||
* @param t current simulation time
|
||||
* @return mass in kg
|
||||
*/
|
||||
double getMass(double t) override;
|
||||
/**
|
||||
* @brief terminateCondition returns true or false, whether the passed-in time/state matches the terminate condition
|
||||
* @param cond time/state pair
|
||||
* @return true if the passed-in time/state satisfies the terminate condition
|
||||
*/
|
||||
bool terminateCondition(double t) override;
|
||||
|
||||
Matrix3 getInertiaTensor(double t) override;
|
||||
|
||||
/**
|
||||
* @brief getThrust returns current motor thrust
|
||||
* @param t current simulation time
|
||||
* @return thrust in Newtons
|
||||
*/
|
||||
double getThrust(double t);
|
||||
|
||||
|
||||
/**
|
||||
* @brief setMotorModel
|
||||
* @param motor
|
||||
*/
|
||||
void setMotorModel(const model::MotorModel& motor);
|
||||
|
||||
|
||||
/**
|
||||
* @brief getMotorModel
|
||||
*/
|
||||
MotorModel getMotorModel() { return mm; }
|
||||
|
||||
/**
|
||||
* @brief Returns the current motor model.
|
||||
* @return The current motor model
|
||||
*/
|
||||
//const model::MotorModel& getCurrentMotorModel() const { return mm; }
|
||||
|
||||
|
||||
/**
|
||||
* @brief setName sets the rocket name
|
||||
* @param n name to set the Rocket
|
||||
*/
|
||||
void setName(const std::string& n) { name = n; }
|
||||
|
||||
double getDragCoefficient() { return 1.0; }
|
||||
void setDragCoefficient(double d) { }
|
||||
void setMass(double m) { }
|
||||
|
||||
private:
|
||||
|
||||
std::string name; /// Rocket name
|
||||
|
||||
model::MotorModel mm; /// Current Motor Model
|
||||
|
||||
model::Part topPart;
|
||||
|
||||
};
|
||||
|
||||
} // namespace model
|
||||
#endif // ROCKETMODEL_H
|
90
model/ThrustCurve.cpp
Normal file
@ -0,0 +1,90 @@
|
||||
|
||||
/// \cond
|
||||
// C headers
|
||||
// C++ headers
|
||||
#include <algorithm>
|
||||
|
||||
// 3rd party headers
|
||||
/// \endcond
|
||||
|
||||
#include "model/ThrustCurve.h"
|
||||
|
||||
ThrustCurve::ThrustCurve(std::vector<std::pair<double, double>>& tc)
|
||||
: thrustCurve(tc),
|
||||
maxTime(0.0),
|
||||
ignitionTime(0.0)
|
||||
{
|
||||
maxTime = std::max_element(thrustCurve.begin(),
|
||||
thrustCurve.end(),
|
||||
[](const auto& a, const auto& b)
|
||||
{
|
||||
return a.first < b.first;
|
||||
})->first;
|
||||
}
|
||||
|
||||
ThrustCurve::ThrustCurve()
|
||||
{
|
||||
thrustCurve.emplace_back(0.0, 0.0);
|
||||
maxTime = 0.0;
|
||||
}
|
||||
|
||||
ThrustCurve::~ThrustCurve()
|
||||
{}
|
||||
|
||||
void ThrustCurve::setThrustCurveVector(const std::vector<std::pair<double, double>>& v)
|
||||
{
|
||||
thrustCurve.clear();
|
||||
thrustCurve.resize(v.size());
|
||||
std::copy(v.begin(), v.end(), thrustCurve.begin());
|
||||
maxTime = std::max_element(thrustCurve.begin(),
|
||||
thrustCurve.end(),
|
||||
[](const auto& a, const auto& b)
|
||||
{
|
||||
return a.first < b.first;
|
||||
})->first;
|
||||
}
|
||||
|
||||
void ThrustCurve::setIgnitionTime(double t)
|
||||
{
|
||||
ignitionTime = t;
|
||||
//maxTime += ignitionTime;
|
||||
}
|
||||
|
||||
double ThrustCurve::getThrust(double t)
|
||||
{
|
||||
// calculate t relative to the start time of the motor
|
||||
t -= ignitionTime;
|
||||
if(t < 0.0 || t > maxTime)
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// Find the right interval
|
||||
auto i = thrustCurve.cbegin();
|
||||
while(i->first <= t)
|
||||
{
|
||||
// If t is equal to a data point that we have, then just return
|
||||
// the thrust we know. Otherwise it fell between two points and we
|
||||
// will interpolate
|
||||
if(i->first == t)
|
||||
{
|
||||
return i->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
i++;
|
||||
}
|
||||
}
|
||||
// linearly interpolate the thrust and return
|
||||
// tStart and tEnd are the start time and the end time
|
||||
// of the current interval. thrustStart is the thrust at
|
||||
// the start of the interval.
|
||||
double tStart = std::prev(i)->first;
|
||||
double thrustStart = std::prev(i)->second;
|
||||
double thrustEnd = i->second;
|
||||
double tEnd = i->first;
|
||||
double slope = (thrustEnd - thrustStart) /
|
||||
(tEnd - tStart);
|
||||
return thrustStart + (t - tStart) * slope;
|
||||
|
||||
}
|
59
model/ThrustCurve.h
Normal file
@ -0,0 +1,59 @@
|
||||
#ifndef MODEL_THRUSTCURVE_H
|
||||
#define MODEL_THRUSTCURVE_H
|
||||
|
||||
/// \cond
|
||||
// C headers
|
||||
// C++ headers
|
||||
#include <vector>
|
||||
|
||||
// 3rd party headers
|
||||
/// \endcond
|
||||
|
||||
class ThrustCurve
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Constructor takes a vector of pairs. The first item a timestamp,
|
||||
* the second the thrust in newtons.
|
||||
*/
|
||||
ThrustCurve(std::vector<std::pair<double, double>>& tc);
|
||||
/**
|
||||
* Default constructor. Will create an empty thrustcurve, always returning 0.0
|
||||
* for all requested times.
|
||||
*/
|
||||
ThrustCurve();
|
||||
ThrustCurve(const ThrustCurve&) = default;
|
||||
ThrustCurve(ThrustCurve&&) = default;
|
||||
~ThrustCurve();
|
||||
|
||||
ThrustCurve& operator=(const ThrustCurve& rhs) = default;
|
||||
|
||||
ThrustCurve& operator=(ThrustCurve&& rhs) = default;
|
||||
|
||||
/**
|
||||
* Assuming that the thrust is one dimensional. Seems reasonable, but just
|
||||
* documenting that for the record. For timesteps between known points the thrust
|
||||
* is interpolated linearly
|
||||
* @param t The time in seconds. For t > burntime or < 0, this will return 0.0
|
||||
* @return Thrust in Newtons
|
||||
*/
|
||||
double getThrust(double t);
|
||||
|
||||
void setIgnitionTime(double t);
|
||||
|
||||
double getMaxTime() const { return maxTime; }
|
||||
|
||||
/**
|
||||
* TODO: Get rid of this. This is for temporary testing
|
||||
*/
|
||||
void setThrustCurveVector(const std::vector<std::pair<double, double>>& v);
|
||||
|
||||
const std::vector<std::pair<double, double>> getThrustCurveData() const { return thrustCurve; }
|
||||
|
||||
private:
|
||||
std::vector<std::pair<double, double>> thrustCurve;
|
||||
double maxTime{0.0};
|
||||
double ignitionTime{0.0};
|
||||
};
|
||||
|
||||
#endif // MODEL_THRUSTCURVE_H
|
16
model/tests/CMakeLists.txt
Normal file
@ -0,0 +1,16 @@
|
||||
|
||||
add_executable(model_tests
|
||||
PartTests.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(model_tests PRIVATE
|
||||
model
|
||||
utils
|
||||
GTest::gtest_main
|
||||
)
|
||||
|
||||
include(GoogleTest)
|
||||
gtest_discover_tests(model_tests)
|
||||
|
||||
add_test(NAME qtrocket_model_tests COMMAND model_tests)
|
||||
|
70
model/tests/PartTests.cpp
Normal file
@ -0,0 +1,70 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "model/Part.h"
|
||||
|
||||
class PartTest : public testing::Test
|
||||
{
|
||||
protected:
|
||||
// Per-test-suite set-up.
|
||||
// Called before the first test in this test suite.
|
||||
// Can be omitted if not needed.
|
||||
static void SetUpTestSuite()
|
||||
{
|
||||
//shared_resource_ = new ...;
|
||||
|
||||
// If `shared_resource_` is **not deleted** in `TearDownTestSuite()`,
|
||||
// reallocation should be prevented because `SetUpTestSuite()` may be called
|
||||
// in subclasses of FooTest and lead to memory leak.
|
||||
//
|
||||
// if (shared_resource_ == nullptr) {
|
||||
// shared_resource_ = new ...;
|
||||
// }
|
||||
}
|
||||
|
||||
// Per-test-suite tear-down.
|
||||
// Called after the last test in this test suite.
|
||||
// Can be omitted if not needed.
|
||||
static void TearDownTestSuite()
|
||||
{
|
||||
//delete shared_resource_;
|
||||
//shared_resource_ = nullptr;
|
||||
}
|
||||
|
||||
// You can define per-test set-up logic as usual.
|
||||
void SetUp() override { }
|
||||
|
||||
// You can define per-test tear-down logic as usual.
|
||||
void TearDown() override { }
|
||||
|
||||
// Some expensive resource shared by all tests.
|
||||
//static T* shared_resource_;
|
||||
};
|
||||
|
||||
//T* FooTest::shared_resource_ = nullptr;
|
||||
|
||||
TEST(PartTest, CreationTests)
|
||||
{
|
||||
Matrix3 inertia;
|
||||
inertia << 1, 0, 0,
|
||||
0, 1, 0,
|
||||
0, 0, 1;
|
||||
Vector3 cm{1, 0, 0};
|
||||
model::Part testPart("testPart",
|
||||
inertia,
|
||||
1.0,
|
||||
cm);
|
||||
|
||||
Matrix3 inertia2;
|
||||
inertia2 << 1, 0, 0,
|
||||
0, 1, 0,
|
||||
0, 0, 1;
|
||||
Vector3 cm2{1, 0, 0};
|
||||
model::Part testPart2("testPart2",
|
||||
inertia2,
|
||||
1.0,
|
||||
cm2);
|
||||
Vector3 R{2.0, 2.0, 2.0};
|
||||
testPart.addChildPart(testPart2, R);
|
||||
|
||||
|
||||
}
|
6
qtrocket.qrc
Normal file
@ -0,0 +1,6 @@
|
||||
<RCC>
|
||||
<qresource prefix="/"/>
|
||||
<qresource prefix="/images">
|
||||
<file>resources/rocket64.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
BIN
resources/qtrocket.ico
Normal file
After Width: | Height: | Size: 333 KiB |
BIN
resources/rocket48.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
resources/rocket64.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
resources/screenshots/Altitude.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
resources/screenshots/MainWindow.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
resources/screenshots/MainWindow2.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
resources/screenshots/OpenDataMotorFileWindow.png
Normal file
After Width: | Height: | Size: 76 KiB |
BIN
resources/screenshots/SimulationOptionsWindow.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
resources/screenshots/ThrustCurveWindow.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
resources/screenshots/Velocity.png
Normal file
After Width: | Height: | Size: 28 KiB |
0
sim/Aero.cpp
Normal file
40
sim/Aero.h
Normal file
@ -0,0 +1,40 @@
|
||||
#ifndef SIM_AERO_H
|
||||
#define SIM_AERO_H
|
||||
|
||||
/// \cond
|
||||
// C headers
|
||||
// C++ headers
|
||||
|
||||
// 3rd party headers
|
||||
/// \endcond
|
||||
|
||||
// qtrocket headers
|
||||
#include "utils/math/MathTypes.h"
|
||||
|
||||
namespace sim
|
||||
{
|
||||
|
||||
class Aero
|
||||
{
|
||||
public:
|
||||
|
||||
private:
|
||||
|
||||
Vector3 cp; /// center of pressure
|
||||
|
||||
double Cx; /// longitudinal coefficient
|
||||
double Cy; /// These are probably the same for axial symmetric
|
||||
double Cz; /// rockets. The coeffients in the y and z body directions
|
||||
|
||||
double Cl; // roll moment coefficient
|
||||
double Cm; // pitch moment coefficient
|
||||
double Cn; // yaw moment coefficient
|
||||
|
||||
double baseCd; // coefficient of drag due to base drag
|
||||
double Cd; // total coeffient of drag
|
||||
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
#endif // SIM_AERO_H
|
23
sim/AtmosphericModel.h
Normal file
@ -0,0 +1,23 @@
|
||||
#ifndef SIM_ATMOSPHERICMODEL_H
|
||||
#define SIM_ATMOSPHERICMODEL_H
|
||||
|
||||
namespace sim
|
||||
{
|
||||
|
||||
class AtmosphericModel
|
||||
{
|
||||
public:
|
||||
AtmosphericModel() {}
|
||||
virtual ~AtmosphericModel() {}
|
||||
|
||||
virtual double getDensity(double altitude) = 0;
|
||||
virtual double getPressure(double altitude) = 0;
|
||||
virtual double getTemperature(double altitude) = 0;
|
||||
|
||||
virtual double getSpeedOfSound(double altitude) = 0;
|
||||
virtual double getDynamicViscosity(double altitude) = 0;
|
||||
|
||||
};
|
||||
|
||||
} // namespace sim
|
||||
#endif // SIM_ATMOSPHERICMODEL_H
|
28
sim/CMakeLists.txt
Normal file
@ -0,0 +1,28 @@
|
||||
add_library(sim
|
||||
Aero.cpp
|
||||
Aero.h
|
||||
AtmosphericModel.h
|
||||
ConstantAtmosphere.h
|
||||
ConstantGravityModel.h
|
||||
DESolver.h
|
||||
Environment.h
|
||||
GeoidModel.h
|
||||
GravityModel.h
|
||||
Propagator.cpp
|
||||
Propagator.h
|
||||
RK4Solver.h
|
||||
SphericalGeoidModel.cpp
|
||||
SphericalGeoidModel.h
|
||||
SphericalGravityModel.cpp
|
||||
SphericalGravityModel.h
|
||||
StateData.h
|
||||
USStandardAtmosphere.cpp
|
||||
USStandardAtmosphere.h
|
||||
WindModel.cpp
|
||||
WindModel.h)
|
||||
|
||||
target_link_libraries(sim PRIVATE
|
||||
utils)
|
||||
|
||||
# Unit tests
|
||||
add_subdirectory(tests)
|
26
sim/ConstantAtmosphere.h
Normal file
@ -0,0 +1,26 @@
|
||||
#ifndef SIM_CONSTANTATMOSPHERE_H
|
||||
#define SIM_CONSTANTATMOSPHERE_H
|
||||
|
||||
// qtrocket headers
|
||||
#include "AtmosphericModel.h"
|
||||
|
||||
namespace sim {
|
||||
|
||||
class ConstantAtmosphere : public AtmosphericModel
|
||||
{
|
||||
public:
|
||||
ConstantAtmosphere() {}
|
||||
virtual ~ConstantAtmosphere() {}
|
||||
|
||||
double getDensity(double) override { return 1.225; }
|
||||
double getPressure(double) override { return 101325.0; }
|
||||
double getTemperature(double) override { return 288.15; }
|
||||
|
||||
double getSpeedOfSound(double) override { return 340.294; }
|
||||
|
||||
double getDynamicViscosity(double) override { return 1.78938e-5; }
|
||||
};
|
||||
|
||||
} // namespace sim
|
||||
|
||||
#endif // SIM_CONSTANTATMOSPHERE_H
|
25
sim/ConstantGravityModel.h
Normal file
@ -0,0 +1,25 @@
|
||||
#ifndef SIM_CONSTANTGRAVITYMODEL_H
|
||||
#define SIM_CONSTANTGRAVITYMODEL_H
|
||||
|
||||
// qtrocket headers
|
||||
#include "sim/GravityModel.h"
|
||||
#include "utils/math/MathTypes.h"
|
||||
|
||||
namespace sim {
|
||||
|
||||
class ConstantGravityModel : public GravityModel
|
||||
{
|
||||
public:
|
||||
ConstantGravityModel() {}
|
||||
|
||||
virtual ~ConstantGravityModel() {}
|
||||
|
||||
Vector3 getAccel(double, double, double) override
|
||||
{
|
||||
return Vector3(0.0, 0.0, -9.8);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace sim
|
||||
|
||||
#endif // SIM_CONSTANTGRAVITYMODEL_H
|
40
sim/DESolver.h
Normal file
@ -0,0 +1,40 @@
|
||||
#ifndef SIM_DESOLVER_H
|
||||
#define SIM_DESOLVER_H
|
||||
|
||||
/// \cond
|
||||
// C headers
|
||||
// C++ headers
|
||||
#include <utility>
|
||||
|
||||
// 3rd party headers
|
||||
/// \endcond
|
||||
|
||||
// qtrocket headers
|
||||
|
||||
namespace sim
|
||||
{
|
||||
|
||||
template<typename T>
|
||||
class DESolver
|
||||
{
|
||||
public:
|
||||
DESolver() {}
|
||||
virtual ~DESolver() {}
|
||||
|
||||
virtual void setTimeStep(double ts) = 0;
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
* @param curVal
|
||||
* @param res
|
||||
* @param t Optional parameter, but not used in QtRocket. Some generic solvers take time as
|
||||
* a parameter to ODEs, but QtRocket's kinematic equations don't. Since I wrote
|
||||
* the RK4 solver independently as a general tool, this interface is needed
|
||||
* here unfortunately.
|
||||
*/
|
||||
virtual std::pair<T, T> step(T& state, T& rate) = 0;
|
||||
};
|
||||
|
||||
} // namespace sim
|
||||
|
||||
#endif // SIM_DESOLVER_H
|
111
sim/Environment.h
Normal file
@ -0,0 +1,111 @@
|
||||
#ifndef SIM_ENVIRONMENT_H
|
||||
#define SIM_ENVIRONMENT_H
|
||||
/// \cond
|
||||
// C headers
|
||||
// C++ headers
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <iterator>
|
||||
// 3rd party headers
|
||||
/// \endcond
|
||||
|
||||
// qtrocket headers
|
||||
#include "sim/ConstantGravityModel.h"
|
||||
#include "sim/SphericalGravityModel.h"
|
||||
|
||||
#include "sim/ConstantAtmosphere.h"
|
||||
#include "sim/USStandardAtmosphere.h"
|
||||
|
||||
namespace sim
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Holds simulation environment information, such as the gravity model, atmosphere model,
|
||||
* Geoid model
|
||||
*
|
||||
*/
|
||||
class Environment
|
||||
{
|
||||
public:
|
||||
Environment()
|
||||
{
|
||||
setGravityModel("Constant Gravity");
|
||||
setAtmosphereModel("Constant Atmosphere");
|
||||
}
|
||||
~Environment() = default;
|
||||
Environment(const Environment&) = delete;
|
||||
Environment(Environment&&) = delete;
|
||||
Environment& operator=(const Environment&) = delete;
|
||||
Environment& operator=(Environment&&) = delete;
|
||||
|
||||
std::vector<std::string> getAvailableGravityModels()
|
||||
{
|
||||
std::vector<std::string> retVal(gravityModels.size());
|
||||
std::transform(gravityModels.begin(), gravityModels.end(), std::back_inserter(retVal),
|
||||
[](auto& i) { return i.first; });
|
||||
return retVal;
|
||||
}
|
||||
|
||||
std::vector<std::string> getAvailableAtmosphereModels()
|
||||
{
|
||||
std::vector<std::string> retVal(atmosphereModels.size());
|
||||
std::transform(atmosphereModels.begin(), atmosphereModels.end(), std::back_inserter(retVal),
|
||||
[](auto& i) { return i.first; });
|
||||
return retVal;
|
||||
}
|
||||
|
||||
void setGravityModel(const std::string& model)
|
||||
{
|
||||
if(model == "Constant Gravity")
|
||||
{
|
||||
gravityModel = model;
|
||||
gravityModels[gravityModel].reset(new sim::ConstantGravityModel);
|
||||
}
|
||||
else if(model == "Spherical Gravity")
|
||||
{
|
||||
gravityModel = model;
|
||||
gravityModels[gravityModel].reset(new sim::SphericalGravityModel);
|
||||
}
|
||||
}
|
||||
|
||||
void setAtmosphereModel(const std::string& model)
|
||||
{
|
||||
if(model == "Constant Atmosphere")
|
||||
{
|
||||
atmosphereModel = model;
|
||||
atmosphereModels[atmosphereModel].reset(new sim::ConstantAtmosphere);
|
||||
}
|
||||
else if(model == "US Standard 1976")
|
||||
{
|
||||
atmosphereModel = model;
|
||||
atmosphereModels[atmosphereModel].reset(new sim::USStandardAtmosphere);
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<sim::AtmosphericModel> getAtmosphericModel()
|
||||
{
|
||||
auto retVal = atmosphereModels[atmosphereModel];
|
||||
return retVal;
|
||||
}
|
||||
std::shared_ptr<sim::GravityModel> getGravityModel() { return gravityModels[gravityModel]; }
|
||||
|
||||
private:
|
||||
|
||||
std::map<std::string, std::shared_ptr<sim::AtmosphericModel>> atmosphereModels{
|
||||
{"Constant Atmosphere", std::shared_ptr<sim::AtmosphericModel>()},
|
||||
{"US Standard 1976", std::shared_ptr<sim::AtmosphericModel>()}};
|
||||
|
||||
std::map<std::string, std::shared_ptr<GravityModel>> gravityModels{
|
||||
{"Constant Gravity", std::shared_ptr<sim::GravityModel>()},
|
||||
{"Spherical Gravity", std::shared_ptr<sim::GravityModel>()}};
|
||||
|
||||
std::string gravityModel{"Constant Gravity"}; /// Constant Gravity Model is the default
|
||||
std::string atmosphereModel{"Constant Atmosphere"}; /// Constant Atmosphere Model is the default
|
||||
};
|
||||
|
||||
} // namespace sim
|
||||
|
||||
|
||||
#endif // SIM_ENVIRONMENT_H
|
25
sim/GeoidModel.h
Normal file
@ -0,0 +1,25 @@
|
||||
#ifndef SIM_GEOIDMODEL_H
|
||||
#define SIM_GEOIDMODEL_H
|
||||
|
||||
namespace sim
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief The GeoidModel represents the physical ellipsoid of the earth. It is used
|
||||
* to determin the distance of the local ground level from the center of mass
|
||||
* of the earth.
|
||||
*
|
||||
*/
|
||||
class GeoidModel
|
||||
{
|
||||
public:
|
||||
GeoidModel() {}
|
||||
virtual ~GeoidModel() {}
|
||||
|
||||
virtual double getGroundLevel(double latitude, double longitude) = 0;
|
||||
|
||||
};
|
||||
|
||||
} // namespace sim
|
||||
|
||||
#endif // SIM_GEOIDMODEL_H
|
21
sim/GravityModel.h
Normal file
@ -0,0 +1,21 @@
|
||||
#ifndef SIM_GRAVITYMODEL_H
|
||||
#define SIM_GRAVITYMODEL_H
|
||||
|
||||
// qtrocket headers
|
||||
#include "utils/math/MathTypes.h"
|
||||
|
||||
namespace sim
|
||||
{
|
||||
|
||||
class GravityModel
|
||||
{
|
||||
public:
|
||||
GravityModel() {}
|
||||
virtual ~GravityModel() {}
|
||||
|
||||
virtual Vector3 getAccel(double x, double y, double z) = 0;
|
||||
Vector3 getAccel(const Vector3& t) { return this->getAccel(t.x(), t.y(), t.z()); }
|
||||
};
|
||||
|
||||
} // namespace sim
|
||||
#endif // SIM_GRAVITYMODEL_H
|
91
sim/Propagator.cpp
Normal file
@ -0,0 +1,91 @@
|
||||
|
||||
/// \cond
|
||||
// C headers
|
||||
// C++ headers
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
|
||||
// 3rd party headers
|
||||
/// \endcond
|
||||
|
||||
// qtrocket headers
|
||||
#include "Propagator.h"
|
||||
|
||||
#include "sim/RK4Solver.h"
|
||||
#include "utils/Logger.h"
|
||||
|
||||
namespace sim
|
||||
{
|
||||
|
||||
Propagator::Propagator(std::shared_ptr<model::Propagatable> r)
|
||||
: linearIntegrator(),
|
||||
object(r),
|
||||
saveStates(true),
|
||||
timeStep(0.01)
|
||||
{
|
||||
// Linear velocity and acceleration
|
||||
std::function<std::pair<Vector3, Vector3>(Vector3&, Vector3&)> linearODEs = [this](Vector3& state, Vector3& rate) -> std::pair<Vector3, Vector3>
|
||||
{
|
||||
Vector3 dPosition;
|
||||
Vector3 dVelocity;
|
||||
// dx/dt
|
||||
dPosition = rate;
|
||||
|
||||
// dvx/dt
|
||||
dVelocity = object->getForces(currentTime) / object->getMass(currentTime);
|
||||
|
||||
return std::make_pair(dPosition, dVelocity);
|
||||
};
|
||||
|
||||
linearIntegrator.reset(new RK4Solver<Vector3>(linearODEs));
|
||||
linearIntegrator->setTimeStep(timeStep);
|
||||
|
||||
saveStates = true;
|
||||
}
|
||||
|
||||
Propagator::~Propagator()
|
||||
{
|
||||
}
|
||||
|
||||
void Propagator::runUntilTerminate()
|
||||
{
|
||||
std::chrono::steady_clock::time_point startTime = std::chrono::steady_clock::now();
|
||||
std::chrono::steady_clock::time_point endTime;
|
||||
|
||||
Vector3 currentPosition;
|
||||
Vector3 currentVelocity;
|
||||
Vector3 nextPosition;
|
||||
Vector3 nextVelocity;
|
||||
while(true)
|
||||
{
|
||||
currentPosition = object->getCurrentState().position;
|
||||
currentVelocity = object->getCurrentState().velocity;
|
||||
|
||||
std::tie(nextPosition, nextVelocity) = linearIntegrator->step(currentPosition, currentVelocity);
|
||||
|
||||
StateData nextState;
|
||||
nextState.position = nextPosition;
|
||||
nextState.velocity = nextVelocity;
|
||||
object->setCurrentState(nextState);
|
||||
|
||||
if(saveStates)
|
||||
{
|
||||
object->appendState(currentTime, nextState);
|
||||
}
|
||||
if(object->terminateCondition(currentTime))
|
||||
break;
|
||||
|
||||
currentTime += timeStep;
|
||||
}
|
||||
endTime = std::chrono::steady_clock::now();
|
||||
|
||||
std::stringstream duration;
|
||||
duration << "runUntilTerminate time (microseconds): ";
|
||||
duration << std::chrono::duration_cast<std::chrono::microseconds>(endTime - startTime).count();
|
||||
utils::Logger::getInstance()->debug(duration.str());
|
||||
|
||||
}
|
||||
|
||||
} // namespace sim
|
71
sim/Propagator.h
Normal file
@ -0,0 +1,71 @@
|
||||
#ifndef SIM_PROPAGATOR_H
|
||||
#define SIM_PROPAGATOR_H
|
||||
|
||||
/// \cond
|
||||
// C headers
|
||||
// C++ headers
|
||||
#include <memory>
|
||||
|
||||
// 3rd party headers
|
||||
/// \endcond
|
||||
|
||||
// qtrocket headers
|
||||
#include "sim/RK4Solver.h"
|
||||
#include "utils/math/MathTypes.h"
|
||||
#include "sim/StateData.h"
|
||||
#include "model/Propagatable.h"
|
||||
|
||||
|
||||
// Forward declare
|
||||
namespace model
|
||||
{
|
||||
class Rocket;
|
||||
}
|
||||
class QtRocket;
|
||||
|
||||
namespace sim
|
||||
{
|
||||
|
||||
class Propagator
|
||||
{
|
||||
public:
|
||||
Propagator(std::shared_ptr<model::Propagatable> o);
|
||||
~Propagator();
|
||||
|
||||
void setInitialState(const StateData& initialState)
|
||||
{
|
||||
object->setInitialState(initialState);
|
||||
}
|
||||
|
||||
const StateData& getCurrentState() const
|
||||
{
|
||||
return object->getCurrentState();
|
||||
}
|
||||
|
||||
void runUntilTerminate();
|
||||
|
||||
void retainStates(bool s)
|
||||
{
|
||||
saveStates = s;
|
||||
}
|
||||
|
||||
void setCurrentTime(double t) { currentTime = t; }
|
||||
void setTimeStep(double ts) { timeStep = ts; }
|
||||
void setSaveStats(bool s) { saveStates = s; }
|
||||
|
||||
private:
|
||||
|
||||
std::unique_ptr<sim::RK4Solver<Vector3>> linearIntegrator;
|
||||
// std::unique_ptr<sim::RK4Solver<Quaternion>> orientationIntegrator;
|
||||
|
||||
std::shared_ptr<model::Propagatable> object;
|
||||
|
||||
bool saveStates{true};
|
||||
double currentTime{0.0};
|
||||
double timeStep{0.01};
|
||||
|
||||
};
|
||||
|
||||
} // namespace sim
|
||||
|
||||
#endif // SIM_PROPAGATOR_H
|
@ -12,7 +12,11 @@
|
||||
/// \endcond
|
||||
|
||||
// qtrocket headers
|
||||
#include "Logger.h"
|
||||
#include "sim/DESolver.h"
|
||||
#include "utils/Logger.h"
|
||||
#include "utils/math/MathTypes.h"
|
||||
|
||||
namespace sim {
|
||||
|
||||
/**
|
||||
* @brief Runge-Kutta 4th order coupled ODE solver.
|
||||
@ -24,28 +28,31 @@
|
||||
* @tparam Ts
|
||||
*/
|
||||
template<typename T>
|
||||
class RK4Solver
|
||||
class RK4Solver : public DESolver<T>
|
||||
{
|
||||
public:
|
||||
|
||||
RK4Solver(std::function<std::pair<T, T>(T&, T&)> func, double ts=0.1)
|
||||
RK4Solver(std::function<std::pair<T, T>(T&, T&)> func)
|
||||
{
|
||||
// static_assert(std::is_same<T, Vector3>::value,
|
||||
// "You can only use Vector3 or Quaternion valued functions in RK4Solver");
|
||||
// This only works for Eigen Vector types.
|
||||
// TODO: Figure out how to make this slightly more generic, but for now
|
||||
// we're only using this for Vector3 and Quaternion types
|
||||
static_assert(std::is_same<T, Vector3>::value
|
||||
|| std::is_same<T, Quaternion>::value,
|
||||
"You can only use Vector3 or Quaternion valued functions in RK4Solver");
|
||||
|
||||
odes = func;
|
||||
setTimeStep(ts);
|
||||
}
|
||||
~RK4Solver() {}
|
||||
virtual ~RK4Solver() {}
|
||||
|
||||
void setTimeStep(double inTs) { dt = inTs; halfDT = dt / 2.0; }
|
||||
void setTimeStep(double inTs) override { dt = inTs; halfDT = dt / 2.0; }
|
||||
|
||||
std::pair<T, T> step(T& state, T& rate)
|
||||
std::pair<T, T> step(T& state, T& rate) override
|
||||
{
|
||||
std::pair<T, T> res;
|
||||
if(dt == std::numeric_limits<double>::quiet_NaN())
|
||||
{
|
||||
Logger::getInstance()->error("Calling RK4Solver without setting dt first is an error");
|
||||
utils::Logger::getInstance()->error("Calling RK4Solver without setting dt first is an error");
|
||||
return res;
|
||||
}
|
||||
|
||||
@ -87,4 +94,6 @@ private:
|
||||
|
||||
};
|
||||
|
||||
} // namespace sim
|
||||
|
||||
#endif // SIM_RK4SOLVER_H
|
23
sim/SphericalGeoidModel.cpp
Normal file
@ -0,0 +1,23 @@
|
||||
|
||||
// qtrocket headers
|
||||
#include "sim/SphericalGeoidModel.h"
|
||||
#include "utils/math/Constants.h"
|
||||
|
||||
namespace sim
|
||||
{
|
||||
|
||||
SphericalGeoidModel::SphericalGeoidModel()
|
||||
{
|
||||
}
|
||||
|
||||
SphericalGeoidModel::~SphericalGeoidModel()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
double SphericalGeoidModel::getGroundLevel(double, double)
|
||||
{
|
||||
return utils::math::Constants::meanEarthRadiusWGS84;
|
||||
}
|
||||
|
||||
} // namespace sim
|
28
sim/SphericalGeoidModel.h
Normal file
@ -0,0 +1,28 @@
|
||||
#ifndef SIM_SPHERICALGEOIDMODEL_H
|
||||
#define SIM_SPHERICALGEOIDMODEL_H
|
||||
|
||||
// qtrocket headers
|
||||
#include "GeoidModel.h"
|
||||
|
||||
namespace sim
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief The SphericalGeoidModel returns the average of the polar radius and equatorial
|
||||
* radius of the Earth, based on WGS84
|
||||
*
|
||||
*/
|
||||
|
||||
class SphericalGeoidModel : public GeoidModel
|
||||
{
|
||||
public:
|
||||
SphericalGeoidModel();
|
||||
virtual ~SphericalGeoidModel();
|
||||
|
||||
double getGroundLevel(double latitude, double longitude) override;
|
||||
|
||||
};
|
||||
|
||||
} // namespace sim
|
||||
|
||||
#endif // SIM_SPHERICALGEOIDMODEL_H
|
50
sim/SphericalGravityModel.cpp
Normal file
@ -0,0 +1,50 @@
|
||||
|
||||
/// \cond
|
||||
// C headers
|
||||
// C++ headers
|
||||
#include <cmath>
|
||||
|
||||
// 3rd party headers
|
||||
/// \endcond
|
||||
|
||||
// qtrocket headers
|
||||
#include "sim/SphericalGravityModel.h"
|
||||
#include "utils/math/Constants.h"
|
||||
|
||||
|
||||
namespace sim
|
||||
{
|
||||
|
||||
SphericalGravityModel::SphericalGravityModel()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
SphericalGravityModel::~SphericalGravityModel()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Vector3 SphericalGravityModel::getAccel(double x, double y, double z)
|
||||
{
|
||||
// Convert x, y, z from meters to km. This is to avoid potential precision losses
|
||||
// with using the earth's gravitation parameter in meters (14 digit number).
|
||||
// GM in kilometers is much more manageable.
|
||||
// An alternative is to use quadruple precision, but that may
|
||||
// take a lot more computation time and I think this will be fine.
|
||||
double x_km = x / 1000.0;
|
||||
double y_km = y / 1000.0;
|
||||
double z_km = z / 1000.0;
|
||||
|
||||
double r_km = std::sqrt(x_km * x_km + y_km * y_km + z_km * z_km);
|
||||
|
||||
double accelFactor = - utils::math::Constants::earthGM_km / std::sqrt(r_km * r_km * r_km);
|
||||
double ax = accelFactor * x_km * 1000.0;
|
||||
double ay = accelFactor * y_km * 1000.0;
|
||||
double az = accelFactor * z_km * 1000.0;
|
||||
|
||||
return Vector3(ax, ay, az);
|
||||
}
|
||||
|
||||
|
||||
}
|
21
sim/SphericalGravityModel.h
Normal file
@ -0,0 +1,21 @@
|
||||
#ifndef SIM_SPHERICALGRAVITYMODEL_H
|
||||
#define SIM_SPHERICALGRAVITYMODEL_H
|
||||
|
||||
// qtrocket headers
|
||||
#include "sim/GravityModel.h"
|
||||
|
||||
namespace sim
|
||||
{
|
||||
|
||||
class SphericalGravityModel : public GravityModel
|
||||
{
|
||||
public:
|
||||
SphericalGravityModel();
|
||||
virtual ~SphericalGravityModel();
|
||||
|
||||
Vector3 getAccel(double x, double y, double z) override;
|
||||
};
|
||||
|
||||
} // namespace sim
|
||||
|
||||
#endif // SIM_SPHERICALGRAVITYMODEL_H
|
95
sim/StateData.h
Normal file
@ -0,0 +1,95 @@
|
||||
#ifndef STATEDATA_H
|
||||
#define STATEDATA_H
|
||||
|
||||
/// \cond
|
||||
// C headers
|
||||
// C++ headers
|
||||
#include <vector>
|
||||
// 3rd party headers
|
||||
/// \endcond
|
||||
|
||||
// qtrocket headers
|
||||
#include "utils/math/MathTypes.h"
|
||||
|
||||
/**
|
||||
* @brief The StateData class holds physical state data. Things such as position, velocity,
|
||||
* and acceleration of the center of mass, as well as orientation and orientation
|
||||
* change rates.
|
||||
*/
|
||||
class StateData
|
||||
{
|
||||
public:
|
||||
StateData() {}
|
||||
~StateData() {}
|
||||
|
||||
StateData(const StateData&) = default;
|
||||
StateData(StateData&&) = default;
|
||||
|
||||
StateData& operator=(const StateData& rhs)
|
||||
{
|
||||
if(this != &rhs)
|
||||
{
|
||||
position = rhs.position;
|
||||
velocity = rhs.velocity;
|
||||
orientation = rhs.orientation;
|
||||
orientationRate = rhs.orientationRate;
|
||||
dcm = rhs.dcm;
|
||||
eulerAngles = rhs.eulerAngles;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
StateData& operator=(StateData&& rhs)
|
||||
{
|
||||
if(this != &rhs)
|
||||
{
|
||||
position = std::move(rhs.position);
|
||||
velocity = std::move(rhs.velocity);
|
||||
orientation = std::move(rhs.orientation);
|
||||
orientationRate = std::move(rhs.orientationRate);
|
||||
dcm = std::move(rhs.dcm);
|
||||
eulerAngles = std::move(rhs.eulerAngles);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::vector<double> getPosStdVector() const
|
||||
{
|
||||
return std::vector<double>{position[0], position[1], position[2]};
|
||||
}
|
||||
std::vector<double> getVelStdVector() const
|
||||
{
|
||||
return std::vector<double>{velocity[0], velocity[1], velocity[2]};
|
||||
}
|
||||
|
||||
|
||||
/// TODO: Put these behind an interface
|
||||
//Vector3 getPosition() const
|
||||
//{
|
||||
// return position;
|
||||
//}
|
||||
|
||||
//Vector3 getVelocity() const
|
||||
//{
|
||||
// return velocity;
|
||||
//}
|
||||
// private:
|
||||
|
||||
// Intended to be used as world state data
|
||||
Vector3 position{0.0, 0.0, 0.0};
|
||||
Vector3 velocity{0.0, 0.0, 0.0};
|
||||
|
||||
// Orientation of body coordinates w.r.t. world coordinates
|
||||
Quaternion orientation{0.0, 0.0, 0.0, 0.0}; /// (vector, scalar)
|
||||
Quaternion orientationRate{0.0, 0.0, 0.0, 0.0}; /// (vector, scalar)
|
||||
|
||||
Matrix3 dcm{{0, 0, 0}, {0, 0, 0}, {0, 0, 0}};
|
||||
|
||||
/// Euler angles are yaw-pitch-roll, and (3-2-1) order
|
||||
/// yaw - psi
|
||||
/// pitch - theta
|
||||
/// roll - phi
|
||||
Vector3 eulerAngles{0.0, 0.0, 0.0};
|
||||
|
||||
};
|
||||
|
||||
#endif // STATEDATA_H
|
155
sim/USStandardAtmosphere.cpp
Normal file
@ -0,0 +1,155 @@
|
||||
|
||||
/// \cond
|
||||
// C headers
|
||||
// C++ headers
|
||||
#include <cmath>
|
||||
|
||||
// 3rd party headers
|
||||
/// \endcond
|
||||
|
||||
// qtrocket headers
|
||||
#include "sim/USStandardAtmosphere.h"
|
||||
#include "utils/math/Constants.h"
|
||||
#include "utils/math/UtilityMathFunctions.h"
|
||||
|
||||
using namespace utils::math;
|
||||
|
||||
namespace sim
|
||||
{
|
||||
|
||||
// Populate static data
|
||||
utils::Bin initTemps()
|
||||
{
|
||||
utils::Bin map;
|
||||
map.insert(std::make_pair(0.0, 288.15));
|
||||
map.insert(std::make_pair(11000.0, 216.65));
|
||||
map.insert(std::make_pair(20000.0, 216.65));
|
||||
map.insert(std::make_pair(32000.0, 228.65));
|
||||
map.insert(std::make_pair(47000.0, 270.65));
|
||||
map.insert(std::make_pair(51000.0, 270.65));
|
||||
map.insert(std::make_pair(71000.0, 214.65));
|
||||
|
||||
return map;
|
||||
|
||||
}
|
||||
|
||||
utils::Bin initLapseRates()
|
||||
{
|
||||
utils::Bin map;
|
||||
map.insert(std::make_pair(0.0, 0.0065));
|
||||
map.insert(std::make_pair(11000.0, 0.0));
|
||||
map.insert(std::make_pair(20000.0, -0.001));
|
||||
map.insert(std::make_pair(32000.0, -0.0028));
|
||||
map.insert(std::make_pair(47000.0, 0.0));
|
||||
map.insert(std::make_pair(51000.0, 0.0028));
|
||||
map.insert(std::make_pair(71000.0, 0.002));
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
utils::Bin initDensities()
|
||||
{
|
||||
utils::Bin map;
|
||||
map.insert(std::make_pair(0.0, 1.225));
|
||||
map.insert(std::make_pair(11000.0, 0.36391));
|
||||
map.insert(std::make_pair(20000.0, 0.08803));
|
||||
map.insert(std::make_pair(32000.0, 0.01322));
|
||||
map.insert(std::make_pair(47000.0, 0.00143));
|
||||
map.insert(std::make_pair(51000.0, 0.00086));
|
||||
map.insert(std::make_pair(71000.0, 0.000064));
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
utils::Bin initPressures()
|
||||
{
|
||||
utils::Bin map;
|
||||
map.insert(std::make_pair(0.0, 101325));
|
||||
map.insert(std::make_pair(11000.0, 22632.1));
|
||||
map.insert(std::make_pair(20000.0, 5474.89));
|
||||
map.insert(std::make_pair(32000.0, 868.02));
|
||||
map.insert(std::make_pair(47000.0, 110.91));
|
||||
map.insert(std::make_pair(51000.0, 66.94));
|
||||
map.insert(std::make_pair(71000.0, 3.96));
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
utils::Bin USStandardAtmosphere::temperatureLapseRate(initLapseRates());
|
||||
utils::Bin USStandardAtmosphere::standardTemperature(initTemps());
|
||||
utils::Bin USStandardAtmosphere::standardDensity(initDensities());
|
||||
utils::Bin USStandardAtmosphere::standardPressure(initPressures());
|
||||
|
||||
USStandardAtmosphere::USStandardAtmosphere()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
USStandardAtmosphere::~USStandardAtmosphere()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
double USStandardAtmosphere::getDensity(double altitude)
|
||||
{
|
||||
if(utils::math::floatingPointEqual(temperatureLapseRate[altitude], 0.0))
|
||||
{
|
||||
return standardDensity[altitude] * std::exp(
|
||||
(-Constants::g0 * Constants::airMolarMass * (altitude - standardDensity.getBinBase(altitude)))
|
||||
/ (Constants::Rstar * standardTemperature[altitude]));
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
double base = (standardTemperature[altitude] - temperatureLapseRate[altitude] *
|
||||
(altitude - standardDensity.getBinBase(altitude))) / standardTemperature[altitude];
|
||||
|
||||
double exponent = (Constants::g0 * Constants::airMolarMass) /
|
||||
(Constants::Rstar * temperatureLapseRate[altitude]) - 1.0;
|
||||
|
||||
return standardDensity[altitude] * std::pow(base, exponent);
|
||||
}
|
||||
}
|
||||
|
||||
double USStandardAtmosphere::getTemperature(double altitude)
|
||||
{
|
||||
double baseTemp = standardTemperature[altitude];
|
||||
double baseAltitude = standardTemperature.getBinBase(altitude);
|
||||
return baseTemp - (altitude - baseAltitude) * temperatureLapseRate[altitude];
|
||||
|
||||
}
|
||||
double USStandardAtmosphere::getPressure(double altitude)
|
||||
{
|
||||
if(utils::math::floatingPointEqual(temperatureLapseRate[altitude], 0.0))
|
||||
{
|
||||
return standardPressure[altitude] * std::exp(
|
||||
(-Constants::g0 * Constants::airMolarMass * (altitude - standardPressure.getBinBase(altitude)))
|
||||
/ (Constants::Rstar * standardTemperature[altitude]));
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
double base = (standardTemperature[altitude] - temperatureLapseRate[altitude] *
|
||||
(altitude - standardPressure.getBinBase(altitude))) / standardTemperature[altitude];
|
||||
|
||||
double exponent = (Constants::g0 * Constants::airMolarMass) /
|
||||
(Constants::Rstar * temperatureLapseRate[altitude]);
|
||||
|
||||
return standardPressure[altitude] * std::pow(base, exponent);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
double USStandardAtmosphere::getSpeedOfSound(double altitude)
|
||||
{
|
||||
return std::sqrt( (Constants::gamma * Constants::Rstar * getTemperature(altitude))
|
||||
/
|
||||
Constants::airMolarMass);
|
||||
}
|
||||
|
||||
double USStandardAtmosphere::getDynamicViscosity(double altitude)
|
||||
{
|
||||
double temperature = getTemperature(altitude);
|
||||
return (Constants::beta * std::pow(temperature, 1.5)) / ( temperature + Constants::S);
|
||||
}
|
||||
} // namespace sim
|
45
sim/USStandardAtmosphere.h
Normal file
@ -0,0 +1,45 @@
|
||||
#ifndef SIM_USSTANDARDATMOSPHERE_H
|
||||
#define SIM_USSTANDARDATMOSPHERE_H
|
||||
|
||||
// qtrocket headers
|
||||
#include "sim/AtmosphericModel.h"
|
||||
#include "utils/Bin.h"
|
||||
|
||||
namespace sim
|
||||
{
|
||||
|
||||
class USStandardAtmosphere : public AtmosphericModel
|
||||
{
|
||||
public:
|
||||
USStandardAtmosphere();
|
||||
virtual ~USStandardAtmosphere();
|
||||
|
||||
/**
|
||||
* @brief Get the density of the air at a given altitude above mean sea level
|
||||
* This is overly simplistic and wrong implementation.
|
||||
*
|
||||
* @todo Fix this implementation. See the 1976 NOAA paper for the right way to
|
||||
* do it
|
||||
*
|
||||
* @param altitude the altitude above sea level
|
||||
* @return the density in kg/m^3
|
||||
*/
|
||||
double getDensity(double altitude) override;
|
||||
double getPressure(double altitude) override;
|
||||
double getTemperature(double altitude) override;
|
||||
|
||||
double getSpeedOfSound(double altitude) override;
|
||||
|
||||
double getDynamicViscosity(double altitude) override;
|
||||
|
||||
private:
|
||||
static utils::Bin temperatureLapseRate;
|
||||
static utils::Bin standardTemperature;
|
||||
static utils::Bin standardDensity;
|
||||
static utils::Bin standardPressure;
|
||||
|
||||
|
||||
};
|
||||
|
||||
} // namespace sim
|
||||
#endif // SIM_USSTANDARDATMOSPHERE_H
|
21
sim/WindModel.cpp
Normal file
@ -0,0 +1,21 @@
|
||||
|
||||
// qtrocket headers
|
||||
#include "WindModel.h"
|
||||
|
||||
namespace sim
|
||||
{
|
||||
|
||||
WindModel::WindModel()
|
||||
{
|
||||
}
|
||||
|
||||
WindModel::~WindModel()
|
||||
{
|
||||
}
|
||||
|
||||
Vector3 WindModel::getWindSpeed(double /* x */, double /* y */ , double /* z */)
|
||||
{
|
||||
return Vector3(0.0, 0.0, 0.0);
|
||||
}
|
||||
|
||||
} // namespace sim
|
22
sim/WindModel.h
Normal file
@ -0,0 +1,22 @@
|
||||
#ifndef SIM_WINDMODEL_H
|
||||
#define SIM_WINDMODEL_H
|
||||
|
||||
// qtrocket headers
|
||||
#include "utils/math/MathTypes.h"
|
||||
|
||||
namespace sim
|
||||
{
|
||||
|
||||
class WindModel
|
||||
{
|
||||
public:
|
||||
WindModel();
|
||||
virtual ~WindModel();
|
||||
|
||||
virtual Vector3 getWindSpeed(double x, double y, double z);
|
||||
|
||||
};
|
||||
|
||||
} // namespace sim
|
||||
|
||||
#endif // SIM_WINDMODEL_H
|
15
sim/tests/CMakeLists.txt
Normal file
@ -0,0 +1,15 @@
|
||||
|
||||
add_executable(sim_tests
|
||||
USStandardAtmosphereTests.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(sim_tests
|
||||
sim
|
||||
GTest::gtest_main
|
||||
)
|
||||
|
||||
include(GoogleTest)
|
||||
gtest_discover_tests(sim_tests)
|
||||
|
||||
add_test(NAME qtrocket_sim_tests COMMAND sim_tests)
|
||||
|
90
sim/tests/USStandardAtmosphereTests.cpp
Normal file
@ -0,0 +1,90 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "sim/USStandardAtmosphere.h"
|
||||
|
||||
TEST(USStandardAtmosphereTests, DensityTests)
|
||||
{
|
||||
sim::USStandardAtmosphere atmosphere;
|
||||
|
||||
// Test that the calucated values are with 0.1% of the published values in the NOAA report
|
||||
EXPECT_NEAR(atmosphere.getDensity(0.0) / 1.225, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getDensity(1000.0) / 1.112, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getDensity(2000.0) / 1.007, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getDensity(3000.0) / 0.9093, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getDensity(4000.0) / 0.8194, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getDensity(5000.0) / 0.7364, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getDensity(6000.0) / 0.6601, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getDensity(7000.0) / 0.5900, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getDensity(15000.0) / 0.19367, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getDensity(20000.0) / 0.088035, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getDensity(25000.0) / 0.039466, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getDensity(30000.0) / 0.018012, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getDensity(40000.0) / 0.0038510, 1.0, 0.001);
|
||||
|
||||
// These are generally accurate to ~0.5%. Slight deviation of calculated
|
||||
// density from the given density in the report table
|
||||
EXPECT_NEAR(atmosphere.getDensity(8000.0) / 0.52579, 1.0, 0.005);
|
||||
EXPECT_NEAR(atmosphere.getDensity(9000.0) / 0.46706, 1.0, 0.005);
|
||||
EXPECT_NEAR(atmosphere.getDensity(10000.0) / 0.41351, 1.0, 0.005);
|
||||
EXPECT_NEAR(atmosphere.getDensity(50000.0) / 0.00097752, 1.0, 0.005);
|
||||
EXPECT_NEAR(atmosphere.getDensity(60000.0) / 0.00028832, 1.0, 0.005);
|
||||
EXPECT_NEAR(atmosphere.getDensity(70000.0) / 0.000074243, 1.0, 0.005);
|
||||
EXPECT_NEAR(atmosphere.getDensity(80000.0) / 0.000015701, 1.0, 0.005);
|
||||
|
||||
}
|
||||
|
||||
TEST(USStandardAtmosphereTests, PressureTests)
|
||||
{
|
||||
sim::USStandardAtmosphere atmosphere;
|
||||
|
||||
// Test that the calucated values are with 0.1% of the published values in the NOAA report
|
||||
EXPECT_NEAR(atmosphere.getPressure(0.0) / 101325.0, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getPressure(1000.0) / 89876.0, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getPressure(2000.0) / 79501.0, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getPressure(3000.0) / 70108.0, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getPressure(4000.0) / 61640.0, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getPressure(5000.0) / 54019.0, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getPressure(6000.0) / 47181.0, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getPressure(7000.0) / 41060.0, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getPressure(8000.0) / 35599.0, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getPressure(9000.0) / 30742.0, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getPressure(10000.0) / 26436.0, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getPressure(15000.0) / 12044.0, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getPressure(20000.0) / 5474.8, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getPressure(25000.0) / 2511.0, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getPressure(30000.0) / 1171.8, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getPressure(40000.0) / 277.52, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getPressure(50000.0) / 75.944, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getPressure(60000.0) / 20.314, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getPressure(70000.0) / 4.6342, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getPressure(80000.0) / 0.88627, 1.0, 0.001);
|
||||
|
||||
}
|
||||
|
||||
TEST(USStandardAtmosphereTests, TemperatureTests)
|
||||
{
|
||||
sim::USStandardAtmosphere atmosphere;
|
||||
|
||||
// Test that the calucated values are with 0.1% of the published values in the NOAA report
|
||||
EXPECT_NEAR(atmosphere.getTemperature(0.0) / 288.15, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getTemperature(1000.0) / 281.651, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getTemperature(2000.0) / 275.154, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getTemperature(3000.0) / 268.659, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getTemperature(4000.0) / 262.166, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getTemperature(5000.0) / 255.676, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getTemperature(6000.0) / 249.187, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getTemperature(7000.0) / 242.7, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getTemperature(8000.0) / 236.215, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getTemperature(9000.0) / 229.733, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getTemperature(10000.0) / 223.252, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getTemperature(15000.0) / 216.65, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getTemperature(20000.0) / 216.65, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getTemperature(25000.0) / 221.552, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getTemperature(30000.0) / 226.509, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getTemperature(40000.0) / 251.05, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getTemperature(50000.0) / 270.65, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getTemperature(60000.0) / 245.45, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getTemperature(70000.0) / 217.450, 1.0, 0.001);
|
||||
EXPECT_NEAR(atmosphere.getTemperature(80000.0) / 196.650, 1.0, 0.001);
|
||||
|
||||
}
|
103
utils/Bin.cpp
Normal file
@ -0,0 +1,103 @@
|
||||
|
||||
/// \cond
|
||||
// C headers
|
||||
// C++ headers
|
||||
#include <algorithm>
|
||||
#include <format>
|
||||
#include <stdexcept>
|
||||
|
||||
// 3rd party headers
|
||||
/// \endcond
|
||||
|
||||
// qtrocket headers
|
||||
#include "Bin.h"
|
||||
|
||||
// TODO: Check on the availability of this in Clang.
|
||||
// Replace libfmt with format when LLVM libc++ supports it
|
||||
//#include <format>
|
||||
|
||||
namespace utils
|
||||
{
|
||||
|
||||
Bin::Bin()
|
||||
: bins()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Bin::Bin(Bin&& o)
|
||||
: bins(std::move(o.bins))
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Bin::~Bin()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// TODO: Very low priority, but if anyone wants to make this more efficient it could be
|
||||
// interesting
|
||||
void Bin::insert(const std::pair<double, double>& toInsert)
|
||||
{
|
||||
bins.push_back(toInsert);
|
||||
std::sort(bins.begin(), bins.end(),
|
||||
[](const auto& a, const auto& b){ return a.first < b.first; });
|
||||
}
|
||||
|
||||
double Bin::operator[](double key)
|
||||
{
|
||||
auto iter = bins.begin();
|
||||
// If the key is less than the lowest bin value, then it is out of range
|
||||
// This should be an error. It's also possible to interpret this as simply
|
||||
// the lowest bin, but I think that invites a logic error so we'll throw
|
||||
// instead
|
||||
if(key < iter->first)
|
||||
{
|
||||
throw std::out_of_range(
|
||||
std::format("{} less than lower bound {} of BinMap", key, iter->first));
|
||||
}
|
||||
// Increment it and start searching If we reach the end without finding an existing key
|
||||
// greater than our search term, then we've just hit the last bin and return that
|
||||
iter++;
|
||||
double retVal = bins.back().second;
|
||||
while(iter != bins.end())
|
||||
{
|
||||
if(key < iter->first)
|
||||
{
|
||||
retVal = std::prev(iter)->second;
|
||||
break;
|
||||
}
|
||||
iter++;
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
double Bin::getBinBase(double key)
|
||||
{
|
||||
auto iter = bins.begin();
|
||||
// If the key is less than the lowest bin value, then it is out of range
|
||||
// This should be an error. It's also possible to interpret this as simply
|
||||
// the lowest bin, but I think that invites a logic error so we'll throw
|
||||
// instead
|
||||
if(key < iter->first)
|
||||
{
|
||||
throw std::out_of_range(
|
||||
std::format("{} less than lower bound {} of BinMap", key, iter->first));
|
||||
}
|
||||
// Increment it and start searching If we reach the end without finding an existing key
|
||||
// greater than our search term, then we've just hit the last bin and return that
|
||||
iter++;
|
||||
double retVal = bins.back().first;
|
||||
while(iter != bins.end())
|
||||
{
|
||||
if(key < iter->first)
|
||||
{
|
||||
retVal = std::prev(iter)->first;
|
||||
break;
|
||||
}
|
||||
iter++;
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
} // namespace utils
|
43
utils/Bin.h
Normal file
@ -0,0 +1,43 @@
|
||||
#ifndef UTILS_BIN_H
|
||||
#define UTILS_BIN_H
|
||||
|
||||
/// \cond
|
||||
// C headers
|
||||
// C++ headers
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
// 3rd party headers
|
||||
/// \endcond
|
||||
|
||||
namespace utils {
|
||||
|
||||
/**
|
||||
* @brief This is a utility class that operates as a map. Instead of a regular map
|
||||
* this one maps a range of key values to a single value. Like a bin, where
|
||||
* each key represents the bottom of the bin, and the next key represents the
|
||||
* bottom of the next bin, etc. When dereferencing the BinMap, it checks for
|
||||
* where the passed in key falls and returns the value in that bin.
|
||||
*
|
||||
* @todo Make this class behave more like a proper STL container. Templetize it for one
|
||||
*
|
||||
*/
|
||||
class Bin
|
||||
{
|
||||
public:
|
||||
Bin();
|
||||
Bin(Bin&& o);
|
||||
~Bin();
|
||||
|
||||
void insert(const std::pair<double, double>& toInsert);
|
||||
double operator[](double key);
|
||||
double getBinBase(double key);
|
||||
|
||||
private:
|
||||
std::vector<std::pair<double, double>> bins;
|
||||
|
||||
};
|
||||
|
||||
} // namespace utils
|
||||
|
||||
#endif // UTILS_BIN_H
|
30
utils/CMakeLists.txt
Normal file
@ -0,0 +1,30 @@
|
||||
add_library(utils
|
||||
Bin.cpp
|
||||
Bin.h
|
||||
CurlConnection.cpp
|
||||
CurlConnection.h
|
||||
Logger.cpp
|
||||
Logger.h
|
||||
MotorModelDatabase.cpp
|
||||
MotorModelDatabase.h
|
||||
RSEDatabaseLoader.cpp
|
||||
RSEDatabaseLoader.h
|
||||
ThreadPool.cpp
|
||||
ThreadPool.h
|
||||
ThrustCurveAPI.cpp
|
||||
ThrustCurveAPI.h
|
||||
TSQueue.h
|
||||
math/Constants.h
|
||||
math/MathTypes.h
|
||||
math/UtilityMathFunctions.h)
|
||||
|
||||
|
||||
#target_include_directories(utils PRIVATE
|
||||
# ${Boost_INCLUDE_DIRS})
|
||||
|
||||
|
||||
target_link_libraries(utils PUBLIC
|
||||
libcurl
|
||||
Boost::property_tree
|
||||
jsoncpp_static
|
||||
eigen)
|
75
utils/CurlConnection.cpp
Normal file
@ -0,0 +1,75 @@
|
||||
|
||||
/// \cond
|
||||
// C headers
|
||||
// C++ headers
|
||||
#include <iostream>
|
||||
|
||||
// 3rd party headers
|
||||
/// \endcond
|
||||
|
||||
// qtrocket headers
|
||||
#include "utils/CurlConnection.h"
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
size_t curlCallback(void* content, size_t size, size_t nmemb, std::string* buffer)
|
||||
{
|
||||
buffer->append(static_cast<char*>(content), size*nmemb);
|
||||
return size*nmemb;
|
||||
}
|
||||
}
|
||||
|
||||
namespace utils
|
||||
{
|
||||
|
||||
CurlConnection::CurlConnection()
|
||||
{
|
||||
curl_global_init(CURL_GLOBAL_ALL);
|
||||
curl = curl_easy_init();
|
||||
}
|
||||
|
||||
std::string CurlConnection::get(const std::string& url,
|
||||
const std::vector<std::string>& extraHttpHeaders)
|
||||
{
|
||||
std::string str_result;
|
||||
std::string post_data = "";
|
||||
std::string action = "GET";
|
||||
if(curl)
|
||||
{
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlCallback);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &str_result);
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_easy_setopt(curl, CURLOPT_ENCODING, "gzip");
|
||||
|
||||
if(!extraHttpHeaders.empty())
|
||||
{
|
||||
struct curl_slist *chunk = NULL;
|
||||
for(size_t i = 0; i < extraHttpHeaders.size(); ++i)
|
||||
{
|
||||
chunk = curl_slist_append(chunk, extraHttpHeaders[i].c_str());
|
||||
}
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk);
|
||||
}
|
||||
|
||||
if(post_data.size() > 0 || action == "POST" || action == "PUT" || action == "DELETE")
|
||||
{
|
||||
if(action == "PUT" || action == "DELETE")
|
||||
{
|
||||
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, action.c_str());
|
||||
}
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_data.c_str());
|
||||
}
|
||||
res = curl_easy_perform(curl);
|
||||
|
||||
if(res != CURLE_OK)
|
||||
{
|
||||
std::cout << "There was an error: " << curl_easy_strerror(res) << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return str_result;
|
||||
}
|
||||
|
||||
} // namespace utils
|
33
utils/CurlConnection.h
Normal file
@ -0,0 +1,33 @@
|
||||
#ifndef UTILS_CURLCONNECTION_H
|
||||
#define UTILS_CURLCONNECTION_H
|
||||
|
||||
/// \cond
|
||||
// C headers
|
||||
#include <curl/curl.h>
|
||||
|
||||
// C++ headers
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// 3rd party headers
|
||||
/// \endcond
|
||||
|
||||
namespace utils {
|
||||
|
||||
class CurlConnection
|
||||
{
|
||||
public:
|
||||
CurlConnection();
|
||||
|
||||
std::string get(const std::string& url, const std::vector<std::string>& extraHttpHeaders);
|
||||
|
||||
|
||||
private:
|
||||
CURL* curl;
|
||||
CURLcode res;
|
||||
|
||||
};
|
||||
|
||||
} // namespace utils
|
||||
|
||||
#endif // UTILS_CURLCONNECTION_H
|
@ -10,57 +10,59 @@
|
||||
// qtrocket headers
|
||||
#include "Logger.h"
|
||||
|
||||
namespace utils
|
||||
{
|
||||
Logger* Logger::instance = nullptr;
|
||||
|
||||
Logger* Logger::getInstance()
|
||||
{
|
||||
if(!instance)
|
||||
{
|
||||
instance = new Logger();
|
||||
}
|
||||
return instance;
|
||||
if(!instance)
|
||||
{
|
||||
instance = new Logger();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
Logger::Logger()
|
||||
{
|
||||
outFile.open("log.txt");
|
||||
outFile.open("log.txt");
|
||||
}
|
||||
|
||||
Logger::~Logger()
|
||||
{
|
||||
outFile.close();
|
||||
outFile.close();
|
||||
}
|
||||
|
||||
void Logger::log(std::string_view msg, const LogLevel& lvl)
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(mtx);
|
||||
// The fallthrough is intentional. Logging is automatically enabled for
|
||||
// all levels at or lower than the current level.
|
||||
switch(currentLevel)
|
||||
{
|
||||
case LogLevel::PERF:
|
||||
if(lvl == LogLevel::PERF)
|
||||
std::lock_guard<std::mutex> lck(mtx);
|
||||
// The fallthrough is intentional. Logging is automatically enabled for
|
||||
// all levels at or lower than the current level.
|
||||
switch(currentLevel)
|
||||
{
|
||||
case PERF_:
|
||||
if(lvl == PERF_)
|
||||
{
|
||||
outFile << "[PERF] " << msg << std::endl;
|
||||
std::cout << "[PERF] " << msg << "\n";
|
||||
std::cout << "[PERF] " << msg << "\n";
|
||||
}
|
||||
[[fallthrough]];
|
||||
case LogLevel::DEBUG:
|
||||
if(lvl == LogLevel::DEBUG)
|
||||
case DEBUG_:
|
||||
if(lvl == DEBUG_)
|
||||
{
|
||||
outFile << "[DEBUG] " << msg << std::endl;
|
||||
std::cout << "[DEBUG] " << msg << "\n";
|
||||
}
|
||||
[[fallthrough]];
|
||||
case LogLevel::INFO:
|
||||
if(lvl == LogLevel::INFO)
|
||||
case INFO_:
|
||||
if(lvl == INFO_)
|
||||
{
|
||||
outFile << "[INFO] " << msg << std::endl;
|
||||
std::cout << "[INFO] " << msg << "\n";
|
||||
}
|
||||
[[fallthrough]];
|
||||
case LogLevel::WARN:
|
||||
if(lvl == LogLevel::WARN)
|
||||
case WARN_:
|
||||
if(lvl == WARN_)
|
||||
{
|
||||
outFile << "[WARN] " << msg << std::endl;
|
||||
std::cout << "[WARN] " << msg << "\n";
|
||||
@ -69,7 +71,7 @@ void Logger::log(std::string_view msg, const LogLevel& lvl)
|
||||
// Regardless of what level is set, ERROR is always logged, so
|
||||
// rather than explicitly check for the ERROR case, we just use default case
|
||||
default:
|
||||
if(lvl == LogLevel::ERROR)
|
||||
if(lvl == ERROR_)
|
||||
{
|
||||
outFile << "[ERROR] " << msg << std::endl;
|
||||
std::cout << "[ERROR] " << msg << "\n";
|
||||
@ -88,3 +90,4 @@ void Logger::log(std::ostream& o, const std::string& msg)
|
||||
o << msg << std::endl;
|
||||
}
|
||||
|
||||
} // namespace utils
|
@ -11,19 +11,22 @@
|
||||
// 3rd party headers
|
||||
/// \endcond
|
||||
|
||||
namespace utils
|
||||
{
|
||||
|
||||
/**
|
||||
* @todo write docs
|
||||
*/
|
||||
class Logger
|
||||
{
|
||||
public:
|
||||
enum class LogLevel
|
||||
enum LogLevel
|
||||
{
|
||||
ERROR,
|
||||
WARN,
|
||||
INFO,
|
||||
DEBUG,
|
||||
PERF
|
||||
ERROR_,
|
||||
WARN_,
|
||||
INFO_,
|
||||
DEBUG_,
|
||||
PERF_
|
||||
};
|
||||
|
||||
static Logger* getInstance();
|
||||
@ -36,11 +39,11 @@ public:
|
||||
|
||||
void setLogLevel(const LogLevel& lvl);
|
||||
|
||||
inline void error(std::string_view msg) { log(msg, LogLevel::ERROR); }
|
||||
inline void warn(std::string_view msg) { log(msg, LogLevel::WARN); }
|
||||
inline void info(std::string_view msg) { log(msg, LogLevel::INFO); }
|
||||
inline void debug(std::string_view msg) { log(msg, LogLevel::DEBUG); }
|
||||
inline void perf(std::string_view msg) { log(msg, LogLevel::PERF); }
|
||||
inline void error(std::string_view msg) { log(msg, ERROR_); }
|
||||
inline void warn(std::string_view msg) { log(msg, WARN_); }
|
||||
inline void info(std::string_view msg) { log(msg, INFO_); }
|
||||
inline void debug(std::string_view msg) { log(msg, DEBUG_); }
|
||||
inline void perf(std::string_view msg) { log(msg, PERF_); }
|
||||
|
||||
void log(std::ostream& o, const std::string& msg);
|
||||
|
||||
@ -55,4 +58,5 @@ private:
|
||||
Logger();
|
||||
};
|
||||
|
||||
} // namespace utils
|
||||
#endif // UTILS_LOGGER_H
|
134
utils/MotorModelDatabase.cpp
Normal file
@ -0,0 +1,134 @@
|
||||
// class header
|
||||
#include "utils/MotorModelDatabase.h"
|
||||
|
||||
/// \cond
|
||||
// C headers
|
||||
// C++ headers
|
||||
// 3rd party headers
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/xml_parser.hpp>
|
||||
/// \endcond
|
||||
|
||||
// qtrocket project headers
|
||||
#include "QtRocket.h"
|
||||
|
||||
namespace utils
|
||||
{
|
||||
|
||||
MotorModelDatabase::MotorModelDatabase()
|
||||
: motorModelMap()
|
||||
{
|
||||
}
|
||||
|
||||
MotorModelDatabase::~MotorModelDatabase()
|
||||
{
|
||||
}
|
||||
|
||||
void MotorModelDatabase::addMotorModel(const model::MotorModel& m)
|
||||
{
|
||||
utils::Logger* logger = QtRocket::getInstance()->getLogger();
|
||||
std::string name = m.data.commonName;
|
||||
if(motorModelMap.find(name) != motorModelMap.end())
|
||||
{
|
||||
logger->debug("Replacing MotorModel " + name + " in MotorModelDatabase");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger->info("Adding MotorModel " + name + " to MotorModelDatabase");
|
||||
}
|
||||
motorModelMap[name] = m;
|
||||
}
|
||||
|
||||
void MotorModelDatabase::addMotorModels(const std::vector<model::MotorModel>& models)
|
||||
{
|
||||
utils::Logger* logger = QtRocket::getInstance()->getLogger();
|
||||
for(const auto& i : models)
|
||||
{
|
||||
addMotorModel(i);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<model::MotorModel> MotorModelDatabase::getMotorModel(const std::string& name)
|
||||
{
|
||||
auto mm = motorModelMap.find(name);
|
||||
if(mm == motorModelMap.end())
|
||||
{
|
||||
Logger::getInstance()->debug("Unable to locate " + name + " in MotorModel database");
|
||||
|
||||
return std::nullopt;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::getInstance()->debug("Retrieved " + name + " from MotorModel database");
|
||||
return motorModelMap[name];
|
||||
}
|
||||
}
|
||||
|
||||
void MotorModelDatabase::saveMotorDatabase(const std::string& filename)
|
||||
{
|
||||
|
||||
namespace pt = boost::property_tree;
|
||||
|
||||
// top-level tree
|
||||
pt::ptree tree;
|
||||
tree.put("QtRocketMotorDatabase.<xmlattr>.version", "0.1");
|
||||
for(const auto& i : motorModelMap)
|
||||
{
|
||||
pt::ptree motor;
|
||||
const auto& m = i.second;
|
||||
motor.put("<xmlattr>.name", m.data.commonName);
|
||||
motor.put("availability", m.data.availability.str());
|
||||
motor.put("avgThrust", m.data.avgThrust);
|
||||
motor.put("burnTime", m.data.burnTime);
|
||||
motor.put("certOrg", m.data.certOrg.str());
|
||||
motor.put("commonName", m.data.commonName);
|
||||
motor.put("designation", m.data.designation);
|
||||
motor.put("diameter", m.data.diameter);
|
||||
motor.put("impulseClass", m.data.impulseClass);
|
||||
motor.put("infoUrl", m.data.infoUrl);
|
||||
motor.put("length", m.data.length);
|
||||
motor.put("manufacturer", m.data.manufacturer.str());
|
||||
motor.put("maxThrust", m.data.maxThrust);
|
||||
motor.put("motorIdTC", m.data.motorIdTC);
|
||||
motor.put("propType", m.data.propType);
|
||||
motor.put("sparky", m.data.sparky ? "true" : "false");
|
||||
motor.put("totalImpulse", m.data.totalImpulse);
|
||||
motor.put("totalWeight", m.data.totalWeight);
|
||||
motor.put("type", m.data.type.str());
|
||||
motor.put("lastUpdated", m.data.lastUpdated);
|
||||
|
||||
// delays tag is in the form of a csv string
|
||||
std::stringstream delays;
|
||||
for (std::size_t i = 0; i < m.data.delays.size() - 1; ++i)
|
||||
{
|
||||
delays << std::to_string(m.data.delays[i]) << ",";
|
||||
}
|
||||
delays << std::to_string(m.data.delays[m.data.delays.size() - 1]);
|
||||
motor.put("delays", delays.str());
|
||||
|
||||
// thrust data
|
||||
{
|
||||
pt::ptree tc;
|
||||
std::vector<std::pair<double, double>> thrust = m.getThrustCurve().getThrustCurveData();
|
||||
for(const auto& j : thrust)
|
||||
{
|
||||
pt::ptree thrustNode;
|
||||
thrustNode.put("<xmlattr>.time", j.first);
|
||||
thrustNode.put("<xmlattr>.force", j.second);
|
||||
tc.add_child("thrust", thrustNode);
|
||||
}
|
||||
motor.add_child("thrustCurve", tc);
|
||||
}
|
||||
tree.add_child("QtRocketMotorDatabase.MotorModels.motor", motor);
|
||||
}
|
||||
pt::xml_writer_settings<std::string> settings(' ', 2);
|
||||
pt::write_xml(filename, tree, std::locale(), settings);
|
||||
}
|
||||
|
||||
void MotorModelDatabase::loadMotorDatabase(const std::string& filename)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
} // namespace utils
|
77
utils/MotorModelDatabase.h
Normal file
@ -0,0 +1,77 @@
|
||||
#ifndef UTILS_MOTORMODELDATABASE_H
|
||||
#define UTILS_MOTORMODELDATABASE_H
|
||||
|
||||
/// \cond
|
||||
// C headers
|
||||
// C++ headers
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
// 3rd party headers
|
||||
/// \endcond
|
||||
|
||||
// qtrocket headers
|
||||
#include "model/MotorModel.h"
|
||||
|
||||
|
||||
namespace utils
|
||||
{
|
||||
/**
|
||||
* @brief MotorModelDatabase is a simple storage, search, and retrieval mechanism for Model Rocket
|
||||
* motors.
|
||||
*
|
||||
*/
|
||||
class MotorModelDatabase
|
||||
{
|
||||
public:
|
||||
MotorModelDatabase();
|
||||
~MotorModelDatabase();
|
||||
|
||||
// No copies
|
||||
MotorModelDatabase(const MotorModelDatabase&) = delete;
|
||||
MotorModelDatabase(MotorModelDatabase&&) = delete;
|
||||
MotorModelDatabase& operator=(const MotorModelDatabase&) = delete;
|
||||
MotorModelDatabase& operator=(MotorModelDatabase&&) = delete;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Adds a single MotorModel to the database. If that MotorModel already exists, it is
|
||||
* replaced.
|
||||
*
|
||||
* @param model MotorModel to add
|
||||
*
|
||||
*/
|
||||
void addMotorModel(const model::MotorModel& m);
|
||||
|
||||
/**
|
||||
* @brief Adds multiple motor models at once. Any duplicates already in the datbase are replaced.
|
||||
*
|
||||
* @param model MotorModels to add
|
||||
*
|
||||
*/
|
||||
void addMotorModels(const std::vector<model::MotorModel>& models);
|
||||
|
||||
/**
|
||||
* @brief Get the Motor Model by Common Name
|
||||
*
|
||||
* @param name Motor Common name
|
||||
* @return std::optional<model::MotorModel>
|
||||
*/
|
||||
std::optional<model::MotorModel> getMotorModel(const std::string& name);
|
||||
|
||||
void saveMotorDatabase(const std::string& filename);
|
||||
void loadMotorDatabase(const std::string& filename);
|
||||
private:
|
||||
|
||||
// The "database" is really just a map. :)
|
||||
/// motorModelMap is keyed off of the motor commonName
|
||||
std::map<std::string, model::MotorModel> motorModelMap;
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
} // namespace utils
|
||||
|
||||
#endif // UTILS_MOTORMODELDATABASE_H
|
108
utils/RSEDatabaseLoader.cpp
Normal file
@ -0,0 +1,108 @@
|
||||
|
||||
/// \cond
|
||||
// C headers
|
||||
// C++ headers
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
|
||||
// 3rd party headers
|
||||
#include <boost/property_tree/xml_parser.hpp>
|
||||
/// \endcond
|
||||
|
||||
// qtrocket headers
|
||||
#include "utils/RSEDatabaseLoader.h"
|
||||
#include "QtRocket.h"
|
||||
#include "Logger.h"
|
||||
|
||||
namespace utils {
|
||||
|
||||
RSEDatabaseLoader::RSEDatabaseLoader(const std::string& filename)
|
||||
: motors(),
|
||||
tree()
|
||||
{
|
||||
boost::property_tree::read_xml(filename, tree);
|
||||
|
||||
// Get the first engine
|
||||
for(boost::property_tree::ptree::value_type& v : tree.get_child("engine-database.engine-list"))
|
||||
{
|
||||
buildAndAppendMotorModel(v.second);
|
||||
}
|
||||
|
||||
QtRocket::getInstance()->addMotorModels(motors);
|
||||
}
|
||||
|
||||
RSEDatabaseLoader::~RSEDatabaseLoader()
|
||||
{}
|
||||
|
||||
model::MotorModel RSEDatabaseLoader::getMotorModelByName(const std::string &name)
|
||||
{
|
||||
|
||||
auto mm = std::find_if(motors.begin(), motors.end(),
|
||||
[&name](const auto& i) { return name == i.data.commonName; });
|
||||
if(mm == motors.end())
|
||||
{
|
||||
Logger::getInstance()->error("Unable to locate " + name + " in RSE database");
|
||||
return model::MotorModel();
|
||||
}
|
||||
return *mm;
|
||||
}
|
||||
|
||||
void RSEDatabaseLoader::buildAndAppendMotorModel(boost::property_tree::ptree& v)
|
||||
{
|
||||
model::MotorModel::MetaData mm;
|
||||
mm.availability = model::MotorModel::MotorAvailability(model::MotorModel::AVAILABILITY::REGULAR);
|
||||
mm.avgThrust = v.get<double>("<xmlattr>.avgThrust", 0.0);
|
||||
mm.burnTime = v.get<double>("<xmlattr>.burn-time", 0.0);
|
||||
mm.certOrg = model::MotorModel::CertOrg(model::MotorModel::CERTORG::UNK);
|
||||
mm.commonName = v.get<std::string>("<xmlattr>.code", "");
|
||||
|
||||
// mm.delays = extract vector from csv list
|
||||
std::string delays = v.get<std::string>("<xmlattr>.delays", "1000");
|
||||
std::size_t pos{0};
|
||||
std::string tok;
|
||||
while ((pos = delays.find(",")) != std::string::npos)
|
||||
{
|
||||
tok = delays.substr(0, pos);
|
||||
mm.delays.push_back(std::atoi(tok.c_str()));
|
||||
delays.erase(0, pos + 1);
|
||||
}
|
||||
mm.delays.push_back(std::atoi(delays.c_str()));
|
||||
|
||||
// mm.designation = What is this?
|
||||
|
||||
mm.diameter = v.get<double>("<xmlattr>.dia", 0.0);
|
||||
// impulse class is the motor letter designation. extract from the first character
|
||||
// of the commonName since it isn't given explicity in the RSE file
|
||||
mm.impulseClass = mm.commonName[0];
|
||||
|
||||
// infoUrl not present in RSE file
|
||||
mm.infoUrl = "";
|
||||
mm.length = v.get<double>("<xmlattr>.len", 0.0);
|
||||
mm.manufacturer = model::MotorModel::MotorManufacturer::toEnum(v.get<std::string>("<xmlattr>.mfg", ""));
|
||||
mm.maxThrust = v.get<double>("<xmlattr>.peakThrust", 0.0);
|
||||
mm.totalWeight = v.get<double>("<xmlattr>.initWt", 0.0) / 1000.0; // convert g -> kg
|
||||
mm.propWeight = v.get<double>("<xmlattr>.propWt", 0.0) / 1000.0; // convert g -> kg
|
||||
mm.totalImpulse = v.get<double>("<xmlattr>.Itot", 0.0);
|
||||
|
||||
mm.type = model::MotorModel::MotorType::toEnum(v.get<std::string>("<xmlattr>.Type"));
|
||||
|
||||
// Now get the thrust data
|
||||
std::vector<std::pair<double, double>> thrustData;
|
||||
for(boost::property_tree::ptree::value_type& w : v.get_child("data"))
|
||||
{
|
||||
double tdata = w.second.get<double>("<xmlattr>.t");
|
||||
double fdata = w.second.get<double>("<xmlattr>.f");
|
||||
thrustData.push_back(std::make_pair(tdata, fdata));
|
||||
}
|
||||
|
||||
ThrustCurve tc(thrustData);
|
||||
model::MotorModel motorModel;
|
||||
motorModel.addThrustCurve(tc);
|
||||
motorModel.moveMetaData(std::move(mm));
|
||||
motors.emplace_back(std::move(motorModel));
|
||||
}
|
||||
|
||||
|
||||
} // namespace utils
|
40
utils/RSEDatabaseLoader.h
Normal file
@ -0,0 +1,40 @@
|
||||
#ifndef UTILS_RSEDATABASELOADER_H
|
||||
#define UTILS_RSEDATABASELOADER_H
|
||||
|
||||
|
||||
/// \cond
|
||||
// C headers
|
||||
// C++ headers
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// 3rd party headers
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
/// \endcond
|
||||
|
||||
// qtrocket headers
|
||||
#include "model/MotorModel.h"
|
||||
|
||||
namespace utils {
|
||||
|
||||
class RSEDatabaseLoader
|
||||
{
|
||||
public:
|
||||
RSEDatabaseLoader(const std::string& filename);
|
||||
~RSEDatabaseLoader();
|
||||
|
||||
std::vector<model::MotorModel>& getMotors() { return motors; }
|
||||
|
||||
model::MotorModel getMotorModelByName(const std::string& name);
|
||||
private:
|
||||
|
||||
std::vector<model::MotorModel> motors;
|
||||
|
||||
void buildAndAppendMotorModel(boost::property_tree::ptree& v);
|
||||
|
||||
boost::property_tree::ptree tree;
|
||||
};
|
||||
|
||||
} // namespace utils
|
||||
|
||||
#endif // UTILS_RSEDATABASELOADER_H
|
89
utils/TSQueue.h
Normal file
@ -0,0 +1,89 @@
|
||||
#ifndef TSQUEUE_H
|
||||
#define TSQUEUE_H
|
||||
|
||||
/// \cond
|
||||
// C headers
|
||||
// C++ headers
|
||||
#include <condition_variable>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
|
||||
// 3rd party headers
|
||||
/// \endcond
|
||||
|
||||
/**
|
||||
* @brief The TSQueue class is a very basic thread-safe queue
|
||||
*/
|
||||
template<typename T>
|
||||
class TSQueue
|
||||
{
|
||||
public:
|
||||
TSQueue()
|
||||
: mtx(),
|
||||
q(),
|
||||
cv()
|
||||
{}
|
||||
|
||||
void push(T newVal)
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(mtx);
|
||||
q.push(newVal);
|
||||
cv.notify_one();
|
||||
}
|
||||
|
||||
void waitAndPop(T& val)
|
||||
{
|
||||
std::unique_lock<std::mutex> lck(mtx);
|
||||
cv.wait(lck, [this]{return !q.empty(); });
|
||||
val = std::move(q.front());
|
||||
q.pop();
|
||||
}
|
||||
|
||||
std::shared_ptr<T> waitAndPop()
|
||||
{
|
||||
std::unique_lock<std::mutex> lck(mtx);
|
||||
cv.wait(lck, [this] { return !q.empty(); });
|
||||
std::shared_ptr<T> res(std::make_shared<T>(std::move(q.front())));
|
||||
q.pop();
|
||||
return res;
|
||||
}
|
||||
|
||||
bool tryPop(T& val)
|
||||
{
|
||||
std::unique_lock<std::mutex> lck(mtx);
|
||||
if(q.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
val = std::move(q.front());
|
||||
q.pop();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<T> tryPop()
|
||||
{
|
||||
std::unique_lock<std::mutex> lck(mtx);
|
||||
if(q.empty())
|
||||
{
|
||||
return std::shared_ptr<T>(); // nullptr
|
||||
}
|
||||
std::shared_ptr<T> retVal(std::move(q.front()));
|
||||
q.pop();
|
||||
return retVal;
|
||||
}
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(mtx);
|
||||
return q.empty();
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
mutable std::mutex mtx;
|
||||
std::queue<T> q;
|
||||
std::condition_variable cv;
|
||||
};
|
||||
|
||||
#endif // TSQUEUE_H
|
54
utils/ThreadPool.cpp
Normal file
@ -0,0 +1,54 @@
|
||||
|
||||
/// \cond
|
||||
// C headers
|
||||
// C++ headers
|
||||
#include <cstdint>
|
||||
|
||||
// 3rd party headers
|
||||
/// \endcond
|
||||
|
||||
// qtrocket headers
|
||||
#include "ThreadPool.h"
|
||||
|
||||
|
||||
ThreadPool::ThreadPool()
|
||||
: done(false),
|
||||
q(),
|
||||
threads(),
|
||||
joiner(threads)
|
||||
{
|
||||
const std::size_t threadCount = std::thread::hardware_concurrency();
|
||||
|
||||
try
|
||||
{
|
||||
for(size_t i = 0; i < threadCount; ++i)
|
||||
{
|
||||
threads.push_back(std::thread(&ThreadPool::worker, this));
|
||||
}
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
done = true;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
ThreadPool::~ThreadPool()
|
||||
{
|
||||
done = true;
|
||||
}
|
||||
void ThreadPool::worker()
|
||||
{
|
||||
while(!done)
|
||||
{
|
||||
std::function<void()> task;
|
||||
if(q.tryPop(task))
|
||||
{
|
||||
task();
|
||||
}
|
||||
else
|
||||
{
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
}
|
68
utils/ThreadPool.h
Normal file
@ -0,0 +1,68 @@
|
||||
#ifndef THREADPOOL_H
|
||||
#define THREADPOOL_H
|
||||
|
||||
/// \cond
|
||||
// C headers
|
||||
// C++ headers
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
// 3rd party headers
|
||||
/// \endcond
|
||||
|
||||
// qtrocket headers
|
||||
#include "utils/TSQueue.h"
|
||||
|
||||
|
||||
/**
|
||||
* @brief A basic ThreadPool class
|
||||
*/
|
||||
class ThreadPool
|
||||
{
|
||||
public:
|
||||
ThreadPool();
|
||||
~ThreadPool();
|
||||
|
||||
template<typename FunctionType>
|
||||
void submit(FunctionType f)
|
||||
{
|
||||
q.push(std::function<void()>(f));
|
||||
}
|
||||
|
||||
private:
|
||||
class JoinThreads
|
||||
{
|
||||
public:
|
||||
explicit JoinThreads(std::vector<std::thread>& inThreads)
|
||||
: threads(inThreads)
|
||||
{}
|
||||
|
||||
~JoinThreads()
|
||||
{
|
||||
for(auto& i : threads)
|
||||
{
|
||||
if(i.joinable())
|
||||
{
|
||||
i.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
std::vector<std::thread>& threads;
|
||||
};
|
||||
|
||||
std::atomic_bool done;
|
||||
TSQueue<std::function<void()>> q;
|
||||
std::vector<std::thread> threads;
|
||||
|
||||
JoinThreads joiner;
|
||||
|
||||
void worker();
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif // THREADPOOL_H
|
315
utils/ThrustCurveAPI.cpp
Normal file
@ -0,0 +1,315 @@
|
||||
|
||||
/// \cond
|
||||
// C headers
|
||||
// C++ headers
|
||||
// 3rd party headers
|
||||
#include <json/json.h>
|
||||
#include <optional>
|
||||
/// \endcond
|
||||
|
||||
// qtrocket headers
|
||||
#include "utils/ThrustCurveAPI.h"
|
||||
#include "utils/Logger.h"
|
||||
|
||||
namespace utils
|
||||
{
|
||||
|
||||
ThrustCurveAPI::ThrustCurveAPI()
|
||||
: hostname("https://www.thrustcurve.org/"),
|
||||
curlConnection()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
ThrustCurveAPI::~ThrustCurveAPI()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
std::optional<ThrustCurve> ThrustCurveAPI::getThrustCurve(const std::string& id)
|
||||
{
|
||||
std::stringstream endpoint;
|
||||
endpoint << hostname << "api/v1/download.json?motorId=" << id << "&data=samples";
|
||||
std::vector<std::string> extraHeaders = {};
|
||||
|
||||
std::string res = curlConnection.get(endpoint.str(), extraHeaders);
|
||||
model::MotorModel mm;
|
||||
|
||||
if(!res.empty())
|
||||
{
|
||||
try
|
||||
{
|
||||
Json::Reader reader;
|
||||
Json::Value jsonResult;
|
||||
reader.parse(res, jsonResult);
|
||||
|
||||
std::vector<std::pair<double, double>> samples;
|
||||
for(Json::ValueConstIterator iter = jsonResult["results"].begin();
|
||||
iter != jsonResult["results"].end();
|
||||
++iter)
|
||||
{
|
||||
// if there are more than 1 items in the results list, we only want the RASP data
|
||||
// Otherwise just take whatever is there
|
||||
if(std::next(iter) != jsonResult["results"].end())
|
||||
{
|
||||
if( (*iter)["format"].asString() != "RASP")
|
||||
continue;
|
||||
}
|
||||
for(Json::ValueConstIterator samplesIter = (*iter)["samples"].begin();
|
||||
samplesIter != (*iter)["samples"].end();
|
||||
++samplesIter)
|
||||
{
|
||||
samples.push_back(std::make_pair((*samplesIter)["time"].asDouble(),
|
||||
(*samplesIter)["thrust"].asDouble()));
|
||||
|
||||
}
|
||||
}
|
||||
return ThrustCurve(samples);
|
||||
}
|
||||
catch(const std::exception& e)
|
||||
{
|
||||
std::string err("Unable to parse JSON from Thrustcurve motor data request. Error: ");
|
||||
err += e.what();
|
||||
|
||||
Logger::getInstance()->error(err);
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
|
||||
}
|
||||
|
||||
model::MotorModel ThrustCurveAPI::getMotorData(const std::string& motorId)
|
||||
{
|
||||
std::stringstream endpoint;
|
||||
endpoint << hostname << "api/v1/download.json?motorId=" << motorId << "&data=samples";
|
||||
std::vector<std::string> extraHeaders = {};
|
||||
|
||||
std::string res = curlConnection.get(endpoint.str(), extraHeaders);
|
||||
model::MotorModel mm;
|
||||
|
||||
if(!res.empty())
|
||||
{
|
||||
try
|
||||
{
|
||||
Json::Reader reader;
|
||||
Json::Value jsonResult;
|
||||
reader.parse(res, jsonResult);
|
||||
|
||||
std::vector<std::pair<double, double>> samples;
|
||||
for(Json::ValueConstIterator iter = jsonResult["results"].begin();
|
||||
iter != jsonResult["results"].end();
|
||||
++iter)
|
||||
{
|
||||
// if there are more than 1 items in the results list, we only want the RASP data
|
||||
// Otherwise just take whatever is there
|
||||
if(std::next(iter) != jsonResult["results"].end())
|
||||
{
|
||||
if( (*iter)["format"].asString() != "RASP")
|
||||
continue;
|
||||
}
|
||||
for(Json::ValueConstIterator samplesIter = (*iter)["samples"].begin();
|
||||
samplesIter != (*iter)["samples"].end();
|
||||
++samplesIter)
|
||||
{
|
||||
samples.push_back(std::make_pair((*samplesIter)["time"].asDouble(),
|
||||
(*samplesIter)["thrust"].asDouble()));
|
||||
|
||||
}
|
||||
}
|
||||
ThrustCurve tc(samples);
|
||||
mm.addThrustCurve(tc);
|
||||
}
|
||||
catch(const std::exception& e)
|
||||
{
|
||||
std::string err("Unable to parse JSON from Thrustcurve motor data request. Error: ");
|
||||
err += e.what();
|
||||
|
||||
Logger::getInstance()->error(err);
|
||||
}
|
||||
}
|
||||
|
||||
return mm;
|
||||
}
|
||||
|
||||
ThrustcurveMetadata ThrustCurveAPI::getMetadata()
|
||||
{
|
||||
|
||||
std::string endpoint = hostname;
|
||||
endpoint += "api/v1/metadata.json";
|
||||
std::string result = curlConnection.get(endpoint, extraHeaders);
|
||||
ThrustcurveMetadata ret;
|
||||
|
||||
if(!result.empty())
|
||||
{
|
||||
try
|
||||
{
|
||||
Json::Reader reader;
|
||||
Json::Value jsonResult;
|
||||
reader.parse(result, jsonResult);
|
||||
|
||||
for(Json::ValueConstIterator iter = jsonResult["certOrgs"].begin();
|
||||
iter != jsonResult["certOrgs"].end();
|
||||
++iter)
|
||||
{
|
||||
std::string org = (*iter)["abbrev"].asString();
|
||||
|
||||
if(org == "AMRS")
|
||||
ret.certOrgs.emplace_back(model::MotorModel::CERTORG::AMRS);
|
||||
else if(org == "CAR")
|
||||
ret.certOrgs.emplace_back(model::MotorModel::CERTORG::CAR);
|
||||
else if(org == "NAR")
|
||||
ret.certOrgs.emplace_back(model::MotorModel::CERTORG::NAR);
|
||||
else if(org == "TRA")
|
||||
ret.certOrgs.emplace_back(model::MotorModel::CERTORG::TRA);
|
||||
else if(org == "UNC")
|
||||
ret.certOrgs.emplace_back(model::MotorModel::CERTORG::UNC);
|
||||
else
|
||||
ret.certOrgs.emplace_back(model::MotorModel::CERTORG::UNK);
|
||||
}
|
||||
for(Json::ValueConstIterator iter = jsonResult["diameters"].begin();
|
||||
iter != jsonResult["diameters"].end();
|
||||
++iter)
|
||||
{
|
||||
ret.diameters.push_back((*iter).asDouble());
|
||||
}
|
||||
for(Json::ValueConstIterator iter = jsonResult["impulseClasses"].begin();
|
||||
iter != jsonResult["impulseClasses"].end();
|
||||
++iter)
|
||||
{
|
||||
ret.impulseClasses.emplace_back((*iter).asString());
|
||||
}
|
||||
for(Json::ValueConstIterator iter = jsonResult["manufacturers"].begin();
|
||||
iter != jsonResult["manufacturers"].end();
|
||||
++iter)
|
||||
{
|
||||
ret.manufacturers[(*iter)["abbrev"].asString()] = (*iter)["name"].asString();
|
||||
}
|
||||
for(Json::ValueConstIterator iter = jsonResult["types"].begin();
|
||||
iter != jsonResult["types"].end();
|
||||
++iter)
|
||||
{
|
||||
std::string type = (*iter)["types"].asString();
|
||||
if(type == "SU")
|
||||
ret.types.emplace_back(model::MotorModel::MOTORTYPE::SU);
|
||||
else if(type == "reload")
|
||||
ret.types.emplace_back(model::MotorModel::MOTORTYPE::RELOAD);
|
||||
else
|
||||
ret.types.emplace_back(model::MotorModel::MOTORTYPE::HYBRID);
|
||||
}
|
||||
}
|
||||
catch(const std::exception& e)
|
||||
{
|
||||
|
||||
std::string err("Unable to parse JSON from Thrustcurve metadata request. Error: ");
|
||||
err += e.what();
|
||||
|
||||
Logger::getInstance()->error(err);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
std::vector<model::MotorModel> ThrustCurveAPI::searchMotors(const SearchCriteria& c)
|
||||
{
|
||||
std::vector<model::MotorModel> retVal;
|
||||
std::string endpoint = hostname;
|
||||
endpoint += "api/v1/search.json?";
|
||||
for(const auto& i : c.criteria)
|
||||
{
|
||||
endpoint += i.first;
|
||||
endpoint += "=";
|
||||
endpoint += i.second;
|
||||
endpoint += "&";
|
||||
}
|
||||
endpoint = endpoint.substr(0, endpoint.length() - 1);
|
||||
|
||||
|
||||
Logger::getInstance()->debug("endpoint: " + endpoint);
|
||||
std::string result = curlConnection.get(endpoint, extraHeaders);
|
||||
if(!result.empty())
|
||||
{
|
||||
try
|
||||
{
|
||||
Json::Reader reader;
|
||||
Json::Value jsonResult;
|
||||
Logger::getInstance()->debug("1");
|
||||
reader.parse(result, jsonResult);
|
||||
Logger::getInstance()->debug("2");
|
||||
|
||||
for(Json::ValueConstIterator iter = jsonResult["results"].begin();
|
||||
iter != jsonResult["results"].end();
|
||||
++iter)
|
||||
{
|
||||
model::MotorModel motorModel;
|
||||
model::MotorModel::MetaData mm;
|
||||
mm.commonName = (*iter)["commonName"].asString();
|
||||
Logger::getInstance()->debug("3");
|
||||
|
||||
std::string availability = (*iter)["availability"].asString();
|
||||
if(availability == "regular")
|
||||
mm.availability = model::MotorModel::MotorAvailability(model::MotorModel::AVAILABILITY::REGULAR);
|
||||
else
|
||||
mm.availability = model::MotorModel::MotorAvailability(model::MotorModel::AVAILABILITY::OOP);
|
||||
|
||||
mm.avgThrust = (*iter)["avgThrustN"].asDouble();
|
||||
mm.burnTime = (*iter)["burnTimeS"].asDouble();
|
||||
Logger::getInstance()->debug("4");
|
||||
// TODO fill in certOrg
|
||||
// TODO fill in delays
|
||||
mm.designation = (*iter)["designation"].asString();
|
||||
mm.diameter = (*iter)["diameter"].asDouble();
|
||||
mm.impulseClass = (*iter)["impulseClass"].asString();
|
||||
mm.length = (*iter)["length"].asDouble();
|
||||
std::string manu = (*iter)["manufacturer"].asString();
|
||||
if(manu == "AeroTech")
|
||||
mm.manufacturer = model::MotorModel::MOTORMANUFACTURER::AEROTECH;
|
||||
//mm.manufacturer = (*iter)["manufacturer"].asString();
|
||||
mm.maxThrust = (*iter)["maxThrustN"].asDouble();
|
||||
mm.motorIdTC = (*iter)["motorId"].asString();
|
||||
mm.propType = (*iter)["propInfo"].asString();
|
||||
mm.propWeight = (*iter)["propWeightG"].asDouble();
|
||||
mm.sparky = (*iter)["sparky"].asBool();
|
||||
mm.totalImpulse = (*iter)["totImpulseNs"].asDouble();
|
||||
mm.totalWeight = (*iter)["totalWeightG"].asDouble();
|
||||
|
||||
std::string type = (*iter)["type"].asString();
|
||||
if(type == "SU")
|
||||
mm.type = model::MotorModel::MotorType(model::MotorModel::MOTORTYPE::SU);
|
||||
else if(type == "reload")
|
||||
mm.type = model::MotorModel::MotorType(model::MotorModel::MOTORTYPE::RELOAD);
|
||||
else
|
||||
mm.type = model::MotorModel::MotorType(model::MotorModel::MOTORTYPE::HYBRID);
|
||||
|
||||
Logger::getInstance()->debug("5");
|
||||
auto tc = getThrustCurve(mm.motorIdTC);
|
||||
motorModel.moveMetaData(std::move(mm));
|
||||
Logger::getInstance()->debug("6");
|
||||
if(tc)
|
||||
{
|
||||
motorModel.addThrustCurve(*tc);
|
||||
}
|
||||
retVal.push_back(motorModel);
|
||||
}
|
||||
}
|
||||
catch(const std::exception& e)
|
||||
{
|
||||
|
||||
std::string err("Unable to parse JSON from Thrustcurve metadata request. Error: ");
|
||||
err += e.what();
|
||||
|
||||
Logger::getInstance()->error(err);
|
||||
}
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
void SearchCriteria::addCriteria(const std::string& name,
|
||||
const std::string& value)
|
||||
{
|
||||
criteria[name] = value;
|
||||
}
|
||||
|
||||
} // namespace utils
|
103
utils/ThrustCurveAPI.h
Normal file
@ -0,0 +1,103 @@
|
||||
#ifndef UTILS_THRUSTCURVEAPI_H
|
||||
#define UTILS_THRUSTCURVEAPI_H
|
||||
|
||||
|
||||
/// \cond
|
||||
// C headers
|
||||
// C++ headers
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
// 3rd party headers
|
||||
/// \endcond
|
||||
|
||||
// qtrocket headers
|
||||
#include "utils/CurlConnection.h"
|
||||
#include "model/MotorModel.h"
|
||||
|
||||
namespace utils
|
||||
{
|
||||
|
||||
class ThrustcurveMetadata
|
||||
{
|
||||
public:
|
||||
ThrustcurveMetadata() = default;
|
||||
~ThrustcurveMetadata() = default;
|
||||
|
||||
ThrustcurveMetadata(const ThrustcurveMetadata&) = default;
|
||||
ThrustcurveMetadata(ThrustcurveMetadata&&) = default;
|
||||
|
||||
ThrustcurveMetadata& operator=(const ThrustcurveMetadata&) = default;
|
||||
ThrustcurveMetadata& operator=(ThrustcurveMetadata&&) = default;
|
||||
|
||||
//private:
|
||||
std::vector<model::MotorModel::CertOrg> certOrgs;
|
||||
std::vector<double> diameters;
|
||||
std::vector<std::string> impulseClasses;
|
||||
std::map<std::string, std::string> manufacturers;
|
||||
std::vector<model::MotorModel::MotorType> types;
|
||||
|
||||
};
|
||||
|
||||
class SearchCriteria
|
||||
{
|
||||
public:
|
||||
SearchCriteria() = default;
|
||||
~SearchCriteria() = default;
|
||||
SearchCriteria(const SearchCriteria&) = default;
|
||||
SearchCriteria(SearchCriteria&&) = default;
|
||||
|
||||
SearchCriteria& operator=(const SearchCriteria&) = default;
|
||||
SearchCriteria& operator=(SearchCriteria&&) = default;
|
||||
|
||||
void addCriteria(const std::string& name,
|
||||
const std::string& vaue);
|
||||
|
||||
std::map<std::string, std::string> criteria;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief This API for Thrustcurve.org - It will provide an interface for querying thrustcurve.org
|
||||
* for motor data
|
||||
*
|
||||
*/
|
||||
class ThrustCurveAPI
|
||||
{
|
||||
public:
|
||||
ThrustCurveAPI();
|
||||
~ThrustCurveAPI();
|
||||
|
||||
|
||||
/**
|
||||
* @brief getThrustCurve will download the thrust data for the given Motor using the motorId
|
||||
* @param m MotorModel to populate
|
||||
*/
|
||||
model::MotorModel getMotorData(const std::string& motorId);
|
||||
|
||||
|
||||
/**
|
||||
* @brief getMetaData
|
||||
*/
|
||||
|
||||
ThrustcurveMetadata getMetadata();
|
||||
|
||||
std::vector<model::MotorModel> searchMotors(const SearchCriteria& c);
|
||||
|
||||
|
||||
|
||||
private:
|
||||
|
||||
const std::string hostname;
|
||||
CurlConnection curlConnection;
|
||||
|
||||
std::optional<ThrustCurve> getThrustCurve(const std::string& id);
|
||||
|
||||
// no extra headers, but CurlConnection library wants them
|
||||
const std::vector<std::string> extraHeaders{};
|
||||
};
|
||||
|
||||
} // namespace utils
|
||||
|
||||
#endif // UTILS_THRUSTCURVEAPI_H
|
32
utils/math/Constants.h
Normal file
@ -0,0 +1,32 @@
|
||||
#ifndef UTILS_MATH_CONSTANTS_H
|
||||
#define UTILS_MATH_CONSTANTS_H
|
||||
|
||||
namespace utils::math
|
||||
{
|
||||
|
||||
namespace Constants
|
||||
{
|
||||
constexpr double Rstar = 8.31432;
|
||||
constexpr const double g0 = 9.80665;
|
||||
constexpr const double airMolarMass = 0.0289644;
|
||||
|
||||
// gamma is the ratio of the specific heat of air at constant pressure to that at
|
||||
// constant volume. Used in computing the speed of sound
|
||||
constexpr const double gamma = 1.4;
|
||||
|
||||
// beta is used in calculating the dynamic viscosity of air. Based on the 1976 US Standard
|
||||
// Atmosphere report, it is empirically measured to be:
|
||||
constexpr const double beta = 1.458e-6;
|
||||
// Sutherland's constant
|
||||
constexpr const double S = 110.4;
|
||||
constexpr const double standardTemperature = 288.15;
|
||||
constexpr const double standardDensity = 1.2250;
|
||||
constexpr const double meanEarthRadiusWGS84 = 6371008.8;
|
||||
|
||||
constexpr const long double earthGM = 398600441800000.0;
|
||||
constexpr const double earthGM_km = 398600.4418;
|
||||
|
||||
};
|
||||
|
||||
} // namespace utils::math
|
||||
#endif // UTILS_MATH_CONSTANTS_H
|
74
utils/math/MathTypes.h
Normal file
@ -0,0 +1,74 @@
|
||||
#ifndef UTILS_MATH_MATHTYPES_H
|
||||
#define UTILS_MATH_MATHTYPES_H
|
||||
|
||||
#include <Eigen/Dense>
|
||||
#include <vector>
|
||||
|
||||
/// This is not in any namespace. These typedefs are intended to be used throughout QtRocket,
|
||||
/// so keeping them in the global namespace seems to make sense.
|
||||
|
||||
template <int Size>
|
||||
using MyMatrix = Eigen::Matrix<double, Size, Size>;
|
||||
|
||||
template <int Size>
|
||||
using MyVector = Eigen::Matrix<double, Size, 1>;
|
||||
|
||||
typedef Eigen::Quaterniond Quaternion;
|
||||
|
||||
using Matrix3 = MyMatrix<3>;
|
||||
using Matrix4 = MyMatrix<4>;
|
||||
|
||||
using Vector3 = MyVector<3>;
|
||||
using Vector6 = MyVector<6>;
|
||||
|
||||
/*
|
||||
namespace utils
|
||||
{
|
||||
std::vector<double> myVectorToStdVector(const Vector3& x)
|
||||
{
|
||||
return std::vector<double>{x.coeff(0), x.coeff(1), x.coeff(2)};
|
||||
}
|
||||
|
||||
std::vector<double> myVectorToStdVector(const Vector6& x)
|
||||
{
|
||||
return std::vector<double>{x.coeff(0),
|
||||
x.coeff(1),
|
||||
x.coeff(2),
|
||||
x.coeff(3),
|
||||
x.coeff(4),
|
||||
x.coeff(5)};
|
||||
}
|
||||
}
|
||||
|
||||
class Vector3 : public MyVector<3>
|
||||
{
|
||||
public:
|
||||
template<typename... Args>
|
||||
Vector3(Args&&... args) : MyVector<3>(std::forward<Args>(args)...)
|
||||
{}
|
||||
operator std::vector<double>()
|
||||
{
|
||||
return std::vector<double>{this->coeff(0), this->coeff(1), this->coeff(2)};
|
||||
}
|
||||
};
|
||||
|
||||
class Vector6 : public MyVector<6>
|
||||
{
|
||||
public:
|
||||
template<typename... Args>
|
||||
Vector6(Args&&... args) : MyVector<6>(std::forward<Args>(args)...)
|
||||
{}
|
||||
operator std::vector<double>()
|
||||
{
|
||||
return std::vector<double>{this->coeff(0),
|
||||
this->coeff(1),
|
||||
this->coeff(2),
|
||||
this->coeff(3),
|
||||
this->coeff(4),
|
||||
this->coeff(5)};
|
||||
}
|
||||
};
|
||||
*/
|
||||
|
||||
|
||||
#endif // UTILS_MATH_MATHTYPES_H
|