Skip to content

Commit

Permalink
ENH: first commit of
Browse files Browse the repository at this point in the history
- update requirements
- update parameters.json
- update gitignore
- some nbks to test the new features (might be excluded in the future
  • Loading branch information
Gui-FernandesBR committed Nov 14, 2023
1 parent 6760bdd commit 5e15ad4
Show file tree
Hide file tree
Showing 17 changed files with 1,933 additions and 16 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,5 @@ dmypy.json

# VS Code
.vscode/

databank/
Binary file added OpenRocket-22.02.jar
Binary file not shown.
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,13 @@ The options are the following:

### Limitations

- Only a single nosecone is supported.
- Only a single stage is supported.
This code won't work for your rocket if it has any of the following features:

- Your file wasn't saved in English
- Your file doesn't contain simulation data
- Your rocket has more than one stage
- Your rocket has more than one engine
- Your rocket has more than one nosecone

### Deserialization

Expand Down
49 changes: 49 additions & 0 deletions destroy-the-bank.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import logging
import os

# Configure the logging settings
logging.basicConfig(
filename="script.log", # Specify the log file name
level=logging.INFO, # Set the logging level to INFO (you can adjust this as needed)
format="%(asctime)s - %(levelname)s - %(message)s", # Define log message format
datefmt="%Y-%m-%d %H:%M:%S", # Define date and time format
)


def destroy_the_bank(file):
"""
Run the 'ork2json' command on the given file.
Args:
file (str): The path to the .ork file.
"""
cli = "ork2json"
output = file[:-4] # Remove '.ork' extension
verbose = True

command = f"{cli} --filepath {file} --output {output} --verbose {verbose}"
logging.info(f"Executing command: {command}")

try:
import subprocess

subprocess.run(command, shell=True, check=True)
logging.info(f"Execution successful for file: {file}")
except subprocess.CalledProcessError as e:
logging.error(f"Error in file: {file}")
logging.error(str(e))
raise


if __name__ == "__main__":
folder = "examples/databank"

ork_files = [file for file in os.listdir(folder) if file.endswith(".ork")]

for file in ork_files:
file_path = os.path.join(folder, file)
try:
destroy_the_bank(file_path)
except Exception as e:
# Log any unexpected exceptions
logging.exception(f"An unexpected error occurred in file: {file}")
19 changes: 14 additions & 5 deletions examples/EPFL--BellaLui--2020/parameters.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"id": {
"comment": null,
"designer": null,
"filepath": "examples/EPFL--BellaLui--2020/rocket.ork",
"filepath": "examples\\EPFL--BellaLui--2020\\rocket.ork",
"rocket_name": "Foguete"
},
"motors": {
Expand All @@ -41,7 +41,7 @@
"nozzle_position": 0,
"nozzle_radius": 0.02025,
"throat_radius": 0.0135,
"thrust_source": "examples/EPFL--BellaLui--2020\\thrust_source.csv"
"thrust_source": "examples\\EPFL--BellaLui--2020\\thrust_source.csv"
},
"nosecones": {
"distance_to_cm": 1.317,
Expand All @@ -53,7 +53,7 @@
"rocket": {
"center_of_mass_without_propellant": 1.559,
"coordinate_system_orientation": "tail_to_nose",
"drag_curve": "examples/EPFL--BellaLui--2020\\drag_curve.csv",
"drag_curve": "examples\\EPFL--BellaLui--2020\\drag_curve.csv",
"inertia": [
0.096246,
0.096246,
Expand All @@ -62,7 +62,16 @@
"mass": 8.419,
"radius": 0.078
},
"stored_results": {},
"stored_results": {
"flight_time": 92.782,
"ground_hit_velocity": 26.142,
"launch_rod_velocity": 14.728,
"max_acceleration": 122.07,
"max_altitude": 1190.0,
"max_mach": 0.5587,
"max_velocity": 189.56,
"time_to_apogee": 14.851
},
"tails": {
"0": {
"bottom_radius": 0.0675,
Expand All @@ -75,7 +84,7 @@
"trapezoidal_fins": {
"0": {
"cant_angle": 0.0,
"distance_to_cm": -0.7886,
"distance_to_cm": 1.317,
"name": "Conjunto de aletas trapezoidais",
"number": 3,
"root_chord": 0.28,
Expand Down
1,038 changes: 1,038 additions & 0 deletions examples/EPFL--BellaLui--2020/simulation.ipynb

Large diffs are not rendered by default.

17 changes: 13 additions & 4 deletions examples/NDRT--Rocket--2020/parameters.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
"rail_length": 2.7432000000000003
},
"id": {
"comment": "Actual weight: 68 oz\n\nNoseconre + epoxy: 69.7 oz",
"comment": "Actual weight: 68 ozNoseconre + epoxy: 69.7 oz",
"designer": null,
"filepath": "examples\\NDRT--Rocket--2020\\rocket-Feb16.ork",
"filepath": "examples/NDRT--Rocket--2020/rocket.ork",
"rocket_name": "Rocket"
},
"motors": {
Expand Down Expand Up @@ -81,7 +81,16 @@
"mass": 20.735,
"radius": 0.1016
},
"stored_results": {},
"stored_results": {
"flight_time": 86.725,
"ground_hit_velocity": 4.7426,
"launch_rod_velocity": 17.993,
"max_acceleration": 67.386,
"max_altitude": 1085.9,
"max_mach": 0.42789,
"max_velocity": 145.01,
"time_to_apogee": 15.761
},
"tails": {
"0": {
"bottom_radius": 0.0776224,
Expand All @@ -94,7 +103,7 @@
"trapezoidal_fins": {
"0": {
"cant_angle": 0.0,
"distance_to_cm": -1.3439,
"distance_to_cm": -0.3786999999999998,
"name": "Trapezoidal fin set",
"number": 4,
"root_chord": 0.15239999999999995,
Expand Down
105 changes: 105 additions & 0 deletions generate_nb_from_ork.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import json
import os
from datetime import datetime, timedelta
from multiprocessing.sharedctypes import Value

import nbformat
import numpy as np
import orhelper
from bs4 import BeautifulSoup

flight_parameters = {}


def generate(ork_file, nb_file, eng_file, open_rocket_instance):
nb = nbformat.read(nb_file, nbformat.NO_CONVERT)

main_code = f'Main = Calisto.addParachute(\n "Main",\n CdS=parameters["MainCds{idx}"],\n trigger=mainTrigger,\n samplingRate=105,\n lag=parameters["MainDeployDelay{idx}"],\n noise=(0, 8.3, 0.5),\n)'
main_trigger = f'def mainTrigger(p, y):\n # p = pressure\n # y = [x, y, z, vx, vy, vz, e0, e1, e2, e3, w1, w2, w3]\n # activate main when vz < 0 m/s and z < 800 + 1400 m (+1400 due to surface elevation).\n return True if y[5] < 0 and y[2] < parameters["MainDeployAltitude{idx}"] + parameters["elevation"] else False'
chute_cell_code += f"{main_trigger}\n\n{main_code}\n\n"

for idx, drogue in enumerate(
filter(lambda x: "Drogue" in x.find("name").text, chutes)
):
drogue_cds = (
search_cd_chute_if_auto(bs)
if drogue.find("cd").text == "auto"
else float(drogue.find("cd").text)
)
drogue_deploy_delay = float(drogue.find("deploydelay").text)
drogue_code = f'Drogue = Calisto.addParachute(\n "Drogue",\n CdS=parameters["DrogueCds{idx}"],\n trigger=drogueTrigger,\n samplingRate=105,\n lag=parameters["DrogueDeployDelay{idx}"],\n noise=(0, 8.3, 0.5),\n)'
drogue_area = np.pi * float(drogue.find("diameter").text) ** 2 / 4

drogue_trigger = "def drogueTrigger(p, y):\n # p = pressure\n # y = [x, y, z, vx, vy, vz, e0, e1, e2, e3, w1, w2, w3]\n # activate drogue when vz < 0 m/s.\n return True if y[5] < 0 else False\n\n\n"
chute_cell_code += f"{drogue_trigger}\n\n{drogue_code}\n"
flight_parameters.update(
{
f"DrogueCds{idx}": drogue_area * drogue_cds,
f"DrogueDeployDelay{idx}": drogue_deploy_delay,
}
)

# TODO: rail_button, tuple for launch date

nb["cells"][4][
"source"
] = f'%matplotlib widget\n\nimport json\n\nparameters = json.loads(open("parameters.json").read())'
nb["cells"][15]["source"] = generate_motor_code(
path,
burnout_position,
burnout_time,
thrust_vect,
time_vector,
propelant_mass,
motor_radius,
tube_length,
)
nb["cells"][20][
"source"
] = f'Calisto = Rocket(\n motor=Pro75M1670,\n radius=parameters["radius"],\n mass=parameters["rocketMass"],\n inertiaI=parameters["inertiaI"],\n inertiaZ=parameters["inertiaZ"],\n distanceRocketNozzle=parameters["distanceRocketNozzle"],\n distanceRocketPropellant=parameters["MotorCm"],\n powerOffDrag="drag_coefficient.csv",\n powerOnDrag="drag_coefficient.csv",\n)\n\nCalisto.setRailButtons([0.2, -0.5])'

nb["cells"][23]["source"] = f"{nosecone}\n\n{trapezoidal_fin}\n\n{tail}"

nb["cells"][27]["source"] = chute_cell_code

nb["cells"][7][
"source"
] = f'Env = Environment(\n railLength=parameters["railLength"], latitude=39.3897, longitude=-8.28896388889, elevation=parameters["elevation"]\n)'
nb["cells"][30][
"source"
] = f'TestFlight = Flight(rocket=Calisto, environment=Env, inclination=parameters["inclination"], heading=parameters["heading"])'
print(np.max(altitude_vector))

with open(f"{path}parameters.json", "w") as convert_file:
convert_file.write(json.dumps(flight_parameters))

nbformat.write(nb, f"{path}Simulation.ipynb")


def trapezoidal_fin_code(fin, element, idx):
trapezoidal_fin = f'Calisto.addTrapezoidalFins(parameters["finN{idx}"], rootChord=parameters["finRootChord{idx}"], tipChord=parameters["finTipChord{idx}"], span=parameters["finSpan{idx}"], distanceToCM=parameters["finDistanceToCm{idx}"], sweepAngle=parameters.get("finSweepAngle{idx}"), sweepLength=parameters.get("finSweepLength{idx}"))\n\n'
return trapezoidal_fin


def generate_nosecone_code(bs, cm):
nosecone = f'NoseCone = Calisto.addNose(length=parameters["noseLength"], kind=parameters["noseShape"], distanceToCM=parameters["noseDistanceToCM"])'
return nosecone


def generate_motor_code(
path,
burnout_position,
burnout_time,
thrust_vect,
time_vector,
propelant_mass,
motor_radius,
motor_height,
):
code = f'Pro75M1670 = SolidMotor(\n thrustSource="thrust_source.csv",\n burnOut=parameters["burnOut"],\n grainNumber=1,\n grainSeparation=0,\n grainDensity=parameters["grainDensity"],\n grainOuterRadius=parameters["grainOuterRadius"],\n grainInitialInnerRadius=parameters["grainInitialInnerRadius"],\n grainInitialHeight=parameters["grainInitialHeight"],\n nozzleRadius=parameters["nozzleRadius"],\n throatRadius=parameters["throatRadius"],\n interpolationMethod="linear",\n)'
return code


with orhelper.OpenRocketInstance() as instance:
nb_file = "./docs/notebooks/getting_started.ipynb"
generate(ork_file, nb_file, eng_file, instance)
122 changes: 122 additions & 0 deletions open_rocket_serializer/_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import logging
from pathlib import Path
from zipfile import BadZipFile, ZipFile

from bs4 import BeautifulSoup

logger = logging.getLogger(__name__)


def extract_ork_from_zip(zip_path: Path, extract_dir: Path) -> Path:
"""Extracts .ork file from a zip archive and returns its path.
This is important because sometimes the .xml file is stuck inside the .ork
file. The function extracts the .ork (.xml) file from the zip (.ork) file
and returns its path as a Path object.
For illustration: if you try to open the .ork file with a text editor and
you see a bunch of weird characters, it is probably a "zip" file. You can
try to rename the file to .zip and open it with a zip extractor.
Parameters
----------
zip_path : Path
The path to the .zip file.
extract_dir : Path
The path to the directory where the .ork file will be extracted.
Returns
-------
Path
The path to the extracted .ork file.
"""
try:
with ZipFile(zip_path) as zf:
zf.extract("rocket.ork", path=extract_dir)
logger.info(
'Successfully extracted rocket.ork from "%s" to "%s"',
zip_path.as_posix(),
extract_dir.as_posix(),
)
return extract_dir / "rocket.ork"
except BadZipFile:
logger.warning(
'The file "%s" seems to be a direct .ork file and not a compressed archive.',
zip_path.as_posix(),
)
return zip_path
except Exception as e:
logger.error(
'Error while extracting data from "%s": %s', zip_path.as_posix(), e
)
raise


def parse_ork_file(ork_path: Path):
"""Parses the .ork file and returns BeautifulSoup and a list of datapoints.
Parameters
----------
ork_path : Path
The path to the .ork file.
Returns
-------
BeautifulSoup
The BeautifulSoup object.
"""
try:
with open(ork_path, encoding="utf-8") as file:
bs = BeautifulSoup(file, features="xml")
datapoints = bs.findAll("datapoint")
logger.info(
"Successfully parsed .ork file at '%s' with %d datapoints",
ork_path.as_posix(),
len(datapoints),
)
return bs, datapoints
except UnicodeDecodeError as exc:
error_msg = (
"The .ork file is not in UTF-8."
+ "Please open the .ork file in a text editor and save it as UTF-8."
)
logger.error(error_msg)
raise UnicodeDecodeError(error_msg) from exc
except Exception as e:
logger.error("Error while parsing the file '%s': %s", ork_path.as_posix(), e)
raise e


def _dict_to_string(dictionary, indent=0):
"""Converts a dictionary to a string.
Parameters
----------
dictionary : dict
Dictionary to be converted.
indent : int, optional
Indentation level, by default 0.
Returns
-------
str
String representation of the dictionary.
Examples
--------
>>> from open_rocket_serializer._helpers import _dict_to_string
>>> _dict_to_string({"a": 1, "b": {"c": 2}})
" a: 1\n b: \n c: 2\n"
"""
string = ""
for key, value in dictionary.items():
string += " " * indent + str(key) + ": "
if isinstance(value, dict):
string += "\n" + _dict_to_string(value, indent + 4)
else:
string += str(value) + "\n"
return string


# if __name__ == "__main__":
# import doctest

# doctest.testmod()
Loading

0 comments on commit 5e15ad4

Please sign in to comment.