diff --git a/classes/@Rocket/Rocket.m b/classes/@Rocket/Rocket.m new file mode 100644 index 0000000000000000000000000000000000000000..61d7be1f25676c96bd1604232b6c8e6f1264ffb5 --- /dev/null +++ b/classes/@Rocket/Rocket.m @@ -0,0 +1,226 @@ +classdef Rocket < Config + % Rocket: Contains and manages all bays + % + % Constructor: + % - Rocket: Creates an instance of the Rocket class. + % Loaded config: rocketConfig.m > rocket (for overrides) + % Loaded data: - + % Arguments: + % - mission: Mission, mission object + % - varIn: (optional) config source. Alternative to config.m + % file + + %% Derived properties + properties(SetAccess = private) + time double % [s] Timestamps vector + cutoffTime (1, 1) double % [s] Shutdown time + cutoffMass (1, 1) double % [m] Total mass at motor cutoff + cutoffInertia (3, 1) double % [kg*m^2] Total inertia at motor cutoff + inertiaDot (3, :) double % [kg*m^2/s]Total inertia time derivative + + stagesMass double % [kg] Mass of stage 2, 3, .. without chutes + crossSection (1, 1) double % [m^2] Rocket cross sectional area + end + + properties(Dependent) + %% BAYS + parafoil Parafoil % [-] Parafoil bay + recovery Recovery % [-] Recovery bay + electronics Electronics % [-] Electronics bay + airbrakes Airbrakes % [-] Airbrakes bay + motor Motor % [-] Motor bay + rear Rear % [-] Rear bay + + %% GEOMETRY + length (1, 1) double % [m] Total length + lengthCenter (1, 1) double % [m] Center length - no nose, boat + lengthCenterNoMotor (1, 1) double % [m] Center length - no nose, boat, motor + diameter (1, 1) double % [m] Diameter + + %% MASS PROPERTIES + mass (1, :) double % [kg] Total mass + massNoMotor (1, 1) double % [kg] Mass without motor + inertia (3, :) double % [kg*m^2] Total inertia - Axibody reference + inertiaNoMotor (3, 1) double % [kg*m^2] Inertia without motor + xcg (1, :) double % [m] Total xCg + xcgNoMotor (1, 1) double % [m] xCg without motor + end + + properties + pitot Pitot % [-] Pitot component + parachutes cell % [-] (nParachutes, nStages) Parachutes onboard + + coefficients struct % [-] Aerodynamic coefficients + end + + properties(Access = private) + bays string % [-] Ordered list of bay names + baysNoMotor string % [-] Ordered list of bay names, without motor + bayStatus dictionary % [-] Dictionary containing initialisation flag for bays + overrides dictionary % [-] Dictionary containing overrides + mission Mission + end + + %% Cached properties + properties(Access = private) + PARAFOIL + RECOVERY + ELECTRONICS + AIRBRAKES + MOTOR + REAR + + LENGTH + LENGTH_CENTER + LENGTH_CENTER_NO_MOTOR + DIAMETER + + MASS + MASS_NO_MOTOR + + INERTIA + INERTIA_NO_MOTOR + + XCG + XCG_NO_MOTOR + end + + %% Config implementation + properties(Constant, Hidden, Access = protected) + configName = 'rocketConfig.m' + variableName = '' + end + + methods + function obj = Rocket(mission, varsIn, options) + arguments + mission Mission = Mission() + varsIn struct = [] + options.loadCoefficients logical = true + options.checkGeometry logical = true + end + obj@Config(mission, varsIn); + if isempty(mission.name), return; end + + %% Initialize base properties + bayNames = camel2UpperSnake(obj.getProperties(Superclass='Bay')); + bayStatus = cellstr(bayNames); + [bayStatus{2, :}] = deal(false); + obj.bayStatus = dictionary(bayStatus{:}); + obj.bays = bayNames; + obj.baysNoMotor = setdiff(bayNames, "MOTOR", 'stable'); + + % Remove bays from overridables, as they do not require override + overridables = camel2UpperSnake( ... + obj.getProperties('Dependent', true, 'SetAccess', 'public')); + overridables = cellstr(setdiff(overridables, bayNames)); + [overridables{2, :}] = deal(false); + obj.overrides = dictionary(overridables{:}); + + %% Loading data + if isempty(varsIn) + varsIn = obj.getConfig(mission.configPath); + end + + obj.parafoil = Parafoil(mission, varsIn); + obj.recovery = Recovery(mission, varsIn); + obj.electronics = Electronics(mission, varsIn); + obj.motor = Motor(mission, varsIn); + obj.airbrakes = Airbrakes(mission, varsIn); + obj.rear = Rear(mission, varsIn); + + obj.pitot = Pitot(mission, varsIn); + paraLoader = ParaLoader(mission); + obj.parachutes = paraLoader.parachutes; + + if options.loadCoefficients + obj.loadData(); + answer = ''; + if isempty(obj.coefficients) || isempty(obj.coefficientsHighAOA) + answer = questdlg(['Coefficient matrices not found. ' ... + 'Do you want to create new matrices?']); + elseif options.checkGeometry && ~all(obj.checkGeometry()) + answer = questdlg(['Coefficient matrices differ from rocket geometry. ' ... + 'Do you want to create new matrices?']); + end + + switch answer + case 'Yes' + parserPath = fullfile(mission.msaPath, 'autoMatricesProtub'); + addpath(genpath(parserPath)); + mainAutoMatProtub(obj); + obj.loadData(); + case 'Cancel' + error('Rocket creation aborted') + otherwise + end + end + end + end + + methods + [coeffsValues, angle0] = interpCoeffs(obj, t, alpha, mach, beta, alt, c) + checks = checkGeometry(obj) + img = plot(obj) + end + + methods(Access = private) + obj = loadData(obj) + obj = updateBay(obj, bayName, value) + obj = updateNoMotor(obj); + obj = update(obj); + end + + methods + function obj = set.parafoil(obj, value), obj = obj.updateBay('PARAFOIL', value); end + function obj = set.recovery(obj, value), obj = obj.updateBay('RECOVERY', value); end + function obj = set.electronics(obj, value), obj = obj.updateBay('ELECTRONICS', value); end + function obj = set.airbrakes(obj, value), obj = obj.updateBay('AIRBRAKES', value); end + function obj = set.motor(obj, value) + obj = obj.updateBay('MOTOR', value); + obj.time = value.time; + obj.cutoffTime = value.cutoffTime; + end + function obj = set.rear(obj, value), obj = obj.updateBay('REAR', value); end + + + function obj = set.length(obj, value), obj = obj.updateOverridable('LENGTH', value); end + function obj = set.lengthCenter(obj, value), obj = obj.updateOverridable('LENGTH_CENTER', value); end + function obj = set.lengthCenterNoMotor(obj, value), obj = obj.updateOverridable('LENGTH_CENTER_NO_MOTOR', value); end + function obj = set.diameter(obj, value) + obj.DIAMETER = value; + obj.crossSection = 0.25*pi*value^2; + end + + function obj = set.mass(obj, value), obj = obj.updateOverridable('MASS', value); end + function obj = set.massNoMotor(obj, value), obj = obj.updateOverridable('MASS_NO_MOTOR', value); end + function obj = set.inertia(obj, value), obj = obj.updateOverridable('INERTIA', value); end + function obj = set.inertiaNoMotor(obj, value), obj = obj.updateOverridable('INERTIA_NO_MOTOR', value); end + function obj = set.xcg(obj, value), obj = obj.updateOverridable('XCG', value); end + function obj = set.xcgNoMotor(obj, value), obj = obj.updateOverridable('XCG_NO_MOTOR', value); end + end + + methods + function value = get.parafoil(obj), value = obj.PARAFOIL; end + function value = get.recovery(obj), value = obj.RECOVERY; end + function value = get.electronics(obj), value = obj.ELECTRONICS; end + function value = get.airbrakes(obj), value = obj.AIRBRAKES; end + function value = get.motor(obj), value = obj.MOTOR; end + function value = get.rear(obj), value = obj.REAR; end + + function value = get.length(obj), value = obj.LENGTH; end + function value = get.lengthCenter(obj), value = obj.LENGTH_CENTER; end + function value = get.lengthCenterNoMotor(obj), value = obj.LENGTH_CENTER_NO_MOTOR; end + function value = get.diameter(obj), value = obj.DIAMETER; end + + function value = get.mass(obj), value = obj.MASS; end + function value = get.massNoMotor(obj) + value = obj.MASS_NO_MOTOR; + end + function value = get.inertia(obj), value = obj.INERTIA; end + + function value = get.inertiaNoMotor(obj), value = obj.INERTIA_NO_MOTOR; end + function value = get.xcg(obj), value = obj.XCG; end + function value = get.xcgNoMotor(obj), value = obj.XCG_NO_MOTOR; end + end +end \ No newline at end of file diff --git a/classes/@Rocket/checkGeometry.m b/classes/@Rocket/checkGeometry.m new file mode 100644 index 0000000000000000000000000000000000000000..95ce79caa9f4ce858921943986cf6d60914ca6fc --- /dev/null +++ b/classes/@Rocket/checkGeometry.m @@ -0,0 +1,83 @@ +function checks = checkGeometry(obj) + % checkGeometry - This methods checks if the rocket geometry + % is consistent with the geometry of the + % aerodynamic matrices + % + % OUTPUTS: + % - checks (n fields of geometry, 1): boolean value of the geometry checks + + xCgRocket = round([ ... + obj.xCg(1); ... + obj.xCg(end) ... + ], 3); + + xCgTest = round([ + obj.coefficients.geometry.xcg(1); + obj.coefficients.geometry.xcg(end); + ], 3); + + if (obj.parafoil.noseCMod & obj.parafoil.nosePMod) == 0 % KARMAN ogive case, no modified p and c coefficients + geometryRocket = round([ + obj.diameter; + obj.parafoil.noseLength; + obj.lengthCenter; + obj.rear.finsRootChord; + obj.rear.finsFreeChord; + obj.rear.finsHeight; + obj.rear.finsDeltaXFreeChord; + obj.rear.nPanel; + obj.rear.boatLength; + obj.rear.boatFinalDiameter; + ], 3); + + geometryTest = round([ + obj.coefficients.geometry.diameter; + obj.coefficients.geometry.lNose; + obj.coefficients.geometry.lCenter; + obj.coefficients.geometry.chord1; + obj.coefficients.geometry.chord2; + obj.coefficients.geometry.height; + obj.coefficients.geometry.deltaXLE; + obj.coefficients.geometry.nPanel; + obj.coefficients.geometry.boatL; + obj.coefficients.geometry.boatD; + ], 3); + else % MHAAK ogive case, modified p and c coefficients + geometryRocket = round([ + obj.diameter; + obj.parafoil.noseLength; + obj.lengthCenter; + obj.parafoil.noseCMod; + obj.parafoil.nosePMod; + obj.rear.finsRootChord; + obj.rear.finsFreeChord; + obj.rear.finsHeight; + obj.rear.finsDeltaXFreeChord; + obj.rear.nPanel; + obj.rear.boatLength; + obj.rear.boatFinalDiameter; + ], 3); + + geometryTest = round([ + obj.coefficients.geometry.diameter; + obj.coefficients.geometry.lNose; + obj.coefficients.geometry.lCenter; + obj.coefficients.geometry.cMod; + obj.coefficients.geometry.pMod; + obj.coefficients.geometry.chord1; + obj.coefficients.geometry.chord2; + obj.coefficients.geometry.height; + obj.coefficients.geometry.deltaXLE; + obj.coefficients.geometry.nPanel; + obj.coefficients.geometry.boatL; + obj.coefficients.geometry.boatD; + ], 3); + end + + checks = [ + xCgRocket == xCgTest; + strcmp(obj.coefficients.geometry.ogType, obj.parafoil.noseType); + geometryRocket == geometryTest; + strcmp(obj.coefficients.geometry.boatType, obj.rear.boatType) + ]; +end \ No newline at end of file diff --git a/classes/@Rocket/interpCoeffs.m b/classes/@Rocket/interpCoeffs.m new file mode 100644 index 0000000000000000000000000000000000000000..717934a3fb9a0cd0a39823a199f29a9fa22490bd --- /dev/null +++ b/classes/@Rocket/interpCoeffs.m @@ -0,0 +1,63 @@ +function [coeffsValues, angle0] = interpCoeffs(obj, t, alpha, mach, beta, alt, c) +% interpCoeffs - interpolation of aerodynamic coefficients. +% +% INPUTS: +% - t, double [1,1], integration time, [s]; +% - alpha, double[1,1], angle of attack, []; +% - mach, double[1,1], mach number, []; +% - beta, double[1,1], sideslip angle, []; +% - alt, double[1,1], altitude, [m]; +% - c, double[1,1], aerobrakes control variable, []; +% +% OUTPUTS: +% - coeffsValues, array [16,1], aerodynamic coefficients; +% - angle0, array [2,1], array of the reference aerodynamic angles. + +if mach > obj.airbrakes.maxMach + c = 0; +end +h = c*obj.coefficients.state.hprot(end); + +[nearAlpha, indexAlpha] = nearestValSorted(obj.coefficients.state.alphas*pi/180, alpha); +[~, indexMach] = nearestValSorted(obj.coefficients.state.machs, mach); +[nearBeta, indexBeta] = nearestValSorted(obj.coefficients.state.betas*pi/180, beta); +[~, indexAlt] = nearestValSorted(obj.coefficients.state.altitudes, alt); +[~, indexControl] = nearestValSorted(obj.coefficients.state.hprot, h); + +angle0 = [nearAlpha; nearBeta]; + +if t >= obj.motor.cutoffTime + % Interpolate on airbrake control + % Uses end xCg, even if tCutOff < tBurnTime + num = length(obj.coefficients.state.hprot); + index1 = indexControl; + index2 = indexControl + 1; + if index2 > num + if num == 1 + coeffsValues = obj.coefficients(:, indexAlpha, indexMach, indexBeta, indexAlt, 1, end); + return; + else + index2 = index2 - 1; + index1 = index1 - 1; + end + end + + VF = obj.coefficients.total(:, indexAlpha, indexMach, indexBeta, indexAlt, index1, end); + VE = obj.coefficients.total(:, indexAlpha, indexMach, indexBeta, indexAlt, index2, end); + + deltaH = obj.coefficients.state.hprot(index2) - obj.coefficients.state.hprot(index1); + coeffsValues = ( (h - obj.coefficients.state.hprot(index1))/deltaH)*(VE - VF) + VF; +else + % Interpolate on xCg, close airbrakes + num = 1:1:length(obj.coefficients.state.xcgTime)-1; + indexXcg = t >= obj.coefficients.state.xcgTime(1:end-1) & t < obj.coefficients.state.xcgTime(2:end); + index1 = num(indexXcg); + index2 = index1 + 1; + + VF = obj.coefficients.total(:, indexAlpha, indexMach, indexBeta, indexAlt, indexControl, index1); + VE = obj.coefficients.total(:, indexAlpha, indexMach, indexBeta, indexAlt, indexControl, index2); + + deltaT = obj.coefficients.state.xcgTime(index2) - obj.coefficients.state.xcgTime(index1); + coeffsValues = ( (t - obj.coefficients.state.xcgTime(index1))/deltaT)*(VE - VF) + VF; +end +end \ No newline at end of file diff --git a/classes/@Rocket/loadData.m b/classes/@Rocket/loadData.m new file mode 100644 index 0000000000000000000000000000000000000000..78b4e6d8bbcc3d16d245183558059641baca8036 --- /dev/null +++ b/classes/@Rocket/loadData.m @@ -0,0 +1,40 @@ +function obj = loadData(obj) +if isempty(obj.motor) + return; +end + +varNames = {'total', 'geometry', 'state', 'finsCN'}; +motorName = obj.motor.name; +aeroPath = fullfile(obj.mission.dataPath, 'aeroCoefficients.mat'); +aeroHighAOAPath = fullfile(obj.mission.dataPath, 'aeroCoefficientsHighAOA.mat'); + +if ~(exist(aeroPath, 'file') && exist(aeroHighAOAPath,'file')) + return; +end +dataCoeffs = load(aeroPath); +dataCoeffsHighAOA = load(aeroHighAOAPath); + +if isfield(dataCoeffs, motorName) && isfield(dataCoeffsHighAOA, motorName) + dataCoeffs = dataCoeffs.(motorName); + dataCoeffsHighAOA = dataCoeffsHighAOA.(motorName); +else + return; +end + +if isfield(dataCoeffs, "finsCN") && ... + isfield(dataCoeffsHighAOA, "finsCN") + obj.coefficients.finsCN = dataCoeffs.finsCN; + obj.coefficientsHighAOA.finsCN = dataCoeffsHighAOA.finsCN; +end + +varNames = varNames{1:end - 1}; +if all(and(isfield(dataCoeffs, varNames), ... + isfield(dataCoeffsHighAOA, varNames))) + obj.coefficients.total = dataCoeffs.total; + obj.coefficientsHighAOA.total = dataCoeffsHighAOA.total; + obj.coefficients.geometry = dataCoeffs.geometry; + obj.coefficientsHighAOA.geometry = dataCoeffsHighAOA.geometry; + obj.coefficients.state = dataCoeffs.state; + obj.coefficientsHighAOA.state = dataCoeffsHighAOA.state; +end +end \ No newline at end of file diff --git a/classes/@Rocket/plot.m b/classes/@Rocket/plot.m new file mode 100644 index 0000000000000000000000000000000000000000..7b87f9326de7648497d2c15b4169880b77e44315 --- /dev/null +++ b/classes/@Rocket/plot.m @@ -0,0 +1,116 @@ +function img = plot(obj) +arguments + obj Rocket +end + +deltaXLE = obj.rear.finsDeltaXFreeChord; +r = obj.diameter/2; +height = obj.rear.finsHeight; +C1 = obj.rear.finsRootChord; +C2 = obj.rear.finsFreeChord; + +Lcent = obj.lengthCenter; +Lnos = obj.parafoil.noseLength; + +Xle1 = Lcent + Lnos - obj.rear.finsAxialDistance - C1; + +Daft = obj.rear.boatFinalDiameter; +Laft = obj.rear.boatLength; + +xNos = Lnos - Lnos*cos(linspace(0, pi/2, 50)); +if strcmp(obj.parafoil.noseType, 'KARMAN') + theta = @(x) acos( 1 - ( (2*x)./Lnos ) ); + Karman = @(x) ( r/sqrt(pi) ) * sqrt( theta(x) - ( sin(2*theta(x))./ 2 ) ); + yNos = Karman(xNos); +elseif strcmp(obj.parafoil.noseType, 'HAACK') + theta = @(x) acos( 1 - ( (2*x)./Lnos ) ); + Haack = @(x) ( r/sqrt(pi) ) * sqrt( theta(x) - ( sin(2*theta(x))./ 2 ) + (1/3)*sin(theta(x)).^3 ); + yNos = Haack(xNos); +elseif strcmp(obj.parafoil.noseType, 'OGIVE') + rho = (r^2 + Lnos^2)/(2*r); + Ogive = @(x) sqrt(rho^2 - (Lnos - x).^2) + r - rho; + yNos = Ogive(xNos); +elseif strcmp(obj.parafoil.noseType, 'POWER') + power = obj.parafoil.nosePower; + Power = @(x) r * (x/Lnos).^(power); + yNos = Power(xNos); +elseif strcmp(obj.parafoil.noseType, 'MHAACK') + p = obj.parafoil.nosePMod; + c = obj.parafoil.noseCMod; + xMod = @(x, p) (x/Lnos).^(p)*Lnos; + thetaMod = @(x, p) acos( 1 - ( (2*xMod(x, p))./Lnos ) ); + haackSeriesMod = @(x, p, C) ( r/sqrt(pi) ) * sqrt( thetaMod(x, p) ... + - ( sin(2*thetaMod(x, p))./ 2 ) + C*sin(thetaMod(x, p)).^3 ); + yNos = haackSeriesMod(xNos, p, c); +end + +if strcmp(obj.rear.boatType, 'OGIVE') % [-] Boat-tail shape. 0: Cone, 1: Tangent Ogive + [xBoat, yBoat] = computeTangentBoatPoints(2*r, Daft, Laft); +else + xBoat = [0 Laft]; + yBoat = [r Daft/2]; +end + +%%% figure begin +img = figure(); +hold on + +%%% NOSECONE +plot(xNos, yNos, 'k'); +plot(xNos, -yNos, 'k'); +plot([Lnos Lnos], [-r r], 'k'); + +%%% CENTERBODY +plot([Lnos Lnos+Lcent], [r r], 'k'); +plot([Lnos Lnos+Lcent], [-r -r], 'k'); +plot([Lnos+Lcent Lnos+Lcent], [-r r], 'k'); + +%%% BOAT-TAIL PLOT +plot(xBoat+Lnos+Lcent, yBoat, 'k'); +plot(xBoat+Lnos+Lcent, -yBoat, 'k'); +plot([Lnos+Lcent+Laft Lnos+Lcent+Laft], [-Daft/2 Daft/2], 'k'); + +%%% FINS PLOT +% top +plot([Xle1 Xle1+deltaXLE], [r r+height],'k'); +plot([Xle1+C1 Xle1+deltaXLE+C2], [r r+height],'k'); +plot([Xle1+deltaXLE Xle1+deltaXLE+C2], [r+height r+height],'k'); +plot([Xle1 Xle1+C1], [r r], 'k'); +% bottom +plot([Xle1 Xle1+deltaXLE], [-r -r-height],'k'); +plot([Xle1+C1 Xle1+deltaXLE+C2], [-r -r-height],'k'); +plot([Xle1+deltaXLE Xle1+deltaXLE+C2], [-r-height, -r-height],'k'); +plot([Xle1 Xle1+C1], [-r -r], 'k'); + +% plot([Xle1 Xle1+deltaXLE], [r+height r+height], 'k--') +% plot([Xle1+deltaXLE Xle1+deltaXLE], [r+height r],'k--') + +%%% BAYS +lengths = obj.absolutePositions.values + 2*Lnos; +baysBody = obj.absolutePositions.keys; +iMotor = find(strcmp(obj.absolutePositions.keys,"motor")); +xline(0,'r-.', 'Label', 'NOSE', ... + 'LabelOrientation', 'aligned', 'LabelVerticalAlignment', 'bottom', ... + 'LabelHorizontalAlignment', 'right'); +for i = 1:length(lengths) + if i == iMotor + line([lengths(i) lengths(i)], [-1 1], 'LineStyle', '--', 'Color', 'b'); % Draw the line + text(lengths(i), -1 + 0.3, baysBody(i), 'VerticalAlignment', 'top', ... + 'HorizontalAlignment', 'right', 'Rotation', 90, 'Color', 'b'); + else + line([lengths(i) lengths(i)], [-1 1], 'LineStyle', '--', 'Color', 'k'); % Draw the line + text(lengths(i), 1 + 0.1, baysBody(i), 'VerticalAlignment', 'top', ... + 'HorizontalAlignment', 'right', 'Rotation', 90); + end +end +boatPlotLength = obj.length - obj.rear.boatLength; +xline(boatPlotLength,'r-.', 'Label', 'BOAT-TAIL', ... + 'LabelOrientation', 'aligned', 'LabelVerticalAlignment', 'bottom', ... + 'LabelHorizontalAlignment', 'right'); + +axis equal +% set(gca, 'xlim', [-100, Lnos+Lcent+Laft+100]); + +%%% TITLE +title(string(strrep(obj.mission.name, '_', ' '))); +end diff --git a/classes/@Rocket/update.m b/classes/@Rocket/update.m new file mode 100644 index 0000000000000000000000000000000000000000..4400d73971f1171aa8da36f1e1899ddd58a1c526 --- /dev/null +++ b/classes/@Rocket/update.m @@ -0,0 +1,45 @@ +function obj = update(obj) +% Updates all properties without motor + +override = obj.overrides; + +%% Geometry +if ~override('LENGTH') + obj.LENGTH = obj.rear.position + obj.rear.length + obj.parafoil.noseLength; +end + +if ~override('LENGTH_CENTER') + obj.LENGTH_CENTER = obj.length - obj.parafoil.noseLength - obj.rear.boatLength; +end + +%% Mass +if ~override('MASS') + obj.MASS = obj.massNoMotor + obj.motor.mass; +end + +if ~override('XCG') + obj.XCG = (obj.xcgNoMotor*obj.massNoMotor + ... + obj.motor.mass.*(obj.motor.xcg+obj.lengthCenterNoMotor))./ ... + obj.mass; +end + +if ~override('INERTIA') + % [3xN] Ix, Iy, Iz + % Assumption: xCgs are close to rocket axis + motorInertia = obj.motor.inertia(2:3, :) + ... + (obj.xcg - (obj.motor.position + obj.motor.xcg)).^2 .* obj.motor.mass; + baysInertia = obj.inertiaNoMotor(2:3, :); + + obj.inertia = [obj.motor.inertia(1, :) + obj.inertiaNoMotor(1, :); + motorInertia + baysInertia]; + + obj.inertiaDot = diff(obj.inertia, 1, 2)./diff(obj.motor.time); + obj.inertiaDot(:, end + 1) = obj.inertiaDot(:, end); +end + +%% Cutoff +obj.cutoffMass = interp1(obj.time, obj.mass, obj.cutoffTime, "linear", 'extrap'); +obj.cutoffInertia = interp1(obj.time, obj.inertia', obj.cutoffTime, "linear", 'extrap')'; + +end + diff --git a/classes/@Rocket/updateBay.m b/classes/@Rocket/updateBay.m new file mode 100644 index 0000000000000000000000000000000000000000..4fb11c5f836abec619d623cb3da2045213d89b61 --- /dev/null +++ b/classes/@Rocket/updateBay.m @@ -0,0 +1,18 @@ +function obj = updateBay(obj, bayName, value) +obj.(bayName) = value; + +obj.bayStatus(bayName) = true; +if ~all(values(obj.bayStatus)), return; end +bays = obj.bays; + +%% Update positions +obj.(bays(1)).position = 0; +for i = 2:length(bays) + bay = bays(i); + prevBay = bays(i-1); + obj.(bay).position = obj.(prevBay).position + obj.(prevBay).length + obj.(bay).offset; +end + +obj = obj.updateNoMotor(); +obj = obj.update(); +end \ No newline at end of file diff --git a/classes/@Rocket/updateNoMotor.m b/classes/@Rocket/updateNoMotor.m new file mode 100644 index 0000000000000000000000000000000000000000..ae7f461cbee0f6c0dd1351a88236310893b176be --- /dev/null +++ b/classes/@Rocket/updateNoMotor.m @@ -0,0 +1,39 @@ +function obj = updateNoMotor(obj) +% Updates all properties without motor + +override = obj.overrides; +bayNames = obj.baysNoMotor; +nBays = length(bayNames); +bays(nBays) = obj.(bayNames(end)); + +for i = 1:nBays - 1, bays(i) = obj.(bayNames(i)); end + +positions = [bays.position]; +masses = [bays.mass]; +xcgs = positions + [bays.xcg]; +inertias = cat(1, bays.inertia)'; + +massNoMotor = sum([bays.mass]); + +%% Geometry +if ~override('LENGTH_CENTER_NO_MOTOR') + obj.LENGTH_CENTER_NO_MOTOR = obj.motor.position; +end + +%% Mass +if ~override('MASS_NO_MOTOR') + obj.MASS_NO_MOTOR = massNoMotor; +end + +if ~override('XCG_NO_MOTOR') + obj.XCG_NO_MOTOR = (xcgs*masses')/massNoMotor; +end + +if ~override('INERTIA_NO_MOTOR') + % [3x1] Ix, Iy, Iz + % Assumption: xCgs are close to rocket axis + IRaw = inertias(2:3, :) + (obj.xcgNoMotor - xcgs).^2 .* masses; + obj.INERTIA_NO_MOTOR = [sum(inertias(1, :)); sum(IRaw, 2)]; +end +end + diff --git a/classes/@Rocket/updateOverridable.m b/classes/@Rocket/updateOverridable.m new file mode 100644 index 0000000000000000000000000000000000000000..7c0197415d6a0ef146996f2971063a33a0778cac --- /dev/null +++ b/classes/@Rocket/updateOverridable.m @@ -0,0 +1,10 @@ +function obj = updateOverridable(obj, name, value) +obj.(name) = value; +obj.overrides(name) = true; + +if isempty(value) + obj.overrides(name) = false; + obj = obj.updateNoMotor(); + obj = obj.update(); +end +end \ No newline at end of file diff --git a/classes/Bay.m b/classes/Bay.m index beb567f262e799b62d746aac808f6c43e4eccf94..1e143c3215ddefba2b605ca7ce57469bc8bcc570 100644 --- a/classes/Bay.m +++ b/classes/Bay.m @@ -1,19 +1,26 @@ -classdef Bay < Component & matlab.mixin.Heterogeneous +classdef(Abstract) Bay < Config & matlab.mixin.Heterogeneous % Bay: Represents an abstraction layer for all bays % Arrays of bay subclasses will be represented as [mxn Bay] arrays + properties + offset (1, 1) double % [m] Position offset relative to previous bay, default: 0 + length double % [m] Total bay length + end + properties(Abstract) - position % [m] OVERRIDE Position, relative to previous component - - length % [m] Total bay length - mass % [kg] Total bay mass - inertia % [kg*m^2] Total bay inertia (Body reference) - xCg % [m] Cg relative to bay upper side + mass (1, :) double % [kg] Total bay mass + inertia (1, :) double % [kg*m^2] Total bay inertia (Body reference) + xcg (1, :) double % [m] Xcg wrt bay top + end + + properties(SetAccess = ?Rocket) + position double % [m] Absolute position wrt ogive base end - methods (Static, Sealed, Access=protected) - function default = getDefaultScalarElement - default = Parafoil; - end + methods (Static,Sealed,Access = protected) + function obj = getDefaultScalarElement() + obj = Electronics(); + end end end diff --git a/classes/Component.m b/classes/Component.m deleted file mode 100644 index 7407018396451bed304a0490b789b80b86a7ec67..0000000000000000000000000000000000000000 --- a/classes/Component.m +++ /dev/null @@ -1,121 +0,0 @@ -classdef Component < Config & matlab.mixin.Copyable -% Component: Represents an abstraction layer for all components -% Components standardize object construction, config and data loading -% -% Constructor: -% - Component: Creates an instance of the Component class. -% Arguments: -% - mission: Mission, mission object, used to find config -% files -% - varsIn: If present, data is read from this variable -% instead of config file -% -% Options: -% -% - elementWise: -% - if true, data is copied element-by-element -% into target class -% - if false, the loaded data substitutes target class - - properties(Abstract, Access = protected) - mission Mission - end - - methods - function obj = Component(mission, varsIn, options) - % Component: Contains and manages all bays - % - % Constructor: - % - Rocket: Creates an instance of the Rocket class. - % Loaded config: rocketConfig.m > rocket (for overrides) - % Loaded data: - - % Arguments: - % - mission: Mission, mission object - % - varIn: (optional) config source. Alternative to config.m - % file - - arguments (Input) - mission Mission = Mission() - varsIn = [] - options.elementWise logical = true - end - - if isempty(mission.name) - return; - end - - if isempty(varsIn) - varsIn = obj.getConfig(mission); - end - - fileName = obj.configName; - varName = obj.variableName; - if isempty(varName) - varName = strtok(fileName, 'C'); - end - - if ~isfield(varsIn, varName) - error(['Class not found inside the config folder: %s\n' ... - 'With name: %s\n' ... - 'Check that the correct mission is set in your component ' ... - 'and the config file is correct'], fileName, varName); - end - - configObj = varsIn.(varName); - - if options.elementWise - fields = configObj.getProperties('writable'); - for field = fields - if isempty([configObj.(field)]), continue; end - obj.(field) = configObj.(field); - end - else - obj = configObj; - end - - [obj.mission] = deal(mission); - end - end - - methods - function m = getMission(obj) - m = obj.mission; - end - end - - methods(Access = protected) - function cp = copyElement(obj) - fields = obj.getProperties('readable', NonCopyable = 0); - cp = copyElement@matlab.mixin.Copyable(obj); %shallow copy of all elements - - for field = fields - if isa(obj.(field), 'matlab.mixin.Copyable') - cp.(field) = copy(obj.(field)); - end - end - end - - function outputVariables = getConfig(obj, mission) - fileName = obj.configName; - filePath = mission.configPath; - file = fullfile(filePath, fileName); - - if ~isfile(file) - error(['File not found inside the config folder: %s\n' ... - 'Check that the correct mission is set in your component ' ... - 'and the config file exists'], fileName); - end - - variables = [who(); "variables"]; - run(file); - loadedVariables = who(); - loadedVariables(ismember(loadedVariables, variables)) = []; - - outputVariables = struct(); - for i = 1:length(loadedVariables) - outputVariables.(loadedVariables{i}) = eval(loadedVariables{i}); - end - end - end -end - diff --git a/classes/Config.m b/classes/Config.m index cec711a4737ba8e95eb0bc2c54ae6732da94de76..5c7b954c1ea39db7d9e46312d9bb3663dd138ff0 100644 --- a/classes/Config.m +++ b/classes/Config.m @@ -1,16 +1,51 @@ -classdef(Abstract) Config < handle +classdef(Abstract) Config % Config: Represents an abstraction layer for all config-dependent Classes -% Config standardizes property management and declares fundemental -% methods (see doc for more info) +% Config standardizes property management, and config loading and +% declares fundemental methods (see doc for more info) and - - properties(Abstract, Access = protected) + properties(Abstract, Constant, Access = protected) configName {mustBeTextScalar} variableName char end methods + function obj = Config(pathIn, varsIn) + % Component: Contains and manages all bays + % + % Constructor: + % - Rocket: Creates an instance of the Rocket class. + % Loaded config: rocketConfig.m > rocket (for overrides) + % Loaded data: - + % Arguments: + % - filePath: Path containing config file + % - varIn: (optional) config source. Alternative to config.m + % file + + arguments (Input) + pathIn {mustBeA(pathIn, {'char', 'string', 'Mission'})} = '' + varsIn = [] + end + + if isa(pathIn, 'Mission') + path = pathIn.configPath; + else + path = pathIn; + end + + if isempty(path), return; end + if isempty(varsIn) + varsIn = obj.getConfig(path); + end + + obj = obj.loadConfig(varsIn); + end + function structOut = toStruct(obj) + % This function converts the object to a struct + % + % WARNING: A struct does not posses update capabilities and only + % publicly readable fields are copied. Use this method carefully + structOut = struct(); fields = obj.getProperties('readable'); for j = 1:size(fields, 2), structOut.(fields{j}) = obj.(fields{j}); end @@ -31,9 +66,8 @@ classdef(Abstract) Config < handle arguments obj - preset char {mustBeMember(preset, {'readable', 'writable', ''})} = '' + preset char {mustBeMember(preset, {'readable', 'writable', ''})} = '' options.Superclass char = '' - options.Heterogeneous logical = 0 attributes.GetAccess char {mustBeMember(attributes.GetAccess, {'public', 'private', ''})} = '' attributes.SetAccess char {mustBeMember(attributes.SetAccess, {'public', 'private', ''})} = '' attributes.Dependent logical {mustBeMember(attributes.Dependent, [0, 1])} = [] @@ -53,7 +87,6 @@ classdef(Abstract) Config < handle attributes.Hidden = 0; case 'writable' attributes.SetAccess = 'public'; - attributes.Dependent = 0; attributes.Constant = 0; attributes.Abstract = 0; attributes.Hidden = 0; @@ -87,50 +120,57 @@ classdef(Abstract) Config < handle propertiesOut = string(properties(1:ii)); if nargout == 2 - if isempty(options.Superclass) || ~options.Heterogeneous - valuesOut = cell(1, ii); - for i = 1:ii, valuesOut{i} = obj.(propertiesOut(i)); end - else - valuesOut(ii) = obj.(propertiesOut(1)); - for i = 1:ii, valuesOut(i) = obj.(propertiesOut(i)); end - end + valuesOut = cell(1, ii); + for i = 1:ii, valuesOut{i} = obj.(propertiesOut(i)); end end end end - methods(Hidden) - function lh = addlistener(varargin) - lh = addlistener@handle(varargin{:}); - end - function notify(varargin) - notify@handle(varargin{:}); - end - function delete(varargin) - delete@handle(varargin{:}); - end - function Hmatch = findobj(varargin) - Hmatch = findobj@handle(varargin{:}); - end - function p = findprop(varargin) - p = findprop@handle(varargin{:}); - end - function TF = eq(varargin) - TF = eq@handle(varargin{:}); - end - function TF = ne(varargin) - TF = ne@handle(varargin{:}); - end - function TF = lt(varargin) - TF = lt@handle(varargin{:}); - end - function TF = le(varargin) - TF = le@handle(varargin{:}); - end - function TF = gt(varargin) - TF = gt@handle(varargin{:}); + methods(Access = protected) + function outputVariables = getConfig(obj, filePath) + fileName = obj.configName; + file = fullfile(filePath, fileName); + + if ~isfile(file) + error(['File not found inside the config folder: %s\n' ... + 'Check that the correct mission is set in your component ' ... + 'and the config file exists'], fileName); + end + + variables = [who(); "variables"]; + + % try run(file); + % catch ME, throwAsCaller(ME); end + run(file); + loadedVariables = who(); + loadedVariables(ismember(loadedVariables, variables)) = []; + + outputVariables = struct(); + for i = 1:length(loadedVariables) + outputVariables.(loadedVariables{i}) = eval(loadedVariables{i}); + end end - function TF = ge(varargin) - TF = ge@handle(varargin{:}); + + function obj = loadConfig(obj, varsIn) + fileName = obj.configName; + varName = obj.variableName; + if isempty(varName) + varName = strtok(fileName, 'C'); + end + + if ~isfield(varsIn, varName) + error(['Class not found inside the config folder: %s\n' ... + 'With name: %s\n' ... + 'Check that the correct mission is set in your component ' ... + 'and the config file is correct'], fileName, varName); + end + + configObj = varsIn.(varName); + fields = configObj.getProperties('writable'); + for field = fields + if isempty([configObj.(field)]), continue; end + obj.(field) = configObj.(field); + end end - end + end end \ No newline at end of file diff --git a/classes/components/Environment.m b/classes/Environment.m similarity index 100% rename from classes/components/Environment.m rename to classes/Environment.m diff --git a/classes/Mission.m b/classes/Mission.m index 25b1eacfaf872d1f9adc39b94eb550516a2ca167..12cb70f968581a8259100b1fdc7c29d7196c1d7c 100644 --- a/classes/Mission.m +++ b/classes/Mission.m @@ -10,10 +10,14 @@ classdef Mission < Config % - loadConfig: bool, Whether to load config file or return % an empty mission - properties + properties(Dependent) name char % Mission name, used to access <mission> folder end + properties(Access = private) + NAME char % Mission name cache + end + properties(SetAccess = protected) currentPath char configPath char @@ -21,17 +25,20 @@ classdef Mission < Config msaPath char end - properties(Access = protected) + properties(Access = private) + changeMatlabPath (1,1) logical + end + + properties(Constant, Access = protected) configName = 'missionConfig.m' variableName = '' - changeMatlabPath (1,1) logical end methods function obj = Mission(mission, options) arguments mission {mustBeA(mission, {'logical', 'string', 'char'})} = false - options.changeMatlabPath = true; + options.changeMatlabPath = false; end if nargin == 0 @@ -41,16 +48,21 @@ classdef Mission < Config filePath = fullfile(fileparts(mfilename("fullpath")), '..', 'missions'); filePath = trimPath(filePath); - obj.changeMatlabPath = options.changeMatlabPath; obj.currentPath = filePath; + obj.changeMatlabPath = options.changeMatlabPath; if isa(mission, 'char') || isa(mission, 'string') obj.name = mission; else - obj.loadConfig(); + vars = obj.getConfig(filePath); + obj.name = vars.mission.name; end end - function set.name(obj, name) + function name = get.name(obj) + name = obj.NAME; + end + + function obj = set.name(obj, name) if strcmp(name,'design') filePath = fullfile(fileparts(mfilename("fullpath")), '..', 'missions'); filePath = trimPath(filePath); @@ -64,47 +76,34 @@ classdef Mission < Config 'Only one file can be selected at a time.',''}, ... 'SelectionMode','single','ListSize', [250,150], ... 'ListString',names); - obj.name = names{indx}; + obj.NAME = names{indx}; else if ~isempty(obj.currentPath) && ... - ~isfolder(fullfile(obj.currentPath, name)) %#ok<MCSUP> + ~isfolder(fullfile(obj.currentPath, name)) error('Invalid mission: %s', name) end - obj.name = name; + obj.NAME = name; end - obj.updatePath(); + + obj = obj.updatePath(); obj.updateMatlabPath(); end end - methods(Access = protected) - function loadConfig(obj) - fileName = obj.configName; - filePath = obj.currentPath; - - if ~isfile(fullfile(filePath, fileName)) - error(strcat("File not found inside the config folder: ", filePath)); - end - - varName = strtok(fileName,'C'); - run(fileName); - configObj = eval(varName); - obj.name = configObj.name; - end - - function updatePath(obj) + methods(Access = private) + function obj = updatePath(obj) if isempty(obj.name) || isempty(obj.currentPath), return; end obj.configPath = fullfile(obj.currentPath, obj.name, 'config'); obj.dataPath = fullfile(obj.currentPath, obj.name, 'data'); obj.msaPath = trimPath( ... - fullfile(obj.currentPath, '..', '..', '..', 'msa-toolkit') ... - ); + fullfile(obj.currentPath, '..', '..', '..', 'msa-toolkit')); end function updateMatlabPath(obj) if isempty(obj.name) || isempty(obj.currentPath), return; end if ~obj.changeMatlabPath, return; end + matlabPath = path; % Checking if other missions are in path pattern = 'missions\\[^\\/]*;'; % Matches any config folder under mission in path @@ -125,8 +124,4 @@ classdef Mission < Config end end end - - methods(Static, Access = protected) - function loadData(), end - end end \ No newline at end of file diff --git a/classes/Rocket.m b/classes/Rocket.m deleted file mode 100644 index 3a18b2201da10673175704b71007e0cebe4511f7..0000000000000000000000000000000000000000 --- a/classes/Rocket.m +++ /dev/null @@ -1,558 +0,0 @@ -classdef Rocket < Component - % Rocket: Contains and manages all bays - % - % Constructor: - % - Rocket: Creates an instance of the Rocket class. - % Loaded config: rocketConfig.m > rocket (for overrides) - % Loaded data: - - % Arguments: - % - mission: Mission, mission object - % - varIn: (optional) config source. Alternative to config.m - % file - - properties - parafoil Parafoil % [-] Parafoil bay - recovery Recovery % [-] Recovery bay - electronics Electronics % [-] Electronics bay - airbrakes Airbrakes % [-] Airbrakes bay - motor Motor % [-] Motor bay - rear Rear % [-] Rear bay - - pitot Pitot % [-] Pitot component - parachutes Para % [-] (nParachutes, nStages) Parachutes onboard - - length double % [m] Total length - lengthCenter double % [m] Center length - no nose, boat - lengthCenterNoMot double % [m] Center length - no nose, no motor - diameter double % [m] Diameter - mass double % [kg] Total mass - massNoMotor double % [kg] Mass without motor - inertia double % [kg*m^2] Total inertia - Axibody reference - inertiaNoMotor double % [kg*m^2] Inertia without motor - xCg double % [m] Total xCg - xCgNoMotor double % [m] xCg without motor - - stagesMass double % [kg] Mass of stage 2, 3, .. without chutes - - coefficients % [-] Aerodynamic coefficients - coefficientsHighAOA % [-] Aerodynamic coefficients for high angle of attacks - end - - properties(SetAccess = protected) - crossSection % [m^2] Rocket cross sectional area - inertiaDot double % [kg*m^2/s]Total inertia time derivative - cutoffInertia double % [kg*m^2] Total inertia at motor cutoff - cutoffMass double % [m] Total mass at motor cutoff - absolutePositions dictionary ... % [m] Bay start positions - 0 is set at nose base - = dictionary() - bays dictionary ... % [Bay] All bays - = dictionary() - end - - properties(Access = protected, Hidden) - configName = 'rocketConfig.m' - variableName = '' - mission Mission - end - - methods % Updaters - function updateAbsolutePositions(obj) - bDictionay = obj.bays; - b = values(bDictionay); - - if ~isempty(obj.lengthCenterNoMot) - obj.absolutePositions = dictionary(["motor", "rear"], ... - [obj.lengthCenterNoMot, obj.lengthCenterNoMot + obj.rear.position]); - return - end - % Populate shift vectors (how much each bay shifts the next one back) - % Shift overrides are applyed to the previous bay - shift = zeros(size(b)); - overrides = ~cellfun(@isempty, {b.position}); - overridesShift = logical([overrides(2:end), 0]); - - if any(overrides) - shift(overridesShift) = b(overrides).position; - end - - if any(~overrides) - shift(~overridesShift) = [b(~overridesShift).length]; - end - - absPositions = cumsum([-shift(1); shift(1:end-1)]); - absPositions = absPositions + ... - (obj.parafoil.length - obj.parafoil.noseLength); - obj.absolutePositions = dictionary(keys(bDictionay), absPositions); - - end - function updateGeometry(obj) - if ~isempty(obj.lengthCenterNoMot) - obj.length = obj.lengthCenterNoMot + obj.motor.length + obj.parafoil.noseLength; - else - obj.length = (obj.absolutePositions("rear") + obj.bays("rear").length) + ... - obj.bays("parafoil").noseLength; - obj.lengthCenterNoMot = obj.absolutePositions("motor"); - end - if isempty(obj.crossSection) - obj.crossSection = 0.25*pi*obj.diameter^2; - end - obj.lengthCenter = obj.length - obj.parafoil.noseLength - obj.rear.boatLength; - end - - function updateMassNoMotor(obj) - if ~isempty(obj.massNoMotor) - return; - end - bNoMotor = values(remove(obj.bays, 'motor')); - obj.massNoMotor = sum([bNoMotor.mass]); - end - - function updateMass(obj) - obj.mass = obj.massNoMotor + obj.motor.mass; - end - - function updateXCgNoMotor(obj) - if ~isempty(obj.xCgNoMotor) - return; - end - bNoMotor = values(remove(obj.bays, 'motor'))'; - aPosNoMotor = values(remove(obj.absolutePositions, 'motor'))'; - obj.xCgNoMotor = ((aPosNoMotor + [bNoMotor.xCg])* ... - [bNoMotor.mass]')/obj.massNoMotor; - end - - function updateXCg(obj) - obj.xCg = (obj.xCgNoMotor*obj.massNoMotor + ... - obj.motor.mass.*(obj.motor.xCg+obj.lengthCenterNoMot))./ ... - obj.mass; - end - - function updateInertiaNoMotor(obj) - if ~isempty(obj.inertiaNoMotor) - return; - end - bNoMotor = values(remove(obj.bays, 'motor'))'; - aPosNoMotor = values(remove(obj.absolutePositions, 'motor'))'; - % [3x1] Ix, Iy, Iz - % Assumption: xCgs are close to rocket axis - inertias = [bNoMotor.inertia]; - temp = inertias(2:3, :) + ... - (obj.xCgNoMotor' - (aPosNoMotor + [bNoMotor.xCg])).^2 .* [bNoMotor.mass]; - obj.inertiaNoMotor = [sum(inertias(1, :)); sum(temp, 2)]; - end - - function updateInertia(obj) - % [3xN] Ix, Iy, Iz - % Assumption: xCgs are close to rocket axis - motorInertia = obj.motor.inertia(2:3, :) + ... - (obj.xCg - (obj.absolutePositions('motor') + obj.motor.xCg)).^2 .* obj.motor.mass; - baysInertia = obj.inertiaNoMotor(2:3, :) + ... - (obj.xCg - obj.xCgNoMotor).^2 .* obj.massNoMotor; - - obj.inertia = [obj.motor.inertia(1, :) + obj.inertiaNoMotor(1, :); - motorInertia + baysInertia]; - - obj.inertiaDot = diff(obj.inertia, 1, 2)./diff(obj.motor.time); - obj.inertiaDot(:, end + 1) = obj.inertiaDot(:, end); - end - - function updateCutoff(obj) - obj.cutoffInertia = interpLinear(obj.motor.time, obj.inertia, obj.motor.cutoffTime); - obj.cutoffMass = interpLinear(obj.motor.time, obj.mass, obj.motor.cutoffTime); - end - - function updateStagesMass(obj) - stage1 = obj.cutoffMass - (obj.parachutes(1,2).mass + obj.parachutes(2,2).mass + obj.parafoil.mass); - % Everything at cut off without parafoil, parafoil drogue and - % parafoil airfoil - stage2 = obj.cutoffMass - stage1; - % only parafoil: parafoil, parafoil drogue and parafoil airfoil - obj.stagesMass = [stage1 stage2]; - end - - function updateAll(obj) - % Note: properties without motor must be updated first - % if obj.motor.isHRE - obj.updateAbsolutePositions; - obj.updateGeometry; - obj.updateMassNoMotor; - obj.updateMass; - obj.updateXCgNoMotor; - obj.updateXCg; - obj.updateInertiaNoMotor; - obj.updateInertia; - obj.updateCutoff; - obj.updateStagesMass; - % end - % obj.updateAbsolutePositions; - % obj.updateGeometry; - % obj.updateMassNoMotor; - % obj.updateMass; - % obj.updateXCgNoMotor; - % obj.updateInertiaNoMotor; - % obj.updateStagesMass; - end - end - - methods(Access = protected) % Loaders - function obj = loadData(obj) - if isempty(obj.motor) - return; - end - varNames = {'total', 'geometry', 'state', 'finsCN'}; - motorName = obj.motor.name; - aeroPath = fullfile(obj.mission.dataPath, 'aeroCoefficients.mat'); - aeroHighAOAPath = fullfile(obj.mission.dataPath, 'aeroCoefficientsHighAOA.mat'); - - if ~(exist(aeroPath, 'file') && exist(aeroHighAOAPath,'file')) - return; - end - dataCoeffs = load(aeroPath); - dataCoeffsHighAOA = load(aeroHighAOAPath); - - if isfield(dataCoeffs, motorName) && isfield(dataCoeffsHighAOA, motorName) - dataCoeffs = dataCoeffs.(motorName); - dataCoeffsHighAOA = dataCoeffsHighAOA.(motorName); - else - return; - end - - if isfield(dataCoeffs, "finsCN") && ... - isfield(dataCoeffsHighAOA, "finsCN") - obj.coefficients.finsCN = dataCoeffs.finsCN; - obj.coefficientsHighAOA.finsCN = dataCoeffsHighAOA.finsCN; - end - - varNames = varNames{1:end - 1}; - if all(and(isfield(dataCoeffs, varNames), ... - isfield(dataCoeffsHighAOA, varNames))) - obj.coefficients.total = dataCoeffs.total; - obj.coefficientsHighAOA.total = dataCoeffsHighAOA.total; - obj.coefficients.geometry = dataCoeffs.geometry; - obj.coefficientsHighAOA.geometry = dataCoeffsHighAOA.geometry; - obj.coefficients.state = dataCoeffs.state; - obj.coefficientsHighAOA.state = dataCoeffsHighAOA.state; - end - end - end - - methods - function obj = Rocket(mission, varIn, options) - arguments - mission Mission = Mission() - varIn struct = [] - options.loadCoefficients logical = true - options.checkGeometry logical = true - end - obj@Component(mission, varIn); - - %% Loading data - if isempty(mission.name) - return; - end - - vars = obj.getConfig(mission); % Load config once and apply to other bays - obj.parafoil = Parafoil(mission, vars); - obj.recovery = Recovery(mission, vars); - obj.electronics = Electronics(mission, vars); - obj.motor = Motor(mission, vars); - obj.airbrakes = Airbrakes(mission, obj.motor, vars); - obj.rear = Rear(mission, vars); - obj.pitot = Pitot(mission, vars); - paras = Para(mission); - obj.parachutes = paras(:, :, 1); - - [bayNames, bayValues] = obj.getProperties(Superclass='Bay', Heterogeneous=1); - obj.bays = dictionary(bayNames, bayValues); - obj.updateAll(); - - if options.loadCoefficients - obj.loadData(); - answer = ''; - if isempty(obj.coefficients) || isempty(obj.coefficientsHighAOA) - answer = questdlg(['Coefficient matrices not found. ' ... - 'Do you want to create new matrices?']); - elseif options.checkGeometry && ~all(obj.checkGeometry()) - answer = questdlg(['Coefficient matrices differ from rocket geometry. ' ... - 'Do you want to create new matrices?']); - end - - switch answer - case 'Yes' - parserPath = fullfile(mission.msaPath, 'autoMatricesProtub'); - addpath(genpath(parserPath)); - mainAutoMatProtub(obj); - obj.loadData(); - case 'Cancel' - error('Rocket creation aborted') - otherwise - end - end - end - - function [coeffsValues, angle0] = interpCoeffs(obj, t, alpha, mach, beta, alt, c) - % interpCoeffs - interpolation of aerodynamic coefficients. - % - % INPUTS: - % - t, double [1,1], integration time, [s]; - % - alpha, double[1,1], angle of attack, []; - % - mach, double[1,1], mach number, []; - % - beta, double[1,1], sideslip angle, []; - % - alt, double[1,1], altitude, [m]; - % - c, double[1,1], aerobrakes control variable, []; - % - % OUTPUTS: - % - coeffsValues, array [16,1], aerodynamic coefficients; - % - angle0, array [2,1], array of the reference aerodynamic angles. - - if mach > obj.airbrakes.maxMach - c = 0; - end - h = c*obj.coefficients.state.hprot(end); - - [nearAlpha, indexAlpha] = nearestValSorted(obj.coefficients.state.alphas*pi/180, alpha); - [~, indexMach] = nearestValSorted(obj.coefficients.state.machs, mach); - [nearBeta, indexBeta] = nearestValSorted(obj.coefficients.state.betas*pi/180, beta); - [~, indexAlt] = nearestValSorted(obj.coefficients.state.altitudes, alt); - [~, indexControl] = nearestValSorted(obj.coefficients.state.hprot, h); - - angle0 = [nearAlpha; nearBeta]; - - if t >= obj.motor.cutoffTime - % Interpolate on airbrake control - % Uses end xCg, even if tCutOff < tBurnTime - num = length(obj.coefficients.state.hprot); %#ok<CPROPLC> - index1 = indexControl; - index2 = indexControl + 1; - if index2 > num - if num == 1 - coeffsValues = obj.coefficients(:, indexAlpha, indexMach, indexBeta, indexAlt, 1, end); - return; - else - index2 = index2 - 1; - index1 = index1 - 1; - end - end - - VF = obj.coefficients.total(:, indexAlpha, indexMach, indexBeta, indexAlt, index1, end); - VE = obj.coefficients.total(:, indexAlpha, indexMach, indexBeta, indexAlt, index2, end); - - deltaH = obj.coefficients.state.hprot(index2) - obj.coefficients.state.hprot(index1); - coeffsValues = ( (h - obj.coefficients.state.hprot(index1))/deltaH)*(VE - VF) + VF; - else - % Interpolate on xCg, close airbrakes - num = 1:1:length(obj.coefficients.state.xcgTime)-1; %#ok<CPROPLC> - indexXcg = t >= obj.coefficients.state.xcgTime(1:end-1) & t < obj.coefficients.state.xcgTime(2:end); - index1 = num(indexXcg); - index2 = index1 + 1; - - VF = obj.coefficients.total(:, indexAlpha, indexMach, indexBeta, indexAlt, indexControl, index1); - VE = obj.coefficients.total(:, indexAlpha, indexMach, indexBeta, indexAlt, indexControl, index2); - - deltaT = obj.coefficients.state.xcgTime(index2) - obj.coefficients.state.xcgTime(index1); - coeffsValues = ( (t - obj.coefficients.state.xcgTime(index1))/deltaT)*(VE - VF) + VF; - end - end - - function img = plot(obj) - arguments - obj Rocket - end - deltaXLE = obj.rear.finsDeltaXFreeChord; - r = obj.diameter/2; - height = obj.rear.finsHeight; - C1 = obj.rear.finsRootChord; - C2 = obj.rear.finsFreeChord; - - Lcent = obj.lengthCenter; - Lnos = obj.parafoil.noseLength; - - Xle1 = Lcent + Lnos - obj.rear.finsAxialDistance - C1; - - Daft = obj.rear.boatFinalDiameter; - Laft = obj.rear.boatLength; - - xNos = Lnos - Lnos*cos(linspace(0, pi/2, 50)); - if strcmp(obj.parafoil.noseType, 'KARMAN') - theta = @(x) acos( 1 - ( (2*x)./Lnos ) ); - Karman = @(x) ( r/sqrt(pi) ) * sqrt( theta(x) - ( sin(2*theta(x))./ 2 ) ); - yNos = Karman(xNos); - elseif strcmp(obj.parafoil.noseType, 'HAACK') - theta = @(x) acos( 1 - ( (2*x)./Lnos ) ); - Haack = @(x) ( r/sqrt(pi) ) * sqrt( theta(x) - ( sin(2*theta(x))./ 2 ) + (1/3)*sin(theta(x)).^3 ); - yNos = Haack(xNos); - elseif strcmp(obj.parafoil.noseType, 'OGIVE') - rho = (r^2 + Lnos^2)/(2*r); - Ogive = @(x) sqrt(rho^2 - (Lnos - x).^2) + r - rho; - yNos = Ogive(xNos); - elseif strcmp(obj.parafoil.noseType, 'POWER') - power = obj.parafoil.nosePower; - Power = @(x) r * (x/Lnos).^(power); - yNos = Power(xNos); - elseif strcmp(obj.parafoil.noseType, 'MHAACK') - p = obj.parafoil.nosePMod; - c = obj.parafoil.noseCMod; - xMod = @(x, p) (x/Lnos).^(p)*Lnos; - thetaMod = @(x, p) acos( 1 - ( (2*xMod(x, p))./Lnos ) ); - haackSeriesMod = @(x, p, C) ( r/sqrt(pi) ) * sqrt( thetaMod(x, p) ... - - ( sin(2*thetaMod(x, p))./ 2 ) + C*sin(thetaMod(x, p)).^3 ); - yNos = haackSeriesMod(xNos, p, c); - end - - if strcmp(obj.rear.boatType, 'OGIVE') % [-] Boat-tail shape. 0: Cone, 1: Tangent Ogive - [xBoat, yBoat] = computeTangentBoatPoints(2*r, Daft, Laft); - else - xBoat = [0 Laft]; - yBoat = [r Daft/2]; - end - - %%% figure begin - img = figure(); - hold on - - %%% NOSECONE - plot(xNos, yNos, 'k'); - plot(xNos, -yNos, 'k'); - plot([Lnos Lnos], [-r r], 'k'); - - %%% CENTERBODY - plot([Lnos Lnos+Lcent], [r r], 'k'); - plot([Lnos Lnos+Lcent], [-r -r], 'k'); - plot([Lnos+Lcent Lnos+Lcent], [-r r], 'k'); - - %%% BOAT-TAIL PLOT - plot(xBoat+Lnos+Lcent, yBoat, 'k'); - plot(xBoat+Lnos+Lcent, -yBoat, 'k'); - plot([Lnos+Lcent+Laft Lnos+Lcent+Laft], [-Daft/2 Daft/2], 'k'); - - %%% FINS PLOT - % top - plot([Xle1 Xle1+deltaXLE], [r r+height],'k'); - plot([Xle1+C1 Xle1+deltaXLE+C2], [r r+height],'k'); - plot([Xle1+deltaXLE Xle1+deltaXLE+C2], [r+height r+height],'k'); - plot([Xle1 Xle1+C1], [r r], 'k'); - % bottom - plot([Xle1 Xle1+deltaXLE], [-r -r-height],'k'); - plot([Xle1+C1 Xle1+deltaXLE+C2], [-r -r-height],'k'); - plot([Xle1+deltaXLE Xle1+deltaXLE+C2], [-r-height, -r-height],'k'); - plot([Xle1 Xle1+C1], [-r -r], 'k'); - - % plot([Xle1 Xle1+deltaXLE], [r+height r+height], 'k--') - % plot([Xle1+deltaXLE Xle1+deltaXLE], [r+height r],'k--') - - %%% BAYS - lengths = obj.absolutePositions.values + 2*Lnos; - baysBody = obj.absolutePositions.keys; - iMotor = find(strcmp(obj.absolutePositions.keys,"motor")); - xline(0,'r-.', 'Label', 'NOSE', ... - 'LabelOrientation', 'aligned', 'LabelVerticalAlignment', 'bottom', ... - 'LabelHorizontalAlignment', 'right'); - for i = 1:length(lengths) - if i == iMotor - line([lengths(i) lengths(i)], [-1 1], 'LineStyle', '--', 'Color', 'b'); % Draw the line - text(lengths(i), -1 + 0.3, baysBody(i), 'VerticalAlignment', 'top', ... - 'HorizontalAlignment', 'right', 'Rotation', 90, 'Color', 'b'); - else - line([lengths(i) lengths(i)], [-1 1], 'LineStyle', '--', 'Color', 'k'); % Draw the line - text(lengths(i), 1 + 0.1, baysBody(i), 'VerticalAlignment', 'top', ... - 'HorizontalAlignment', 'right', 'Rotation', 90); - end - end - boatPlotLength = obj.length - obj.rear.boatLength; - xline(boatPlotLength,'r-.', 'Label', 'BOAT-TAIL', ... - 'LabelOrientation', 'aligned', 'LabelVerticalAlignment', 'bottom', ... - 'LabelHorizontalAlignment', 'right'); - - axis equal - % set(gca, 'xlim', [-100, Lnos+Lcent+Laft+100]); - - %%% TITLE - title(string(strrep(obj.mission.name, '_', ' '))); - end - - - function checks = checkGeometry(obj) - % checkGeometry - This methods checks if the rocket geometry - % is consistent with the geometry of the - % aerodynamic matrices - % - % OUTPUTS: - % - checks (n fields of geometry, 1): boolean value of the geometry checks - - xCgRocket = round([ ... - obj.xCg(1); ... - obj.xCg(end) ... - ], 3); - - xCgTest = round([ - obj.coefficients.geometry.xcg(1); - obj.coefficients.geometry.xcg(end); - ], 3); - - if (obj.parafoil.noseCMod & obj.parafoil.nosePMod) == 0 % KARMAN ogive case, no modified p and c coefficients - geometryRocket = round([ - obj.diameter; - obj.parafoil.noseLength; - obj.lengthCenter; - obj.rear.finsRootChord; - obj.rear.finsFreeChord; - obj.rear.finsHeight; - obj.rear.finsDeltaXFreeChord; - obj.rear.nPanel; - obj.rear.boatLength; - obj.rear.boatFinalDiameter; - ], 3); - - geometryTest = round([ - obj.coefficients.geometry.diameter; - obj.coefficients.geometry.lNose; - obj.coefficients.geometry.lCenter; - obj.coefficients.geometry.chord1; - obj.coefficients.geometry.chord2; - obj.coefficients.geometry.height; - obj.coefficients.geometry.deltaXLE; - obj.coefficients.geometry.nPanel; - obj.coefficients.geometry.boatL; - obj.coefficients.geometry.boatD; - ], 3); - else % MHAAK ogive case, modified p and c coefficients - geometryRocket = round([ - obj.diameter; - obj.parafoil.noseLength; - obj.lengthCenter; - obj.parafoil.noseCMod; - obj.parafoil.nosePMod; - obj.rear.finsRootChord; - obj.rear.finsFreeChord; - obj.rear.finsHeight; - obj.rear.finsDeltaXFreeChord; - obj.rear.nPanel; - obj.rear.boatLength; - obj.rear.boatFinalDiameter; - ], 3); - - geometryTest = round([ - obj.coefficients.geometry.diameter; - obj.coefficients.geometry.lNose; - obj.coefficients.geometry.lCenter; - obj.coefficients.geometry.cMod; - obj.coefficients.geometry.pMod; - obj.coefficients.geometry.chord1; - obj.coefficients.geometry.chord2; - obj.coefficients.geometry.height; - obj.coefficients.geometry.deltaXLE; - obj.coefficients.geometry.nPanel; - obj.coefficients.geometry.boatL; - obj.coefficients.geometry.boatD; - ], 3); - end - - checks = [ - xCgRocket == xCgTest; - strcmp(obj.coefficients.geometry.ogType, obj.parafoil.noseType); - geometryRocket == geometryTest; - strcmp(obj.coefficients.geometry.boatType, obj.rear.boatType) - ]; - end - end -end \ No newline at end of file diff --git a/classes/bays/@Motor/Motor.m b/classes/bays/@Motor/Motor.m new file mode 100644 index 0000000000000000000000000000000000000000..e49fe4e8e208374bcbca4b702cc473a9b5580502 --- /dev/null +++ b/classes/bays/@Motor/Motor.m @@ -0,0 +1,137 @@ +classdef Motor < Bay + % Motor: Represents the motor configuration for a rocket. + % + % Constructor: + % - Motor: Creates an instance of the Motor class. + % Loaded config: rocketConfig.m > motor + % Loaded data: motors.mat + % Arguments: + % - mission: Mission, mission object + % - varIn: (optional) config source. Alternative to config.m file + + %% Bay Implementation + properties(Dependent) + name char % [-] Motor name + mass % [kg] Total bay mass + inertia % [kg*m^2] Total bay inertia (Body reference) + xcg % [m] Xcg wrt bay top + end + + properties(Access = private) + NAME % [-] Motor name + MASS % [kg] Total bay mass + INERTIA % [kg*m^2] Total bay inertia (Body reference) + XCG % [m] Xcg wrt bay top + end + + %% Motor properties + properties(SetAccess = private) + type (1, 1) MotorType % [-] Motor type - Solid, Hybrid, Liquid + thrust (1, :) double % [N] Engine thrust vector + propellantMass (1, :) double % [Kg] Propellant Mass in time + chamberPressure (1, :) double % [Pa] Pressure inside combustion chamber + pe (1, :) double % [Pa] Eflux pressure + end + + properties + time double % [s] Engine time vector + cutoffTime double % [s] Shutdown time + ignitionTransient double % [s] Ignition transient duration + cutoffTransient double % [s] Cutoff transient duration + tankLength double % [m] Tank length + fuelMass double % [kg] Fuel grain initial mass + oxidizerMass double % [kg] Oxidizer initial mass + structureMass double % [kg] Engine Structural Mass + fuselageMass double % [kg] Fuselage of the engine only + ae double % [Pa] Eflux Area + end + + properties(Constant, Access = protected) + configName = 'rocketConfig.m' + variableName = 'motor' + end + + properties(Access = private) + mission Mission = Mission() + end + + methods + function obj = Motor(mission, varIn) + arguments(Input) + mission Mission = Mission() + varIn = [] + end + obj@Bay(mission, varIn); + obj.mission = mission; + obj = obj.loadData(); + end + end + + methods (Access = private) + obj = loadData(obj) + out = validateTimeDependent(obj, value) + end + + %% Bay implementation + % Getters + methods + function value = get.name(obj) + value = obj.NAME; + end + + function value = get.mass(obj) + value = obj.MASS; + end + + function value = get.inertia(obj) + value = obj.INERTIA; + end + + function value = get.xcg(obj) + value = obj.XCG; + end + end + + % Setters + methods + function obj = set.name(obj, name) + obj.NAME = name; + obj = obj.loadData(); + end + + function obj = set.mass(obj, mass) + value = obj.validateTimeDependent(mass); + obj.MASS = value; + end + + function obj = set.inertia(obj, inertia) + value = obj.validateTimeDependent(inertia); + obj.INERTIA = value; + end + + function obj = set.xcg(obj, xcg) + value = obj.validateTimeDependent(xcg); + obj.XCG = value; + end + + function obj = set.thrust(obj, thrust) + value = obj.validateTimeDependent(thrust); + obj.thrust = value; + end + + function obj = set.propellantMass(obj, propellantMass) + value = obj.validateTimeDependent(propellantMass); + obj.propellantMass = value; + end + + function obj = set.chamberPressure(obj, chamberPressure) + value = obj.validateTimeDependent(chamberPressure); + obj.chamberPressure = value; + end + + function obj = set.pe(obj, pe) + value = obj.validateTimeDependent(pe); + obj.pe = value; + end + end +end \ No newline at end of file diff --git a/classes/bays/@Motor/loadData.m b/classes/bays/@Motor/loadData.m new file mode 100644 index 0000000000000000000000000000000000000000..460082b873ab45a33d5a0976ae5453187832f353 --- /dev/null +++ b/classes/bays/@Motor/loadData.m @@ -0,0 +1,50 @@ +function obj = loadData(obj) + +if isempty(obj.name) || isempty(obj.mission.name) + return; +end +load(fullfile(obj.mission.dataPath, 'motors.mat'), 'motors'); +chosenMotor = motors(strcmp({motors.MotorName}, obj.name)); + +if ~(isempty(obj.name) || isvarname(obj.name)) + error('Motor name is not a valid MATLAB variable name') +end + +if isempty(chosenMotor) + error(strcat('Unable to find engine: ', obj.name)); +end + +[obj.time, iUniqueTime] = unique(chosenMotor.t); +obj.thrust = chosenMotor.T(iUniqueTime); +obj.propellantMass = chosenMotor.m(iUniqueTime); +obj.structureMass = chosenMotor.mc; + +if contains(obj.name, 'HRE') + obj.type = "Hybrid"; +else + obj.type = "Solid"; +end + +if obj.type == "Hybrid" + obj.length = chosenMotor.L; + obj.tankLength = chosenMotor.Ltank; + obj.fuelMass = chosenMotor.mFu; + obj.oxidizerMass = chosenMotor.mOx; + inertiaRaw = [chosenMotor.Ixx;chosenMotor.Iyy;chosenMotor.Izz]; + obj.INERTIA = inertiaRaw(:, iUniqueTime); + obj.XCG = chosenMotor.xcg(iUniqueTime); + obj.pe = chosenMotor.Pe(iUniqueTime); + obj.ae = chosenMotor.Ae; + obj.fuselageMass = chosenMotor.mFus; + obj.chamberPressure = chosenMotor.Pc; + obj.MASS = obj.propellantMass + obj.structureMass; +else + obj.length = chosenMotor.L/1000; % lengths are saved in mm for solid motors + obj.xcg = obj.length/2 * ones(1, size(obj.time, 2)); +end + +if isempty(obj.cutoffTime) || obj.cutoffTime > obj.time(end) + obj.cutoffTime = obj.time(end); +end + +end \ No newline at end of file diff --git a/classes/bays/@Motor/validateTimeDependent.m b/classes/bays/@Motor/validateTimeDependent.m new file mode 100644 index 0000000000000000000000000000000000000000..458350973afe3cdf66fbb27d8f146029cdbaa86c --- /dev/null +++ b/classes/bays/@Motor/validateTimeDependent.m @@ -0,0 +1,14 @@ +function out = validateTimeDependent(obj, value) +len = length(obj.time); + +if isempty(obj.time), out = value; return; end +if size(value, 2) == 1, out = repmat(value, 1, len); return; end +if length(value) ~= length(obj.time) + error = MException( ... + 'MSA:badInput', ... + 'Incorrect dimensions: %s and time vector must be the same size', ... + property); + throwAsCaller(error); +end +out = value; +end \ No newline at end of file diff --git a/classes/bays/Airbrakes.m b/classes/bays/Airbrakes.m index dbb8478838af27fba4ea60be0c2c1bb4c559b74e..73495d58137e2a369b6d84114c164aca63367222 100644 --- a/classes/bays/Airbrakes.m +++ b/classes/bays/Airbrakes.m @@ -10,6 +10,13 @@ classdef Airbrakes < Bay % - varIn: (optional) config source. Alternative to config.m % file + %% Bay Implementation + properties + mass % [kg] Total bay mass + inertia % [kg*m^2] Total bay inertia (Body reference) + xcg % [m] Xcg wrt bay top + end + properties enabled logical % If false, airbrakes will be kept closed during flight identification logical % If false, @@ -24,52 +31,44 @@ classdef Airbrakes < Bay servoTau double servoOmega double % [rad/s] Servo-motor angular velocity - extension double % aerobrakes, 1-2-3 for 0%, 50% or 100% opened + extension double % aerobrakes, 0-1 for 0-100% opened deltaTime double % aerobrakes, configurations usage time maxMach double % [-] Maximum Mach at which airbrakes can be used height double % [m] Block airbrakes opening coordinate ( First entry must be 0! ) angleFunction function_handle % [-] Relation between angle and extension height + end - position % [m] Offset with respect to other bays (negative -> shift forward) - length % [m] Total bay length - mass % [kg] Total bay mass - inertia % [kg*m^2] Total bay inertia (Body reference) - xCg % [m] Cg relative to bay upper side + properties(SetAccess = ?Rocket, GetAccess = private) + minTime % [s] Minimum time since engine fire at which airbrakes can open end - - properties(Access = protected) - configName = 'rocketConfig.m' - variableName = 'airbrakes' - mission Mission = Mission() - motor Motor = Motor() - end - properties(Access = public) + properties(SetAccess = private) time double end - methods - function obj = Airbrakes(mission, motor, varIn) - arguments(Input) - mission Mission = Mission() - motor Motor = Motor() - varIn = [] - end - obj@Bay(mission, varIn); - obj.motor = motor; + properties(Constant, Access = protected) + configName = 'rocketConfig.m' + variableName = 'airbrakes' + end + + methods + function obj = set.minTime(obj, minTime) + obj.minTime = minTime; + obj = obj.updateTime(); end - - function set.motor(obj, motor) - obj.motor = motor; - obj.updateTime; + + function obj = set.deltaTime(obj, deltaTime) + obj.deltaTime = deltaTime; + obj = obj.updateTime(); end - end - methods - function updateTime(obj) - obj.time = cumsum(obj.deltaTime) + obj.motor.cutoffTime; + function obj = updateTime(obj) + if isempty(obj.deltaTime) || isempty(obj.minTime), return; end + obj.time = cumsum(obj.deltaTime) + obj.minTime; end + end + methods function extension = angle2Extension(obj, angle) extension = obj.angleFunction(angle) / obj.height(end); end diff --git a/classes/bays/Electronics.m b/classes/bays/Electronics.m index a9448aa834efea6ca915ddce4ebf3b4b97b7e4aa..7f87a9879973869974a126062b575f7f5b08996b 100644 --- a/classes/bays/Electronics.m +++ b/classes/bays/Electronics.m @@ -9,28 +9,16 @@ classdef Electronics < Bay % - mission: Mission, mission object % - varIn: (optional) config source. Alternative to config.m % file + + %% Bay Implementation properties - offset = 0 - position % [m] Absolute position, relative to nose base - length % [m] Total bay length - mass % [kg] Total bay mass - inertia % [kg*m^2] Total bay inertia (Body reference) - xCg % [m] Cg relative to bay upper side + mass % [kg] Total bay mass + inertia % [kg*m^2] Total bay inertia (Body reference) + xcg % [m] Xcg wrt bay top end - properties(Access = protected) + properties(Constant, Access = protected) configName = 'rocketConfig.m' variableName = 'electronics' - mission Mission = Mission() - end - - methods - function obj = Electronics(mission, varIn) - arguments(Input) - mission Mission = Mission() - varIn = [] - end - obj@Bay(mission, varIn); - end end end \ No newline at end of file diff --git a/classes/bays/Motor.m b/classes/bays/Motor.m deleted file mode 100644 index 39f6ed2d790b8a95a4acc8d1e12fab435921fe39..0000000000000000000000000000000000000000 --- a/classes/bays/Motor.m +++ /dev/null @@ -1,135 +0,0 @@ -classdef Motor < Bay - % Motor: Represents the motor configuration for a rocket. - % - % Constructor: - % - Motor: Creates an instance of the Motor class. - % Loaded config: rocketConfig.m > motor - % Loaded data: motors.mat - % Arguments: - % - mission: Mission, mission object - % - varIn: (optional) config source. Alternative to config.m file - - properties - name {mustBeTextScalar} = '' % [-] Motor name - position % [m] Absolute position, relative to nose base - length % [m] Total length (motor + tank) - inertia % [kg*m^2] Total Motor inertia (Body reference) - cutoffTime double % [s] Shutdown time - tankLength double % [m] Tank length - ignitionTransient double % [s] Ignition transient duration - cutoffTransient double % [s] Cutoff transient duration - time double % [s] Engine time vector - thrust double % [N] Engine thrust vector - fuelMass double % [kg] Fuel (grain) initial mass - oxidizerMass double % [kg] Oxidizer initial mass - propellantMass double % [Kg] Propellant Mass (in time) - structureMass double % [kg] Engine Structural Mass - fuselageMass double % [kg] Fuselage of the engine only - chamberPressure double % [Pa] Pressure inside combustion chamber - xCg % [m] Engine xcg from tank tip - pe double % [Pa] Eflux pressure - ae double % [Pa] Eflux Area - end - - properties - mass % [kg] Total Motor mass - fuselageXCg double % [m] xcg of the engine fuselage only from tank tip - end - - properties(Access = protected) - configName = 'rocketConfig.m' - variableName = 'motor' - mission Mission = Mission() - end - - properties(SetAccess = protected) - isHRE logical % [-] Flag relateed to the type of motor: true if HRE - end - - properties(Access = private) - isBeingUpdated logical = false % [-] Flag used internally when some properties are being updated - end - - methods - function obj = Motor(mission, varIn) - arguments(Input) - mission Mission = Mission() - varIn = [] - end - obj@Bay(mission, varIn); - obj.loadData(); - obj.updateAll(); - end - - function set.name(obj, name) - obj.name = name; - obj.changeState(); - obj.loadData(); - obj.updateAll(); - obj.changeState(); - end - - function updateAll(obj) - obj.updateMass(); - end - - function changeState(obj) - obj.isBeingUpdated = not(obj.isBeingUpdated); - end - - function updateMass(obj) - obj.fuselageXCg = (obj.length - ... - obj.tankLength)/2 + obj.tankLength; - obj.mass = obj.propellantMass + obj.structureMass; - end - end - - methods (Access = protected) - function obj = loadData(obj) - if isempty(obj.mission.name) || isempty(obj.name) - return; - end - load(fullfile(obj.mission.dataPath, 'motors.mat'), 'motors'); - chosenMotor = motors(strcmp({motors.MotorName}, obj.name)); - - if ~(isempty(obj.name) || isvarname(obj.name)) - error('Motor name is not a valid MATLAB variable name') - end - - if isempty(chosenMotor) - error(strcat('Unable to find engine: ', obj.name)); - end - - [obj.time, iUniqueTime] = unique(chosenMotor.t); - obj.thrust = chosenMotor.T(iUniqueTime); - obj.propellantMass = chosenMotor.m(iUniqueTime); - obj.structureMass = chosenMotor.mc; - - obj.isHRE = contains(obj.name, 'HRE'); - - if obj.isHRE - obj.length = chosenMotor.L; - obj.tankLength = chosenMotor.Ltank; - if isempty(obj.tankLength) - obj.tankLength = chosenMotor.LtankPr + chosenMotor.LtankOx; - end - obj.fuelMass = chosenMotor.mFu; - obj.oxidizerMass = chosenMotor.mOx; - inertiaRaw = [chosenMotor.Ixx;chosenMotor.Iyy;chosenMotor.Izz]; - obj.inertia = inertiaRaw(:, iUniqueTime); - obj.xCg = chosenMotor.xcg(iUniqueTime); - obj.pe = chosenMotor.Pe(iUniqueTime); - obj.ae = chosenMotor.Ae; - obj.fuselageMass = chosenMotor.mFus; - obj.chamberPressure = chosenMotor.Pc; - else - obj.length = chosenMotor.L/1000; % lengths are saved in mm for solid motors - obj.xCg = obj.length/2 * ones(1, size(obj.time, 2)); - end - - if isempty(obj.cutoffTime) || obj.cutoffTime > obj.time(end) || obj.isBeingUpdated - obj.cutoffTime = obj.time(end); - end - end - end -end \ No newline at end of file diff --git a/classes/bays/MotorType.m b/classes/bays/MotorType.m new file mode 100644 index 0000000000000000000000000000000000000000..56cce1cbef4baa9218dafc21705d9e8b0f410155 --- /dev/null +++ b/classes/bays/MotorType.m @@ -0,0 +1,8 @@ +classdef MotorType + enumeration + Solid + Hybrid + Liquid + end +end + diff --git a/classes/bays/Parafoil.m b/classes/bays/Parafoil.m index 378fb88708e41030e60d487c5e5c11f3be4bbcee..f87d709d87937f566beb4ae0d21bda9a230e4855 100644 --- a/classes/bays/Parafoil.m +++ b/classes/bays/Parafoil.m @@ -9,7 +9,14 @@ classdef Parafoil < Bay % - mission: Mission, mission object % - varIn: (optional) config source. Alternative to config.m % file - + + %% Bay Implementation + properties + mass % [kg] Total bay mass + inertia % [kg*m^2] Total bay inertia (Body reference) + xcg % [m] Xcg wrt bay top + end + properties noseLength double % [m] Nosecone length noseType {mustBeMember(noseType, { ... @@ -19,22 +26,15 @@ classdef Parafoil < Bay nosePower double % [-] Nosecone power type parameter nosePMod double % [-] P coefficient for modified nosecone shapes noseCMod double % [-] C coefficient for modified nosecone shapes - - position % [m] Absolute position, relative to nose base - length % [m] Total bay length - mass % [kg] Total bay mass - inertia % [kg*m^2] Total bay inertia (Body reference) - xCg % [m] Cg relative to bay upper side end - properties (Dependent) - inverseInertia double - end + % properties (Dependent) + % inverseInertia double + % end - properties(Access = protected) + properties(Constant, Access = protected) configName = 'rocketConfig.m' variableName = 'parafoil' - mission Mission = Mission() end methods @@ -47,9 +47,9 @@ classdef Parafoil < Bay end end - methods - function inverseInertia = get.inverseInertia(obj) - inverseInertia = inv(obj.inertiaMatrix); - end - end + % methods + % function inverseInertia = get.inverseInertia(obj) + % inverseInertia = inv(obj.inertiaMatrix); + % end + % end end \ No newline at end of file diff --git a/classes/bays/Rear.m b/classes/bays/Rear.m index 65d561d73eb9973703fe104184df2805f0981cd7..2e713d59def14c04a6ef9920d638bc43a8d37e75 100644 --- a/classes/bays/Rear.m +++ b/classes/bays/Rear.m @@ -10,13 +10,14 @@ classdef Rear < Bay % - varIn: (optional) config source. Alternative to config.m % file + %% Bay Implementation + properties + mass % [kg] Total bay mass + inertia % [kg*m^2] Total bay inertia (Body reference) + xcg % [m] Xcg wrt bay top + end + properties - position % [m] Absolute position, relative to nose base - length % [m] Total bay length - mass % [kg] Total bay mass - inertia % [kg*m^2] Total bay inertia (Body reference) - xCg % [m] Cg relative to bay upper side - boatType {mustBeMember(boatType, { ... '', 'CONE', 'OGIVE'})} = '' % [] Boat type boatLength double % [m] Boat length @@ -34,20 +35,9 @@ classdef Rear < Bay finsMaxThicknessPosition double % [m] Fraction of chord from leading edge to max thickness end - properties(Access = protected) + properties(Constant, Access = protected) configName = 'rocketConfig.m' variableName = 'rear' - mission Mission = Mission() - end - - methods - function obj = Rear(mission, varIn) - arguments(Input) - mission Mission = Mission() - varIn = [] - end - obj@Bay(mission, varIn); - end end end diff --git a/classes/bays/Recovery.m b/classes/bays/Recovery.m index b5d10de236fb9b2668a0c71493442c1cf19b903b..40c7147bf7ac6d1ebfa75ca8acbe60086a7999e5 100644 --- a/classes/bays/Recovery.m +++ b/classes/bays/Recovery.m @@ -10,27 +10,15 @@ classdef Recovery < Bay % - varIn: (optional) config source. Alternative to config.m % file + %% Bay Implementation properties - position % [m] Absolute position, relative to nose base - length % [m] Total bay length - mass % [kg] Total bay mass - inertia % [kg*m^2] Total bay inertia (Body reference) - xCg % [m] Cg relative to bay upper side + mass % [kg] Total bay mass + inertia % [kg*m^2] Total bay inertia (Body reference) + xcg % [m] Xcg wrt bay top end - properties(Access = protected) + properties(Constant, Access = protected) configName = 'rocketConfig.m' variableName = 'recovery' - mission Mission = Mission() - end - - methods - function obj = Recovery(mission, varIn) - arguments(Input) - mission Mission = Mission() - varIn = [] - end - obj@Bay(mission, varIn); - end end end \ No newline at end of file diff --git a/classes/components/Para.m b/classes/misc/ParaLoader.m similarity index 51% rename from classes/components/Para.m rename to classes/misc/ParaLoader.m index fb7388e7c1af52dd9afab7f6dde18e6aceb35883..0046722c475f09e8bd4fb23ba750ebd7f756b421 100644 --- a/classes/components/Para.m +++ b/classes/misc/ParaLoader.m @@ -1,4 +1,4 @@ -classdef Para < matlab.mixin.Heterogeneous & Component +classdef ParaLoader < Config % Parachute: Represents a parachute component. % % Constructor: @@ -15,38 +15,28 @@ classdef Para < matlab.mixin.Heterogeneous & Component % the third dimension can be trimmed away. % Do NOT consider the third dimension as valid - properties(Access = protected) + properties + parachutes + end + + properties(Constant, Access = protected) configName = 'paraConfig.m' variableName = 'para' - mission Mission end methods - function obj = Para(mission, varIn) + function obj = ParaLoader(mission, varsIn) arguments(Input) mission Mission = Mission() - varIn = [] + varsIn = [] end - obj@Component(mission, varIn, "elementWise", false); - - %% Forcing creation of heterogeneous array - if any(size(obj) > 1) - switch class(obj) - % In case of homogeneous type, forces conversion to - % heterogeneous Para array - case 'Parachute' - obj(1, 1, 2) = Wing(); - case 'Wing' - obj(1, 1, 2) = Parachute(); - otherwise - end + + if isempty(mission.configPath), return; end + if isempty(varsIn) + varsIn = obj.getConfig(mission.configPath); end - end - end - methods (Static,Sealed,Access=protected) - function default = getDefaultScalarElement - default = Parachute; + obj.parachutes = varsIn.para; end end end \ No newline at end of file diff --git a/classes/components/Parachute.m b/classes/misc/Parachute.m similarity index 85% rename from classes/components/Parachute.m rename to classes/misc/Parachute.m index e16eb69be8156fcb408dd3c03960d2015a70df64..c706a1f9c2bd234b5d8e009f38a016d83aee8d12 100644 --- a/classes/components/Parachute.m +++ b/classes/misc/Parachute.m @@ -1,4 +1,4 @@ -classdef Parachute < Para +classdef Parachute % Parachute: Represents a parachute component. % % Constructor: @@ -28,17 +28,5 @@ classdef Parachute < Para nf double % [-] Adimensional Opening Time forceCoefficient double % [-] Empirical coefficient to obtain correct peak force at deployment end - - methods - function obj = Parachute(mission, varIn) - arguments(Input) - mission Mission = Mission() - varIn = [] - end - - obj@Para(mission, varIn); - obj = obj(:, :, 1); - end - end end diff --git a/classes/components/Pitot.m b/classes/misc/Pitot.m similarity index 81% rename from classes/components/Pitot.m rename to classes/misc/Pitot.m index 1d8cd4885a8e90c1f0fcb17f681edfdbf77f5118..dd2d023d35cbdf0cf1f13c6290e38abd3845b470 100644 --- a/classes/components/Pitot.m +++ b/classes/misc/Pitot.m @@ -1,4 +1,4 @@ -classdef Pitot < Component +classdef Pitot < Config % Pitot: Represents a pitot tube. % % Constructor: @@ -19,19 +19,19 @@ classdef Pitot < Component finalConeDiameter double % [m] Pitot final conic section diameter end - properties(Access = protected) + properties(Constant, Access = protected) configName = 'rocketConfig.m' variableName = 'pitot' - mission Mission = Mission() end methods - function obj = Pitot(mission, varIn) - arguments(Input) + function obj = Pitot(mission, varsIn) + arguments (Input) mission Mission = Mission() - varIn = [] + varsIn = [] end - obj@Component(mission, varIn); + + obj@Config(mission.configPath, varsIn); end end end \ No newline at end of file diff --git a/classes/components/WindCustom.m b/classes/misc/WindCustom.m similarity index 100% rename from classes/components/WindCustom.m rename to classes/misc/WindCustom.m diff --git a/classes/components/WindMatlab.m b/classes/misc/WindMatlab.m similarity index 100% rename from classes/components/WindMatlab.m rename to classes/misc/WindMatlab.m diff --git a/classes/components/Wing.m b/classes/misc/Wing.m similarity index 85% rename from classes/components/Wing.m rename to classes/misc/Wing.m index 4e47f9e3a9938d03e714c9721344f2da8e712c57..abab7f9dba8385a9377fd5db562dfa381fdbb7bf 100644 --- a/classes/components/Wing.m +++ b/classes/misc/Wing.m @@ -1,4 +1,4 @@ -classdef Wing < Para +classdef Wing % Parachute: Represents a parachute component. % % Constructor: @@ -18,7 +18,6 @@ classdef Wing < Para mass double % [kg] Wing Mass openingTime double % [s] Wing opening time (delay + time to get fully open) - inertia % [kg*m^2] 3x3 Inertia Matrix (the diagonal is made of the inertia vector values) finalAltitude double % [m] Final altitude of the parafoil semiWingSpan double % [m] Wing semiwingspan MAC double % [m] Wing mean aero chord @@ -45,29 +44,27 @@ classdef Wing < Para controlParams = [] % Struct containing all the control parameters end + properties(Dependent) + inertia % [kg*m^2] 3x3 Inertia Matrix (the diagonal is made of the inertia vector values) + end + properties(SetAccess = private) inverseInertia end - methods - function obj = Wing(mission, varIn) - arguments(Input) - mission Mission = Mission() - varIn = [] - end - - obj@Para(mission, varIn); - obj = obj(:, :, 1); - end + properties(Access = private) + INERTIA + end - function set.inertia(obj, inertia) - obj.inertia = inertia; - obj.updateAll(); + methods + function obj = set.inertia(obj, inertia) + obj.INERTIA = inertia; + if isempty(obj.inertia), return; end + obj.inverseInertia = obj.INERTIA\eye(3); end - function updateAll(obj) - if isempty(obj.inertia), return; end - obj.inverseInertia = obj.inertia\eye(3); + function inertia = get.inertia(obj) + inertia = obj.INERTIA; end end end \ No newline at end of file diff --git a/functions/miscellaneous/camel2UpperSnake.m b/functions/miscellaneous/camel2UpperSnake.m new file mode 100644 index 0000000000000000000000000000000000000000..a1d852b7755c5d919b8bdd544b085cf3c7d0b250 --- /dev/null +++ b/functions/miscellaneous/camel2UpperSnake.m @@ -0,0 +1,6 @@ +function result = camel2UpperSnake(camelStr) + % Convert a camelCase string to UPPER_SNAKE_CASE + modifiedStr = regexprep(camelStr, '([A-Z])', '_$1'); + upperStr = upper(modifiedStr); + result = regexprep(upperStr, '^_', ''); % Remove leading underscore if present +end \ No newline at end of file diff --git a/missions/2025_Orion_Portugal_October/config/rocketConfig.m b/missions/2025_Orion_Portugal_October/config/rocketConfig.m index 71e89f1b36a4ea8678b67abdd027b886e92a11aa..70935bb21235288b69d68c21e73dcf373140ea95 100644 --- a/missions/2025_Orion_Portugal_October/config/rocketConfig.m +++ b/missions/2025_Orion_Portugal_October/config/rocketConfig.m @@ -1,13 +1,13 @@ % CONFIG - This script sets up rocket's parameters %% ROCKET - OVERRIDES BAYS CONFIG -rocket = Rocket(); - -rocket.diameter = 0.15; % [m] Rocket diameter -rocket.massNoMotor = []; % [kg] OVERRIDE mass without motor -rocket.inertiaNoMotor = []; % [kg*m^2] OVERRIDE inertia without motor - body axes reference -rocket.xCgNoMotor = []; % [m] OVERRIDE xCg without motor -rocket.lengthCenterNoMot = []; % [m] OVERRIDE Center length - no nose, no motor +% rocket = Rocket(); +% +% rocket.diameter = 0.15; % [m] Rocket diameter +% rocket.massNoMotor = []; % [kg] OVERRIDE mass without motor +% rocket.inertiaNoMotor = []; % [kg*m^2] OVERRIDE inertia without motor - body axes reference +% rocket.xCgNoMotor = []; % [m] OVERRIDE xCg without motor +% rocket.lengthCenterNoMot = []; % [m] OVERRIDE Center length - no nose, no motor %% PRF - Includes Parafoil + Nose parafoil = Parafoil(); @@ -15,7 +15,7 @@ parafoil = Parafoil(); parafoil.length = 646.2 * 1e-3; % [m] Total bay length parafoil.mass = 5; % [kg] Total bay mass parafoil.inertia = 1e-9*[10514313; 92235828; 92322682]; % [kg*m^2] Total bay inertia (Body reference) -parafoil.xCg = 453.55 * 1e-3; % [m] Cg relative to bay upper side +parafoil.xcg = 453.55 * 1e-3; % [m] Cg relative to bay upper side parafoil.noseLength = 0.327; % [m] Nosecone length parafoil.noseType = 'MHAACK'; % [-] Nosecone shape @@ -29,7 +29,7 @@ recovery = Recovery(); recovery.length = 826 * 1e-3; % [m] Total bay length recovery.mass = 4.452; % [kg] Total bay mass recovery.inertia = 1e-9*[11889636; 274548189; 275245205]; % [kg*m^2] Total bay inertia (Body reference) -recovery.xCg = 459 * 1e-3; % [m] Cg relative to bay upper side +recovery.xcg = 459 * 1e-3; % [m] Cg relative to bay upper side %% ELC electronics = Electronics(); @@ -37,7 +37,7 @@ electronics = Electronics(); electronics.length = 422.5 * 1e-3; % [m] Total bay length electronics.mass = 3.3271; % [kg] Total bay mass electronics.inertia = 1e-9*[10041478; 49742634; 50292878]; % [kg*m^2] Total bay inertia (Body reference) -electronics.xCg = 221.11 * 1e-3; % [m] Cg relative to bay upper side +electronics.xcg = 221.11 * 1e-3; % [m] Cg relative to bay upper side %% ARB airbrakes = Airbrakes(); @@ -47,7 +47,7 @@ airbrakes.identification = false; % Control parameter airbrakes.length = 54.8 * 1e-3; % [m] Total bay length airbrakes.mass = 0.96; % [kg] Total bay mass airbrakes.inertia = 1e-9*[1886729; 1931082; 3088968]; % [kg*m^2] Total bay inertia (Body reference) -airbrakes.xCg = 35.2 * 1e-3; % [m] Cg relative to bay upper side +airbrakes.xcg = 35.2 * 1e-3; % [m] Cg relative to bay upper side airbrakes.enabled = false; % If true, multiple and smooth airbrakes opening will be simulated airbrakes.extension = [1]; % aerobrakes, 1-2-3 for 0%, 50% or 100% opened @@ -82,7 +82,7 @@ rear.position = []; % [m] offse rear.length = 477 * 1e-3; % [m] Total bay length rear.mass = 1736.32 *1e-3; % [kg] Total bay mass rear.inertia = 1e-9*[13662070; 47529495; 47528846]; % [kg*m^2] Total bay inertia (Body reference) -rear.xCg = 236.82 * 1e-3; % [m] Cg relative to bay upper side +rear.xcg = 236.82 * 1e-3; % [m] Cg relative to bay upper side rear.boatType = 'OGIVE'; % [-] Boat type rear.boatLength = 0.240; % [m] Boat length