diff --git a/propulsion_design/CMakeLists.txt b/propulsion_design/CMakeLists.txt index 00a1dd5863dee559b4f5e0ba93f13ad7d4acccfc..92048d807bfb15ffb782fbab45cec9c011085634 100644 --- a/propulsion_design/CMakeLists.txt +++ b/propulsion_design/CMakeLists.txt @@ -13,14 +13,19 @@ set(MODULE_SOURCES src/engine_design/engine_design.cpp src/engine_design/rubber/rubber_design.cpp src/engine_design/rubber/turbofan.cpp + src/engine_design/rubber/openrotorfan.cpp src/integration/default/default_integration.cpp src/integration/default/turbofan.cpp + src/integration/default/openrotorfan.cpp src/nacelle/default/default_nacelle.cpp src/nacelle/default/turbofan.cpp + src/nacelle/default/openrotorfan.cpp src/pylon/default/default_pylon.cpp src/pylon/default/turbofan.cpp + src/pylon/default/openrotorfan.cpp src/mass/point_mass.cpp src/mass/default/turbofan.cpp + src/mass/default/openrotorfan.cpp src/io/engine_xml.cpp src/io/aircraft_xml.cpp src/report/create_html_report.cpp diff --git a/propulsion_design/propulsion_design_conf.xml b/propulsion_design/propulsion_design_conf.xml index 82ac2cd4c41ccaabfef603dba8dcc6bd71e7863d..ba8b56252deeeff7710a2cde17f5a2566fc980a0 100644 --- a/propulsion_design/propulsion_design_conf.xml +++ b/propulsion_design/propulsion_design_conf.xml @@ -121,7 +121,7 @@ </BPR> </Empirical> <Rubber description="Settings for rubber engine designer. Selector: PW1127G-JM / V2527-A5"> - <engine_model><value>PW1127G-JM</value></engine_model> + <engine_model><value>CROR</value></engine_model> </Rubber> <GasTurb description="Settings for gasTurb interface engine designer"> </GasTurb> diff --git a/propulsion_design/src/engine_design/engine_design.cpp b/propulsion_design/src/engine_design/engine_design.cpp index c3d583aa35c4f834be0459dfcec4680a22ae097b..9e58678de03f1fb915ae621051fcc7e3fda6d4c5 100644 --- a/propulsion_design/src/engine_design/engine_design.cpp +++ b/propulsion_design/src/engine_design/engine_design.cpp @@ -1,7 +1,7 @@ /* * UNICADO - UNIversity Conceptual Aircraft Design and Optimization * - * Copyright (C) 2025 UNICADO consortium + * Copyright (C) 2024 UNICADO consortium * * 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 @@ -84,4 +84,36 @@ namespace design } return deck_data; } + + // Function, that generates an empty deck data + auto EngineDesigner::generate_empty_deck_data(std::string name, std::vector<double> FL, std::vector<double> Mach, std::vector<double> N) -> DeckData + { + DeckData deck; + + // Assign values of the arguments to the deck + deck.name = name; + deck.FL = FL; // Example flight levels in meters + deck.Mach = Mach; // Example Mach numbers + deck.N = N; // Example engine speeds + + + // Set a default value + const double default_value = -9999.0; + + // Define the shape of the multi_array + boost::array<std::size_t, 3> shape = {deck.N.size(), deck.FL.size(), deck.Mach.size()}; + deck.values.resize(shape); + + // Populate the multi_array with example values + for (std::size_t i = 0; i < shape[0]; ++i) { + for (std::size_t j = 0; j < shape[1]; ++j) { + for (std::size_t k = 0; k < shape[2]; ++k) { + deck.values[i][j][k] = default_value; // Set each value to -9999 + } + } + } + + return deck; +} + }; // namespace design diff --git a/propulsion_design/src/engine_design/engine_design.h b/propulsion_design/src/engine_design/engine_design.h index d4da2ead77a263792b8a99c05577e2fb8cf21b91..af84dff5f9a23d9bd05cecdb0d5eb1f1f6ba5bc6 100644 --- a/propulsion_design/src/engine_design/engine_design.h +++ b/propulsion_design/src/engine_design/engine_design.h @@ -156,6 +156,15 @@ namespace design */ auto scale_deck_data_values(DeckData &deck_data, double efficiency_factor) -> DeckData; + /** + * @brief Recalculate the engine data deck with the efficiency defined in the engine.xml. + * + * @param deck_data Engine deck data values. + * @param efficiency_factor Value of the efficiency factor defined in the engine.xml. + * @return scaled deck data. + */ + auto generate_empty_deck_data(std::string name, std::vector<double> FL, std::vector<double> Mach, std::vector<double> N) -> DeckData; + private: /* === Properties === */ std::unordered_map<std::string, double> technology_factors; /** [-] The technology factors of the components. */ diff --git a/propulsion_design/src/engine_design/rubber/openrotorfan.cpp b/propulsion_design/src/engine_design/rubber/openrotorfan.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0556719d51caeb64743731ddc3c51ae0f830a780 --- /dev/null +++ b/propulsion_design/src/engine_design/rubber/openrotorfan.cpp @@ -0,0 +1,95 @@ +/* + * UNICADO - UNIversity Conceptual Aircraft Design and Optimization + * + * Copyright (C) 2025 UNICADO consortium + * + * 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/>. + * + * Description: + * This file is part of UNICADO. + */ + +/* === Includes === */ +#include "rubber_design.h" +#include <cmath> +#include <format> +#include "../../utility.h" +#include "../../io/engine_xml.h" + +/* === Design implementations === */ +namespace design +{ + void Rubber::operator()(Openrotorfan<EnergyCarrier::Kerosene> &engine) + { + /* Check whether the engine model is preselected */ + auto model_preselected = this->preselected_engine(engine.id()); + if (model_preselected.has_value()) + { + engine.set_model(*model_preselected); + } + else + { + /** @todo Should this selection always take place or should we stick to the selection + * in the following iteration loops even if there is a better fitting engine now? + */ + engine.set_model(this->select_engine(engine.required_thrust())); + } + + /* Get the engine data from the project or database directory */ + const EngineData engine_data = + io::load_engine_data(engine.model(), + {this->engine_directory(), this->engine_database()}); + + /* Scale the engine */ + + /* Load the unscaled engine */ + auto engine_unscaled = io::load_engine_scaled( + engine.model(), + 1, + {this->engine_directory(), + this->engine_database()}); + + /* Get the unscaled SLST value from the deck values as the highest N1 at SLS */ + engine_unscaled.calculate_N1_with_penalties(0 , 0, this->flight_condition().ambiance, 1.0, "takeoff", 0, 0); + + /* Scale the engine */ + engine.set_scale( + engine.required_thrust() > 0.0 ? engine.required_thrust() / engine_unscaled.get_thrust() : 1.0); // engine_unscaled.get_thrust() + + /* Set and sale the parameters according to Ray12 p. 285 */ + engine.set_dimension({engine_data.dimensions().height * std::pow(engine.scale(), 0.5), + engine_data.dimensions().width * std::pow(engine.scale(), 0.5), + engine_data.dimensions().length * std::pow(engine.scale(), 0.4)}); + engine.set_fan({engine_data.dimensions().diameter * std::pow(engine.scale(), 0.5)}); + + /* Set the engine mass */ + PointMass engine_mass; + engine_mass.mass = std::pow(engine.scale(), 1.1) * engine_data.dry_mass(); // Ray12 p. 285 + engine_mass.mass *= this->technology_factor("engine_mass"); // Apply the technology factor + engine.set_pointmass(Component::Engine, engine_mass); + + /* Set the bucket point of the engine */ + engine.set_bucket_point( + this->calculate_bucket_point(engine) + ); + + /* At this point the engine was successfully sized */ + this->add_designed_engine(engine.model()); + + /* Print information */ + utility::print(std::format( + "Designed engine: {} with thrust = {:6.2f} kN, mass = {:5.1f} kg, scale = {:1.2f}", + engine.model(), engine.required_thrust() / 1000, engine_mass.mass, engine.scale()), Level::Out); + } +} \ No newline at end of file diff --git a/propulsion_design/src/engine_design/rubber/rubber_design.h b/propulsion_design/src/engine_design/rubber/rubber_design.h index 5dc71b364d1942753758bd8678d339478a16023d..5aff95423568afeda9f637230ae8f23294c6922f 100644 --- a/propulsion_design/src/engine_design/rubber/rubber_design.h +++ b/propulsion_design/src/engine_design/rubber/rubber_design.h @@ -77,6 +77,8 @@ namespace design void operator()(Turbofan<EnergyCarrier::Liquid_Hydrogen> &engine); // NOLINT runtime/references + void operator()(Openrotorfan<EnergyCarrier::Kerosene> &engine); // NOLINT runtime/references + private: /* === Methods === */ /** diff --git a/propulsion_design/src/integration/default/default_integration.h b/propulsion_design/src/integration/default/default_integration.h index 5996c20e915333e6f1e00d8ce1f8c55bb28256e1..5bb570bd954942ef96a2e8bc819d45c3992e7f09 100644 --- a/propulsion_design/src/integration/default/default_integration.h +++ b/propulsion_design/src/integration/default/default_integration.h @@ -76,6 +76,8 @@ namespace design void operator()(Turbofan<EnergyCarrier::Liquid_Hydrogen>& engine); // NOLINT runtime/references + void operator()(Openrotorfan<EnergyCarrier::Kerosene> &engine); // NOLINT runtime/references + private: /* === Methods === */ /** @@ -104,6 +106,22 @@ namespace design template <EnergyCarrier EC> void turbofan_method(Turbofan<EC>& engine); // NOLINT runtime/references + + template <EnergyCarrier EC> + void integrate_into_wing(Openrotorfan<EC> &engine); // NOLINT runtime/references + + template <EnergyCarrier EC> + void integrate_into_fuselage(Openrotorfan<EC> &engine); // NOLINT runtime/references + + template <EnergyCarrier EC> + void integrate_into_empennage(Openrotorfan<EC> &engine); // NOLINT runtime/references + + template <EnergyCarrier EC> + void guess_position(Openrotorfan<EC> &engine); // NOLINT runtime/references + + template <EnergyCarrier EC> + void openrotorfan_method(Openrotorfan<EC>& engine); // NOLINT runtime/references + /** * @brief Calculate the engine span location on the wing * according to the procedure described in \cite Ata10. @@ -117,6 +135,24 @@ namespace design * @return double [m] The absolute span position of the engine. */ [[nodiscard]] auto calculate_span_position( + const double width_fuselage, + const double diameter_engine_max, + const double wing_span, + const bool is_outer) const -> double; + + /** + * @brief Calculate the engine span location on the wing + * according to the procedure described in \cite Ata10. + * @attention This always returns positive values! You have to adjust + * the sign according to the wing extrusion direction. + * + * @param width_fuselage [m] The maximum width of the fuselage. + * @param diameter_engine_max [m] The maximum diameter of the engine. + * @param wing_span [m] The absolute span of the wing. + * @param is_outer [-] Whether the position should be calculated for the outer engine. + * @return double [m] The absolute span position of the engine. + */ + [[nodiscard]] auto calculate_span_position_openrotor( const double width_fuselage, const double diameter_engine_max, const double wing_span, diff --git a/propulsion_design/src/integration/default/openrotorfan.cpp b/propulsion_design/src/integration/default/openrotorfan.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9eca5064460b6a4937a21788907b445f3ec8012a --- /dev/null +++ b/propulsion_design/src/integration/default/openrotorfan.cpp @@ -0,0 +1,331 @@ +/* + * UNICADO - UNIversity Conceptual Aircraft Design and Optimization + * + * Copyright (C) 2025 UNICADO consortium + * + * 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/>. + * + * Description: + * This file is part of UNICADO. + */ + +/* === Includes === */ +#include "default_integration.h" +#include <aircraftGeometry2/processing/measure.h> +#include <aircraftGeometry2/processing/transform.h> +#include <cmath> +#include "../../utility.h" + +/* === Design implementations === */ +namespace design +{ + namespace integration + { + namespace detail + { + /** + * @brief Set of supported parent components for the engine integration. + */ + const std::set<ParentComponent> supported_parents = { + {Parent::Wing, Lateral::Right, Longitudinal::Front, Vertical::Under}, + {Parent::Wing, Lateral::Left, Longitudinal::Front, Vertical::Under}, + {Parent::Wing, Lateral::Right, Longitudinal::Front, Vertical::Over}, + {Parent::Wing, Lateral::Left, Longitudinal::Front, Vertical::Over}, + {Parent::Fuselage, Lateral::Right, Longitudinal::Rear, Vertical::Mid}, + {Parent::Fuselage, Lateral::Left, Longitudinal::Rear, Vertical::Mid}, + {Parent::Empennage, Lateral::Mid, Longitudinal::Front, Vertical::In}}; + } // namespace detail + + template <EnergyCarrier EC> + void Default::openrotorfan_method(Openrotorfan<EC> &engine) + { + /* Check whether the parent component is supported */ + if (!detail::supported_parents.contains(engine.parent_component())) + { + throw std::runtime_error("[design::integrator::Default] The parent component or location of the engine is not supported."); + } + + /* Check whether valid aircraft geometry was provided */ + if (this->aircraft()) + { + /* Check which parent component this engine has */ + switch (std::get<Parent>(engine.parent_component())) + { + case Parent::Wing: + /* Integrate the engine into the wing */ + integrate_into_wing(engine); + break; + case Parent::Fuselage: + /* Integrate the engine into the fuselage */ + integrate_into_fuselage(engine); + break; + case Parent::Empennage: + /* Integrate the engine into the empennage */ + integrate_into_empennage(engine); + break; + default: + throw std::runtime_error("[design::integrator::Default] The parent component of the engine is not supported."); + } + } + else + { + /* When no aircraft geometry exists we need to guess the initial engine position */ + this->guess_position(engine); + } + + /* At this point we can assume the engine was placed successfully */ + this->parents_placed.insert(engine.parent_component()); + } + + template <EnergyCarrier EC> + void Default::integrate_into_wing(Openrotorfan<EC> &engine) + { + /* + * => This method follows the procedure described in \cite Ata10 + * It assumes one main wing which is extruded in global Y direction + */ + /** @fixed Add a warning when the aircraft contains more than one wing or fuselage */ + const auto &wing = this->select_wing(); + const auto &fuselage = this->select_fuselage(); + const double sign_wing_extrusion = std::signbit(wing.sections.back().origin.z()) ? -1.0 : 1.0; + if (wing.normal != geom2::Direction_3{0, sign_wing_extrusion * 1, 0}) + { + throw std::runtime_error("[design::integrator::Default] The wing is not oriented in y-direction. This method is not applicable."); + } + if (std::get<Vertical>(engine.parent_component()) != Vertical::Under) + { + throw std::runtime_error("[design::integrator::Default] The engine is not under the wing. This method is not applicable."); + } + + /* When the current parent position is already placed, we assume this is the outer engine */ + bool is_outer = this->parents_placed.contains(engine.parent_component()); + + /* Check whether there is still space for the engine to be integrated */ + switch (this->n_engines_.wing) + { + case 4: + /* Check whether the outer position is already placed */ + if (is_outer && this->engines_done.wing % 2) + { + break; + } + [[fallthrough]]; + case 2: + /* At this point there does not exist an outer engine position */ + if (is_outer) + { + throw std::runtime_error("[design::integrator::Default] No space on the wing for the engine to be integrated."); + } + break; + default: + throw std::runtime_error("[design::integrator::Default] The number of engines on the wing is not supported."); + } + + /* !!! + * The following assumes the wing local coordinate system: + * - X axis: chord direction + * - Y axis: height direction + * - Z axis: span direction (should be negative coordinates) + * !!! + */ + /* Get the relevant measurements from the aircraft */ + const double span = geom2::measure::span(wing); + const double width_fuselage = geom2::measure::width_max(fuselage); + const double diameter_max = 1.21 * engine.fan().diameter; // \cite Ata10 p. 45 + + /* Wing local z position of engine */ + const double span_engine = sign_wing_extrusion * this->calculate_span_position_openrotor( + width_fuselage, diameter_max, span, is_outer); + + /* Get the wing quarter coordinate at the engine span position */ + const double wing_chord = geom2::measure::chord(wing, span_engine); + const auto wing_quarter = + geom2::measure::offset_LE(wing, span_engine) + + geom2::Vector_3{0.25 * wing_chord, 0, 0}; + + /* Wing local x position of engine */ + const double chord_engine = -0.9 * wing_chord + geom2::measure::offset_LE(wing, span_engine).x(); // \cite Ata10 p. 48f + + /* Wing local y position of engine */ + const double height_engine = wing_quarter.y() - + 0.5 * geom2::measure::height_max(engine.nacelle()) - + 0.1 * wing_chord; // \cite Ata10 p. 48f adapted to geometry_lib + + /* Adjust whether the engine is left or right */ + auto position = geom2::Point_3{chord_engine, height_engine, span_engine}; + + /* Include user input in there to shift engine in every direction acc to config */ + position = {position.x() + usershift_x, position.y() + usershift_z, position.z() - usershift_y}; + + if (std::get<Lateral>(engine.parent_component()) == Lateral::Left) + { + position = {position.x(), position.y(), -position.z()}; + } + + /* Set the position of the engine in the GLOBAL (!) reference frame */ + engine.set_position(geom2::transform::to_parent(wing, position)); + ++this->engines_done.wing; + } + + template <EnergyCarrier EC> + void Default::integrate_into_fuselage(Openrotorfan<EC> &engine) + { + /* + * => This method follows the procedure described in \cite Ata10 + * It assumes one main fuselage which is extruded in global X direction + */ + /** @fixed Add a warning when there are more than one fuselages */ + const auto &fuselage = this->select_fuselage(); + const double sign_extrusion = std::signbit(fuselage.sections.back().origin.z()) ? -1.0 : 1.0; + if (fuselage.normal != geom2::Direction_3{sign_extrusion * 1, 0, 0}) + { + throw std::runtime_error("[design::integrator::Default] The fuselage is not oriented in x-direction. This method is not applicable."); + } + + /* Only two engines on the fuselage are supported right now */ + if (this->n_engines_.fuselage != 2) + { + throw std::runtime_error("[design::integrator::Default] The number of engines on the fuselage is not supported."); + } + + /* Check whether there is still space for the engine to be integrated */ + if (this->parents_placed.contains(engine.parent_component())) + { + throw std::runtime_error("[design::integrator::Default] No space left on the fuselage for the engine to be integrated."); + } + + /* !!! + * The following assumes the fuselage local coordinate system: + * - X axis: width direction + * - Y axis: height direction + * - Z axis: length direction + * !!! + */ + /* Get the engine position in local z direction */ + const double z_engine = sign_extrusion * 0.8 * geom2::measure::length(fuselage); + + /* Get the engine position in local y direction */ + const double y_engine = 0.25 * geom2::measure::height(fuselage, z_engine); + + /* Get the engine position in local x direction */ + const double x_engine = 0.5 * geom2::measure::width(fuselage, z_engine) + + 1.25 * 0.5 * geom2::measure::width_max(engine.nacelle()); + + /* Adjust whether engine is left or right */ + auto position = geom2::Point_3{x_engine, y_engine, z_engine}; + if (std::get<Lateral>(engine.parent_component()) == Lateral::Left) + { + position = {-position.x(), position.y(), position.z()}; + } + + /* Set the position of the engine in the GLOBAL (!) reference frame */ + engine.set_position(geom2::transform::to_parent(fuselage,position)); + ++this->engines_done.fuselage; + } + + template <EnergyCarrier EC> + void Default::integrate_into_empennage(Openrotorfan<EC> &engine) + { + /* Get the first empennage surface */ + /** @fixed Make the selection which empennage surface to integrate the engine into a bit smarter. */ + const auto &empennage = this->select_vertical_tail(); + const auto &fuselage = this->select_fuselage(); + + /* Check whether there is still space for the engine to be integrated */ + if (this->parents_placed.contains(engine.parent_component())) + { + throw std::runtime_error("[design::integrator::Default] No space left on the empennage for the engine to be integrated."); + } + + /* Check whether the empennage surface is in line with the fuselage center */ + if (std::abs(fuselage.origin.y() - empennage.origin.y()) > 1e-4) + { + throw std::runtime_error( + "[design::integrator::Default] The empennage is not in line with the fuselage center. " + "This method is not applicable!"); + } + + /* Get quarter line point at the mac position of the empennage surface */ + const auto mac_position = geom2::measure::mean_aerodynamic_chord_position(empennage); + const auto chord = geom2::measure::chord(empennage, mac_position); + const auto p_25_local = geom2::measure::offset_LE(empennage, mac_position) + + geom2::Vector_3{0.25 * chord, 0, 0}; + const auto p_25_global = geom2::transform::to_parent(empennage, p_25_local); + const auto p_25_fuselage = geom2::transform::to_local(fuselage, p_25_global); + + /* Calculate the engine position */ + const auto position = geom2::Point_3{ + p_25_global.x() - 0.25 * engine.dimension().length, // Place engine 25% in front of quarter line + fuselage.origin.y(), // Place engine in the middle of the fuselage + 0.5 * geom2::measure::height(fuselage, p_25_fuselage.z()) + 1.25 * 0.5 * engine.dimension().width}; + + /* Set the position of the engine */ + engine.set_position(position); + ++this->engines_done.empennage; + } + + template <EnergyCarrier EC> + void Default::guess_position(Openrotorfan<EC> &engine) + { + /* Guess the position of the engine based on their parent component */ + switch (std::get<Parent>(engine.parent_component())) + { + /* For now place everything at the origin */ + case Parent::Wing: + case Parent::Fuselage: + case Parent::Empennage: + default: + /* Fall back to ORIGIN as the default position */ + engine.set_position(CGAL::ORIGIN); + break; + } + } + + auto Default::calculate_span_position_openrotor( + const double width_fuselage, + const double diameter_engine_max, + const double wing_span, + const bool is_outer) const -> double + { + /* How many engines on the wing */ + if (this->n_engines_.wing == 2) + { + return 0.95 * (width_fuselage / 2.0 + + 1.3 * diameter_engine_max + + 0.5 * diameter_engine_max); // \cite Ata10 p. 52, 0.95 additional factor + } + else if (this->n_engines_.wing == 4) + { + /* Return the position whether the engine is on the outer + * or inner side of the wing. + */ + if (is_outer) + { + return 0.64 * wing_span / 2.0; // \cite Ata10 p. 83 + } + return (0.9 / 4.0) * (0.64 * wing_span + + width_fuselage - + diameter_engine_max); // \cite Ata10 p. 94, 0.9 additional factor + } + /* Not a valid engine on the wing count */ + throw std::runtime_error("[design::integrator::Default] The number of engines on the wing is not valid."); + } + + void Default::operator()(Openrotorfan<EnergyCarrier::Kerosene>& engine) + { + openrotorfan_method(engine); + }; + + } // namespace integration +} // namespace design diff --git a/propulsion_design/src/io/aircraft_xml.cpp b/propulsion_design/src/io/aircraft_xml.cpp index 620e13705e8f8dfcaa7e8dd93c057f42427905fb..961872c7cefbe5cff35818daf4317b9eaa4442fb 100644 --- a/propulsion_design/src/io/aircraft_xml.cpp +++ b/propulsion_design/src/io/aircraft_xml.cpp @@ -163,6 +163,16 @@ namespace io this->insert_propulsion<EnergyCarrier::Liquid_Hydrogen>(engine); } + /** + * @brief Insert the data of a turbofan engine. + * + * @param engine The engine to insert into the xml. + */ + void insert(const Openrotorfan<EnergyCarrier::Kerosene> &engine) override + { + this->insert_propulsion<EnergyCarrier::Kerosene>(engine); + } + private: /* === Methods === */ /** diff --git a/propulsion_design/src/io/aircraft_xml.h b/propulsion_design/src/io/aircraft_xml.h index a9ddf7d00340b59ac65e95649c4e97287eef9bf8..2f0ddce3b6ae5a6a7f252409cef73a7f725e0c36 100644 --- a/propulsion_design/src/io/aircraft_xml.h +++ b/propulsion_design/src/io/aircraft_xml.h @@ -47,6 +47,7 @@ namespace io virtual void insert(const Turbofan<EnergyCarrier::Liquid_Hydrogen> &engine) = 0; virtual void insert(const Turboprop<EnergyCarrier::Kerosene> &engine) = 0; virtual void insert(const Turboprop<EnergyCarrier::Liquid_Hydrogen> &engine) = 0; + virtual void insert(const Openrotorfan<EnergyCarrier::Kerosene> &engine) = 0; }; }; // namespace detail @@ -113,6 +114,19 @@ namespace io this->xml_interface->insert(engine); } + /** + * @brief Insert the data of a turboprop engine + * into the aircraft xml. + * + * @tparam carrier The energy carrier of the engine. + * @param engine The engine to insert into the xml. + */ + template <EnergyCarrier carrier> + void insert(const Openrotorfan<carrier> &engine) + { + this->xml_interface->insert(engine); + } + private: std::unique_ptr<detail::AircraftXMLInterface> xml_interface; /** [-] The pointer to the xml interface. */ }; diff --git a/propulsion_design/src/mass/default/default_mass.h b/propulsion_design/src/mass/default/default_mass.h index 9dc4a28b6aa08359bb10b180deb72604cf17d053..caa0fe4dea838ed273ec92a204388cefac9d80a4 100644 --- a/propulsion_design/src/mass/default/default_mass.h +++ b/propulsion_design/src/mass/default/default_mass.h @@ -53,6 +53,17 @@ public: template <EnergyCarrier EC> void turbofan_method(Turbofan<EC>& engine); + /* === Methods === */ + /** + * @brief Analyze the mass of a openrotorfan engine with kerosene as fuel. + * + * @param engine The engine to analyze the mass for. + */ + template <EnergyCarrier EC> + void openrotorfan_method(Openrotorfan<EC>& engine); + + void operator() (Openrotorfan<EnergyCarrier::Kerosene>& engine); + void operator() (Turbofan<EnergyCarrier::Kerosene>& engine); void operator() (Turbofan<EnergyCarrier::Liquid_Hydrogen> &engine); diff --git a/propulsion_design/src/mass/default/openrotorfan.cpp b/propulsion_design/src/mass/default/openrotorfan.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9d8866b98a14bffb2df824d811731ee6a829cb49 --- /dev/null +++ b/propulsion_design/src/mass/default/openrotorfan.cpp @@ -0,0 +1,189 @@ +/* + * UNICADO - UNIversity Conceptual Aircraft Design and Optimization + * + * Copyright (C) 2025 UNICADO consortium + * + * 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/>. + * + * Description: + * This file is part of UNICADO. + */ + +/* === Includes === */ +#include "default_mass.h" +#include <aircraftGeometry2/processing/measure.h> +#include <aircraftGeometry2/processing/transform.h> + +/* === Design implementations === */ +namespace mass +{ +/** + * @brief Method to calculate the engine mass based on a regression line of the LTH-data. + * @cite Pet13 + */ +auto calculate_engine_mass_openrotor(const double engine_technology_factor , const double thrust) -> double +{ + return engine_technology_factor * (0.0159 * thrust + 306.57); +} + +/** + * @brief Method to calculate the engine CG assuming a circular cylinder + */ +auto calculate_engine_CG_openrotor(double length) -> Eigen::Vector3d +{ + /* Calculate local center of gravity position (local KOS @ circle center of fan/propeller) */ + double cg_x_local = 0.5 * length; + Eigen::Vector3d engine_local_cg(cg_x_local, 0, 0); + Eigen::Vector3d engine_cg = engine_local_cg; + return engine_cg; +} + +/** + * @brief Method to calculate the engine inertia with respect to the center of gravity assuming a solid cylinder + */ +auto calculate_engine_inertia_openrotor(double height, double width, double length, double mass) -> Eigen::Matrix3d +{ + Eigen::Matrix3d inertia_tensor = Eigen::Matrix3d::Zero(); + /* Assume average of height and width as engine diameter, respective radius */ + double engine_radius_average = (height + width)/ 4; + inertia_tensor(0, 0) = mass / 2 * pow(engine_radius_average, 2); // in length (x) axis + inertia_tensor(1, 1) = mass / 12 * (3 * pow(engine_radius_average, 2) + pow(length, 2)); // in y axis + inertia_tensor(2, 2) = mass / 12 * (3 * pow(engine_radius_average, 2) + pow(length, 2)); // in z axis - assumed symmetrical + return inertia_tensor; +} + +/** + * @brief Method to calculate the nacelle mass based on a regression line of the LTH-data. + * @cite Pet13 + */ +auto calculate_nacelle_mass_openrotor(const double nacelle_technology_factor, const double thrust)-> double +{ + return nacelle_technology_factor * (thrust * 0.0039 + 595.42); +} + +/** + * @brief Method to calculate the nacelle CG with the aircraftGeometry2 lib + */ +auto calculate_nacelle_CG_openrotor(const geom2::MultisectionSurface<geom2::PolygonSection> &nacelle_surface) -> Eigen::Vector3d +{ + /* + * Get the CG in the engine LOCAL coordinate frame + * => We always assume the CG to be in the LOCAL coordinate frame of the engine + */ + geom2::Point_3 nacelle_geom2_cg_local = geom2::transform::to_parent( + nacelle_surface, + geom2::measure::centroid(nacelle_surface)); + + /* Convert Point_3 in Vector3d */ + Eigen::Vector3d nacelle_cg; + nacelle_cg << nacelle_geom2_cg_local.x(), nacelle_geom2_cg_local.y(), nacelle_geom2_cg_local.z(); + return nacelle_cg; +} + +/** + * @brief Method to calculate the nacelle inertia with respect to the center of gravity with the aircraftGeometry2 lib + */ +auto calculate_nacelle_inertia_openrotor(const geom2::MultisectionSurface<geom2::PolygonSection> &nacelle_surface) -> Eigen::Matrix3d +{ + /* Use geom2 lib to calculate the inertia tensor */ + CGAL::Eigen_matrix inertia_tensor_cgal = geom2::measure::inertia(nacelle_surface); + /* Convert CGAL Eigen_matrix to Eigen Matrix3d */ + Eigen::Matrix3d inertia_tensor = inertia_tensor_cgal.eigen_object(); + return inertia_tensor; +} + +/** + * @brief Method to calculate the pylon mass based on a regression line of the LTH-data. + * @cite Pet13 + */ +auto calculate_pylon_mass_openrotor(const double pylon_technology_factor, const double thrust)-> double +{ + return pylon_technology_factor * (thrust * 0.0024 + 233.29); +} + +/** + * @brief Method to calculate the pylon CG with the aircraftGeometry2 lib + * @return Eigen::Vector3d The center of gravity of the pylon in the engine LOCAL coordinate system. + */ +auto calculate_pylon_CG_openrotor(const geom2::MultisectionSurface<geom2::AirfoilSection> & pylon_surface)-> Eigen::Vector3d +{ + /* Calculate centroid with aircraft geometry lib */ + geom2::Point_3 pylon_geom2_cg_local = geom2::transform::to_parent( + pylon_surface, + geom2::measure::centroid(pylon_surface)); + + /* Convert Point_3 in Vector3d */ + Eigen::Vector3d pylon_cg; + pylon_cg << pylon_geom2_cg_local.x(), pylon_geom2_cg_local.y(), pylon_geom2_cg_local.z(); + return pylon_cg; +} + +/** + * @brief Method to calculate the pylon inertia with respect to the center of gravity with the aircraftGeometry2 lib + */ +auto calculate_pylon_inertia_openrotor(const geom2::MultisectionSurface<geom2::AirfoilSection> &pylon_surface) -> Eigen::Matrix3d +{ + /* Use geom2 lib to calculate the inertia tensor */ + CGAL::Eigen_matrix inertia_tensor_cgal = geom2::measure::inertia(pylon_surface); + /* Convert CGAL Eigen_matrix to Eigen Matrix3d */ + Eigen::Matrix3d inertia_tensor = inertia_tensor_cgal.eigen_object(); + return inertia_tensor; +} + +template <EnergyCarrier EC> +void Default::openrotorfan_method(Openrotorfan<EC>& engine) +{ + const double thrust = engine.required_thrust(); + Dimension_3 dimension_engine = engine.dimension(); + + /* Mass, CG and inertia of engine (mass only if not calculated already) */ + Eigen::Vector3d CG_engine = calculate_engine_CG_openrotor(dimension_engine.length); + double mass_engine = engine.pointmass(Component::Engine).mass; + if (mass_engine == 0.0) + { + /* Calculate engine mass with massAnalyzer function */ + const double tf_engine = this->technology_factor("engine_mass"); + mass_engine = calculate_engine_mass_openrotor(tf_engine, thrust); + } + Eigen::Matrix3d inertia_engine = calculate_engine_inertia_openrotor(dimension_engine.height, + dimension_engine.width, dimension_engine.length, mass_engine); + engine.set_pointmass(Component::Engine, {CG_engine, inertia_engine, mass_engine}); + + + /* Mass, CG and inertia of nacelle */ + const double tf_nacelle = this->technology_factor("nacelle_mass"); + double mass_nacelle = calculate_nacelle_mass_openrotor(tf_nacelle, thrust); + Eigen::Vector3d CG_nacelle = calculate_nacelle_CG_openrotor(engine.nacelle()); + Eigen::Matrix3d inertia_nacelle = calculate_nacelle_inertia_openrotor(engine.nacelle()); + engine.set_pointmass(Component::Nacelle, {CG_nacelle, inertia_nacelle, mass_nacelle}); + + /* Mass, CG and inertia of pylon (if geometry not empty)*/ + if (!engine.pylon().sections.empty()) + { + const double tf_pylon = this->technology_factor("pylon_mass"); + double mass_pylon = calculate_pylon_mass_openrotor(tf_pylon, thrust); + Eigen::Vector3d CG_pylon = calculate_pylon_CG_openrotor(engine.pylon()); + Eigen::Matrix3d inertia_pylon = calculate_pylon_inertia_openrotor(engine.pylon()); + engine.set_pointmass(Component::Pylon, {CG_pylon, inertia_pylon, mass_pylon}); + } + +} + +void Default::operator()(Openrotorfan<EnergyCarrier::Kerosene>& engine) +{ + openrotorfan_method(engine); +} + + +} //namespace mass diff --git a/propulsion_design/src/nacelle/default/default_nacelle.h b/propulsion_design/src/nacelle/default/default_nacelle.h index 1dd8faf1f050b29e9d8fba572e49a806632b66cf..13a05e5b9636ba81331d994422f7ea4f1c27e1a3 100644 --- a/propulsion_design/src/nacelle/default/default_nacelle.h +++ b/propulsion_design/src/nacelle/default/default_nacelle.h @@ -64,6 +64,7 @@ namespace geometry */ void operator()(Turbofan<EnergyCarrier::Kerosene> &engine); void operator()(Turbofan<EnergyCarrier::Liquid_Hydrogen>& engine); + void operator()(Openrotorfan<EnergyCarrier::Kerosene> &engine); template <EnergyCarrier EC> void turbofan_method(Turbofan<EC>& engine); @@ -77,7 +78,7 @@ namespace geometry */ template <EnergyCarrier EC> - void openrotor_method(Turbofan<EC>& engine); + void openrotor_method(Openrotorfan<EC>& engine); private: /* === Methods === */ diff --git a/propulsion_design/src/nacelle/default/openrotorfan.cpp b/propulsion_design/src/nacelle/default/openrotorfan.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e0f4c43931c8c31b7f0233a007233e8faba51bc1 --- /dev/null +++ b/propulsion_design/src/nacelle/default/openrotorfan.cpp @@ -0,0 +1,120 @@ +/* === Includes === */ +#include "default_nacelle.h" +#include <algorithm> +#include <aircraftGeometry2/geometry/builder.h> + +/* === Design implementations === */ +namespace geometry +{ + namespace nacelle + { + template <EnergyCarrier EC> + void Default::openrotor_method(Openrotorfan<EC> &engine) + { + /* Initialize the surface and its builder */ + geom2::MultisectionSurface<geom2::PolygonSection> surface_test{}; + geom2::MultisectionSurface<geom2::PolygonSection> surface_engine{}; + geom2::SectionBuilder<geom2::PolygonSection> builder_test{}; + geom2::SectionBuilder<geom2::PolygonSection> builder_engine{}; + + /* Get the maximum diameter the nacelle has to fit */ + const double diameter_inner = std::max( + { engine.dimension().width, engine.dimension().height, engine.fan().diameter }); + + /* Create the cross section shape */ + geom2::PolygonSection shape_test = this->get_section_shape(engine.id()); + shape_test.set_width(diameter_inner); + shape_test.set_height(diameter_inner); + + /* Create the cross section shape */ + geom2::PolygonSection shape_engine = this->get_section_shape(engine.id()); + shape_engine.set_width(diameter_inner); + shape_engine.set_height(diameter_inner); + + /* + * Transform the scaled polygon coordinates to + * absolute values, so that the section can be + * scaled using the scale factor. + */ + shape_test.set_contour(shape_test.get_contour(true)); + shape_engine.set_contour(shape_engine.get_contour(true)); + + /* Section 0 */ + shape_engine.set_scale(0.3); + builder_engine.insert_back(shape_engine, {0.0, 0.0, 0.0}); + + /* Section 0 */ + shape_engine.set_scale(0.4); + builder_engine.insert_back(shape_engine, {0.0, 0.0, engine.dimension().length * 0.1}); + + /* Section 0 */ + shape_engine.set_scale(0.4); + builder_engine.insert_back(shape_engine, {0.0, 0.0, engine.dimension().length * 0.2}); + + /* Section 0 */ + shape_engine.set_scale(1.0); + builder_engine.insert_back(shape_engine, {0.0, 0.0, 0.0}); + + /* Section 0 */ + shape_engine.set_scale(1.0); + builder_engine.insert_back(shape_engine, {0.0, 0.0, engine.dimension().length * 0.001}); + + /* Section 0 */ + shape_engine.set_scale(0.4); + builder_engine.insert_back(shape_engine, {0.0, 0.0, 0.0}); + + /* Section 0 */ + shape_engine.set_scale(0.4); + builder_engine.insert_back(shape_engine, {0.0, 0.0, engine.dimension().length * 0.05}); + + /* Section 0 */ + shape_engine.set_scale(0.35); + builder_engine.insert_back(shape_engine, {0.0, 0.0, engine.dimension().length * 0.05}); + + shape_test.set_scale(0.0); + builder_test.insert_back(shape_test, {0.0, 0.0, 0}); + + shape_test.set_scale(0.0); + builder_test.insert_back(shape_test, {0.0, 0.0, engine.dimension().length * 0.401}); + + shape_test.set_scale(0.4); + builder_test.insert_back(shape_test, {0.0, 0.0, engine.dimension().length * 0.0}); + + shape_test.set_scale(0.4); + builder_test.insert_back(shape_test, {0.0, 0.0, engine.dimension().length * 0.05}); + + shape_test.set_scale(1.0); + builder_test.insert_back(shape_test, {0.0, 0.0, 0.0}); + + shape_test.set_scale(1.0); + builder_test.insert_back(shape_test, {0.0, 0.0, engine.dimension().length * 0.001}); + + shape_test.set_scale(0.6); + builder_test.insert_back(shape_test, {0.0, 0.0, 0.0}); + + shape_test.set_scale(0.6); + builder_test.insert_back(shape_test, {0.0, 0.0, engine.dimension().length * 0.248}); + + shape_test.set_scale(0.4); + builder_test.insert_back(shape_test, {0.0, 0.0, engine.dimension().length * 0.3}); + + /* Build the surface and insert it into the engine */ + surface_test.name = "nacelle_" + std::to_string(engine.id()); + surface_test.normal = {1.0, 0.0, 0.0}; // Orient the nacelle along the global X direction + surface_test.sections = builder_test.get_result(); + engine.set_nacelle(surface_test); + + surface_engine.name = "engine_" + std::to_string(engine.id()); + surface_engine.normal = {1.0, 0.0, 0.0}; // Orient the nacelle along the global X direction + surface_engine.sections = builder_engine.get_result(); + engine.set_enginegeometry(surface_engine); + + } + + void Default::operator()(Openrotorfan<EnergyCarrier::Kerosene>& engine) + { + openrotor_method(engine); + }; + + } +} \ No newline at end of file diff --git a/propulsion_design/src/nacelle/default/turbofan.cpp b/propulsion_design/src/nacelle/default/turbofan.cpp index 69348ea66e4bedd2a656d134bd10ef5254fa4a7a..1207ab2598dea2c60e1adaa59488cbb0b4aa280e 100644 --- a/propulsion_design/src/nacelle/default/turbofan.cpp +++ b/propulsion_design/src/nacelle/default/turbofan.cpp @@ -98,114 +98,119 @@ namespace geometry } - template <EnergyCarrier EC> - void Default::openrotor_method(Turbofan<EC> &engine) - { - /* Initialize the surface and its builder */ - geom2::MultisectionSurface<geom2::PolygonSection> surface_test{}; - geom2::MultisectionSurface<geom2::PolygonSection> surface_engine{}; - geom2::SectionBuilder<geom2::PolygonSection> builder_test{}; - geom2::SectionBuilder<geom2::PolygonSection> builder_engine{}; - - /* Get the maximum diameter the nacelle has to fit */ - const double diameter_inner = std::max( - { engine.dimension().width, engine.dimension().height, engine.fan().diameter }); - - /* Create the cross section shape */ - geom2::PolygonSection shape_test = this->get_section_shape(engine.id()); - shape_test.set_width(diameter_inner); - shape_test.set_height(diameter_inner); - - /* Create the cross section shape */ - geom2::PolygonSection shape_engine = this->get_section_shape(engine.id()); - shape_engine.set_width(diameter_inner); - shape_engine.set_height(diameter_inner); - - /* - * Transform the scaled polygon coordinates to - * absolute values, so that the section can be - * scaled using the scale factor. - */ - shape_test.set_contour(shape_test.get_contour(true)); - shape_engine.set_contour(shape_engine.get_contour(true)); - - /* Section 0 */ - shape_engine.set_scale(0.3); - builder_engine.insert_back(shape_engine, {0.0, 0.0, 0.0}); - - /* Section 0 */ - shape_engine.set_scale(0.4); - builder_engine.insert_back(shape_engine, {0.0, 0.0, engine.dimension().length * 0.1}); - - /* Section 0 */ - shape_engine.set_scale(0.4); - builder_engine.insert_back(shape_engine, {0.0, 0.0, engine.dimension().length * 0.2}); - - /* Section 0 */ - shape_engine.set_scale(1.0); - builder_engine.insert_back(shape_engine, {0.0, 0.0, 0.0}); - - /* Section 0 */ - shape_engine.set_scale(1.0); - builder_engine.insert_back(shape_engine, {0.0, 0.0, engine.dimension().length * 0.001}); - - /* Section 0 */ - shape_engine.set_scale(0.4); - builder_engine.insert_back(shape_engine, {0.0, 0.0, 0.0}); - - /* Section 0 */ - shape_engine.set_scale(0.4); - builder_engine.insert_back(shape_engine, {0.0, 0.0, engine.dimension().length * 0.05}); - - /* Section 0 */ - shape_engine.set_scale(0.35); - builder_engine.insert_back(shape_engine, {0.0, 0.0, engine.dimension().length * 0.05}); - - shape_test.set_scale(0.0); - builder_test.insert_back(shape_test, {0.0, 0.0, 0}); - - shape_test.set_scale(0.0); - builder_test.insert_back(shape_test, {0.0, 0.0, engine.dimension().length * 0.401}); - - shape_test.set_scale(0.4); - builder_test.insert_back(shape_test, {0.0, 0.0, engine.dimension().length * 0.0}); - - shape_test.set_scale(0.4); - builder_test.insert_back(shape_test, {0.0, 0.0, engine.dimension().length * 0.05}); - - shape_test.set_scale(1.0); - builder_test.insert_back(shape_test, {0.0, 0.0, 0.0}); - - shape_test.set_scale(1.0); - builder_test.insert_back(shape_test, {0.0, 0.0, engine.dimension().length * 0.001}); - - shape_test.set_scale(0.6); - builder_test.insert_back(shape_test, {0.0, 0.0, 0.0}); - - shape_test.set_scale(0.6); - builder_test.insert_back(shape_test, {0.0, 0.0, engine.dimension().length * 0.248}); - - shape_test.set_scale(0.4); - builder_test.insert_back(shape_test, {0.0, 0.0, engine.dimension().length * 0.3}); - - /* Build the surface and insert it into the engine */ - surface_test.name = "nacelle_" + std::to_string(engine.id()); - surface_test.normal = {1.0, 0.0, 0.0}; // Orient the nacelle along the global X direction - surface_test.sections = builder_test.get_result(); - engine.set_nacelle(surface_test); - - surface_engine.name = "engine_" + std::to_string(engine.id()); - surface_engine.normal = {1.0, 0.0, 0.0}; // Orient the nacelle along the global X direction - surface_engine.sections = builder_engine.get_result(); - engine.set_enginegeometry(surface_engine); + // template <EnergyCarrier EC> + // void Default::openrotor_method(Turbofan<EC> &engine) + // { + // /* Initialize the surface and its builder */ + // geom2::MultisectionSurface<geom2::PolygonSection> surface_test{}; + // geom2::MultisectionSurface<geom2::PolygonSection> surface_engine{}; + // geom2::SectionBuilder<geom2::PolygonSection> builder_test{}; + // geom2::SectionBuilder<geom2::PolygonSection> builder_engine{}; + + // /* Get the maximum diameter the nacelle has to fit */ + // const double diameter_inner = std::max( + // { engine.dimension().width, engine.dimension().height, engine.fan().diameter }); + + // /* Create the cross section shape */ + // geom2::PolygonSection shape_test = this->get_section_shape(engine.id()); + // shape_test.set_width(diameter_inner); + // shape_test.set_height(diameter_inner); + + // /* Create the cross section shape */ + // geom2::PolygonSection shape_engine = this->get_section_shape(engine.id()); + // shape_engine.set_width(diameter_inner); + // shape_engine.set_height(diameter_inner); + + // /* + // * Transform the scaled polygon coordinates to + // * absolute values, so that the section can be + // * scaled using the scale factor. + // */ + // shape_test.set_contour(shape_test.get_contour(true)); + // shape_engine.set_contour(shape_engine.get_contour(true)); + + // /* Section 0 */ + // shape_engine.set_scale(0.3); + // builder_engine.insert_back(shape_engine, {0.0, 0.0, 0.0}); + + // /* Section 0 */ + // shape_engine.set_scale(0.4); + // builder_engine.insert_back(shape_engine, {0.0, 0.0, engine.dimension().length * 0.1}); + + // /* Section 0 */ + // shape_engine.set_scale(0.4); + // builder_engine.insert_back(shape_engine, {0.0, 0.0, engine.dimension().length * 0.2}); + + // /* Section 0 */ + // shape_engine.set_scale(1.0); + // builder_engine.insert_back(shape_engine, {0.0, 0.0, 0.0}); + + // /* Section 0 */ + // shape_engine.set_scale(1.0); + // builder_engine.insert_back(shape_engine, {0.0, 0.0, engine.dimension().length * 0.001}); + + // /* Section 0 */ + // shape_engine.set_scale(0.4); + // builder_engine.insert_back(shape_engine, {0.0, 0.0, 0.0}); + + // /* Section 0 */ + // shape_engine.set_scale(0.4); + // builder_engine.insert_back(shape_engine, {0.0, 0.0, engine.dimension().length * 0.05}); + + // /* Section 0 */ + // shape_engine.set_scale(0.35); + // builder_engine.insert_back(shape_engine, {0.0, 0.0, engine.dimension().length * 0.05}); + + // shape_test.set_scale(0.0); + // builder_test.insert_back(shape_test, {0.0, 0.0, 0}); + + // shape_test.set_scale(0.0); + // builder_test.insert_back(shape_test, {0.0, 0.0, engine.dimension().length * 0.401}); + + // shape_test.set_scale(0.4); + // builder_test.insert_back(shape_test, {0.0, 0.0, engine.dimension().length * 0.0}); + + // shape_test.set_scale(0.4); + // builder_test.insert_back(shape_test, {0.0, 0.0, engine.dimension().length * 0.05}); + + // shape_test.set_scale(1.0); + // builder_test.insert_back(shape_test, {0.0, 0.0, 0.0}); + + // shape_test.set_scale(1.0); + // builder_test.insert_back(shape_test, {0.0, 0.0, engine.dimension().length * 0.001}); + + // shape_test.set_scale(0.6); + // builder_test.insert_back(shape_test, {0.0, 0.0, 0.0}); + + // shape_test.set_scale(0.6); + // builder_test.insert_back(shape_test, {0.0, 0.0, engine.dimension().length * 0.248}); + + // shape_test.set_scale(0.4); + // builder_test.insert_back(shape_test, {0.0, 0.0, engine.dimension().length * 0.3}); + + // /* Build the surface and insert it into the engine */ + // surface_test.name = "nacelle_" + std::to_string(engine.id()); + // surface_test.normal = {1.0, 0.0, 0.0}; // Orient the nacelle along the global X direction + // surface_test.sections = builder_test.get_result(); + // engine.set_nacelle(surface_test); + + // surface_engine.name = "engine_" + std::to_string(engine.id()); + // surface_engine.normal = {1.0, 0.0, 0.0}; // Orient the nacelle along the global X direction + // surface_engine.sections = builder_engine.get_result(); + // engine.set_enginegeometry(surface_engine); - } + // } void Default::operator()(Turbofan<EnergyCarrier::Kerosene>& engine) { - openrotor_method(engine); + turbofan_method(engine); }; + // void Default::operator()(Openrotorfan<EnergyCarrier::Kerosene>& engine) + // { + // openrotor_method(engine); + // }; + void Default::operator()(Turbofan<EnergyCarrier::Liquid_Hydrogen>& engine) { turbofan_method(engine); diff --git a/propulsion_design/src/propulsion.h b/propulsion_design/src/propulsion.h index b1243cc49ddb5d5e80f711951def06060df23531..161832e5ef706b65dcf672c2717b650b9992a00b 100644 --- a/propulsion_design/src/propulsion.h +++ b/propulsion_design/src/propulsion.h @@ -636,6 +636,41 @@ class Turboprop : public Propulsion<carrier> ~Turboprop() override = default; }; +/** + * @class Openrotorfan + * @brief Propulsion specialization for turbofan engines. + * + * @tparam carrier The energy carrier of the propulsion. + */ +template <EnergyCarrier carrier> +class Openrotorfan : public Propulsion<carrier> +{ + public: + Openrotorfan() = default; + explicit Openrotorfan(const int id) : Propulsion<carrier>(id) {} + Openrotorfan(const int id, const ParentComponent parent) : Propulsion<carrier>(id, parent) {} + Openrotorfan(const int id, const ParentComponent parent, const double required_thrust) : Propulsion<carrier>(id, parent, required_thrust) {} + Openrotorfan(const int id, const ParentComponent parent, const double required_thrust, const Offtakes &required_offtakes) : Propulsion<carrier>(id, parent, required_thrust, required_offtakes) {} + Openrotorfan(const Openrotorfan &other) = delete; + Openrotorfan(Openrotorfan &&other) = default; + auto operator=(const Openrotorfan &other) -> Openrotorfan & = delete; + auto operator=(Openrotorfan &&other) -> Openrotorfan & = default; + ~Openrotorfan() override = default; + + /* === Getters === */ + [[nodiscard]] auto bypass_ratio() const -> double { return this->bypass_ratio_; } + [[nodiscard]] auto fan() const -> Fan { return this->fan_; } + + /* === Setters === */ + void set_bypass_ratio(const double bypass_ratio) noexcept { this->bypass_ratio_ = bypass_ratio; } + void set_fan(const Fan fan) noexcept { this->fan_ = fan; } + + private: + /* === Properties === */ + Fan fan_{}; /** [-] The fan of the propulsion. */ + double bypass_ratio_{0.0}; /** [-] The bypass ratio of the propulsion. */ +}; + /* === Type Aliases === */ /** * @brief The type alias for the different propulsion types used @@ -644,6 +679,7 @@ class Turboprop : public Propulsion<carrier> using PropulsionType = std::variant< Turbofan<EnergyCarrier::Kerosene>, Turbofan<EnergyCarrier::Liquid_Hydrogen>, + Openrotorfan<EnergyCarrier::Kerosene>, Turboprop<EnergyCarrier::Kerosene>, Turboprop<EnergyCarrier::Liquid_Hydrogen>>; diff --git a/propulsion_design/src/propulsion_strategy.h b/propulsion_design/src/propulsion_strategy.h index 6fb765ac9c563131795d700ac70385cb596d21af..289da145f7300f184d079970a2c1861842d0c358 100644 --- a/propulsion_design/src/propulsion_strategy.h +++ b/propulsion_design/src/propulsion_strategy.h @@ -72,6 +72,10 @@ class PropulsionStrategy : public Strategy { throw std::invalid_argument("The strategy does not yet implement a liquid hydrogen turboprop engine."); }; + virtual void operator()(Openrotorfan<EnergyCarrier::Kerosene> &engine) // NOLINT runtime/reference + { + throw std::invalid_argument("The strategy does not yet implement a kerosene openrotorfan engine."); + }; /* === Methods === */ /** diff --git a/propulsion_design/src/pylon/default/default_pylon.h b/propulsion_design/src/pylon/default/default_pylon.h index 473b93d40cada88649f2c5a419cc018f73b12692..e9b249628d8efa5099b49ff54c55c527fab9b908 100644 --- a/propulsion_design/src/pylon/default/default_pylon.h +++ b/propulsion_design/src/pylon/default/default_pylon.h @@ -57,17 +57,21 @@ namespace geometry /* === Methods === */ /** - * @brief Design the geometry of a turbofan pylon + * @brief Design the geometry of a turbofan and openrotorfan pylon * with kerosene as fuel. * * @param engine The engine to design the geometry for. */ void operator()(Turbofan<EnergyCarrier::Kerosene> &engine); void operator()(Turbofan<EnergyCarrier::Liquid_Hydrogen> &engine); + void operator()(Openrotorfan<EnergyCarrier::Kerosene> &engine); template <EnergyCarrier EC> void turbofan_method(Turbofan<EC> &engine); + template <EnergyCarrier EC> + void openrotorfan_method(Openrotorfan<EC> &engine); + /** * @brief Initialize the default pylon geometry designer. */ diff --git a/propulsion_design/src/pylon/default/openrotorfan.cpp b/propulsion_design/src/pylon/default/openrotorfan.cpp new file mode 100644 index 0000000000000000000000000000000000000000..213be86bce785408c54bacd5113f02f01a63a197 --- /dev/null +++ b/propulsion_design/src/pylon/default/openrotorfan.cpp @@ -0,0 +1,95 @@ +/* + * UNICADO - UNIversity Conceptual Aircraft Design and Optimization + * + * Copyright (C) 2025 UNICADO consortium + * + * 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/>. + * + * Description: + * This file is part of UNICADO. + */ + +/* === Includes === */ +#include "default_pylon.h" +#include <aircraftGeometry2/io/dat.h> +#include <aircraftGeometry2/processing/measure.h> +#include <aircraftGeometry2/processing/transform.h> +#include <cmath> +#include <set> +#include <utility> + +/* === Design implementations === */ +namespace geometry +{ + namespace pylon + { + template <EnergyCarrier EC> + void Default::openrotorfan_method(Openrotorfan<EC> &engine) + { + /* Check whether a pylon can be designed */ + if ( + std::get<Vertical>(engine.parent_component()) == Vertical::In || + !this->aircraft() + ) + { + return; + } + + /* Get the start and end points of pylon */ + const auto attachment = this->get_attachment_points( + engine.position(), engine.nacelle(), engine.parent_component()); + + /* Get profile of each section */ + auto profile = this->get_section_profile(engine.id()); + + /* Set the global location and orientation of the pylon */ + geometry::Pylon pylon{}; + pylon.origin = CGAL::ORIGIN; + pylon.normal = (engine.position() - attachment.first).direction(); + + /* Set the pylon name */ + pylon.name = "pylon_" + std::to_string(engine.id()); + + /* + * The offset of the engine to the origin since + * the pylon origin relative to the position of + * the second segment of the nacelle. Therefore, + * the pylon is also elevated to the second of + * the nacelle height. + */ + const auto offset_start = engine.position() - CGAL::ORIGIN - geom2::Vector_3( + engine.dimension().length * 0.25, 0, (engine.dimension().height)*0.21/2); + const auto offset_end = engine.position() - CGAL::ORIGIN; + + /* Section at nacelle attachment */ + pylon.sections.emplace_back(profile); + pylon.sections.back().origin = geom2::transform::to_local(pylon, attachment.first - offset_start); + pylon.sections.back().set_chord_length(engine.dimension().length); + + /* Section attaching to the parent component */ + pylon.sections.emplace_back(profile); + pylon.sections.back().origin = geom2::transform::to_local(pylon, attachment.second -offset_end); + pylon.sections.back().set_chord_length(engine.dimension().length); + + /* Add the pylon to the engine */ + engine.set_pylon(pylon); + } + + void Default::operator()(Openrotorfan<EnergyCarrier::Kerosene> &engine) + { + openrotorfan_method(engine); + }; + + } // namespace pylon +} // namespace geometry diff --git a/propulsion_design/src/utility.cpp b/propulsion_design/src/utility.cpp index 66d94591e2870770572eed9306c408faf53fcac0..d6be3d77ef20b30176726d32405dec5668bdd06f 100644 --- a/propulsion_design/src/utility.cpp +++ b/propulsion_design/src/utility.cpp @@ -233,6 +233,16 @@ namespace utility throw std::runtime_error("The energy carrier is not supported for a turboprop engine!"); } } + else if ((power_type + thrust_type) == "openrotorfan") + { + switch (energy_carrier) + { + case EnergyCarrier::Kerosene: + return Openrotorfan<EnergyCarrier::Kerosene>{id, position, design_thrust, engine_offtakes}; + default: + throw std::runtime_error("The energy carrier is not supported for a openrotor engine!"); + } + } else { throw std::runtime_error("The powertrain '" + power_type + "' + '" + thrust_type + "' is not supported.");