Skip to content

Commit 4b906d4

Browse files
aasitvora99claude
andauthored
ENH: new dimensions route to get rocket geometry (#71)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 7c25e05 commit 4b906d4

13 files changed

Lines changed: 878 additions & 9 deletions

File tree

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ pymongo>=4.15
99
jsonpickle
1010
gunicorn
1111
uvicorn
12-
git+https://github.com/RocketPy-Team/RocketPy.git@develop
12+
rocketpy
1313
uptrace
1414
opentelemetry.instrumentation.fastapi
1515
opentelemetry.instrumentation.requests

src/controllers/motor.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
ControllerBase,
33
controller_exception_handler,
44
)
5-
from src.views.motor import MotorSimulation
5+
from src.views.motor import MotorSimulation, MotorDrawingGeometryView
66
from src.models.motor import MotorModel
77
from src.services.motor import MotorService
88

@@ -57,3 +57,27 @@ async def get_motor_simulation(self, motor_id: str) -> MotorSimulation:
5757
motor = await self.get_motor_by_id(motor_id)
5858
motor_service = MotorService.from_motor_model(motor.motor)
5959
return motor_service.get_motor_simulation()
60+
61+
@controller_exception_handler
62+
async def get_motor_drawing_geometry(
63+
self, motor_id: str
64+
) -> MotorDrawingGeometryView:
65+
"""
66+
Build the motor-only drawing-geometry payload for a persisted motor.
67+
68+
Renders the motor at its own coordinate origin (motor_position=0,
69+
parent_csys=1) so the playground can show a motor in isolation.
70+
71+
Args:
72+
motor_id: str
73+
74+
Returns:
75+
views.MotorDrawingGeometryView
76+
77+
Raises:
78+
HTTP 404 Not Found: If the motor does not exist in the database.
79+
HTTP 422: If the motor has no drawable geometry.
80+
"""
81+
motor = await self.get_motor_by_id(motor_id)
82+
motor_service = MotorService.from_motor_model(motor.motor)
83+
return motor_service.get_drawing_geometry()

src/controllers/rocket.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@
44
ControllerBase,
55
controller_exception_handler,
66
)
7-
from src.views.rocket import RocketSimulation, RocketCreated
7+
from src.views.rocket import (
8+
RocketSimulation,
9+
RocketCreated,
10+
RocketDrawingGeometry,
11+
)
812
from src.models.motor import MotorModel
913
from src.models.rocket import (
1014
RocketModel,
@@ -75,6 +79,27 @@ async def get_rocketpy_rocket_binary(self, rocket_id: str) -> bytes:
7579
rocket_service = RocketService.from_rocket_model(rocket.rocket)
7680
return rocket_service.get_rocket_binary()
7781

82+
@controller_exception_handler
83+
async def get_rocket_drawing_geometry(
84+
self, rocket_id: str
85+
) -> RocketDrawingGeometry:
86+
"""
87+
Build the drawing geometry payload for a persisted rocket.
88+
89+
Args:
90+
rocket_id: str
91+
92+
Returns:
93+
views.RocketDrawingGeometry
94+
95+
Raises:
96+
HTTP 404 Not Found: If the rocket does not exist in the database.
97+
HTTP 422: If the rocket has no aerodynamic surfaces to draw.
98+
"""
99+
rocket = await self.get_rocket_by_id(rocket_id)
100+
rocket_service = RocketService.from_rocket_model(rocket.rocket)
101+
return rocket_service.get_drawing_geometry()
102+
78103
@controller_exception_handler
79104
async def get_rocket_simulation(
80105
self,

src/models/motor.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,22 @@ def validate_motor_kind(self):
8282
)
8383
return self
8484

85+
@model_validator(mode='after')
86+
def validate_dry_inertia_for_kind(self):
87+
# RocketPy's SolidMotor/LiquidMotor/HybridMotor require dry_inertia with no default.
88+
# Only GenericMotor accepts (0, 0, 0). Surface a clear error at the API boundary
89+
# instead of letting RocketPy crash deep in construction.
90+
if self.motor_kind != MotorKinds.GENERIC and self.dry_inertia == (
91+
0,
92+
0,
93+
0,
94+
):
95+
raise ValueError(
96+
f"dry_inertia is required for {self.motor_kind} motors "
97+
f"and must be explicitly provided (cannot be (0, 0, 0))."
98+
)
99+
return self
100+
85101
@staticmethod
86102
def UPDATED():
87103
return

src/models/sub/tanks.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ class MotorTank(BaseModel):
2222
liquid: TankFluids
2323
flux_time: Tuple[float, float]
2424
position: float
25-
discretize: int
25+
# discretize is optional in RocketPy's Tank classes (defaults to 100).
26+
discretize: int = 100
2627

2728
# Level based tank parameters
2829
liquid_height: Optional[float] = None

src/routes/motor.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
MotorSimulation,
1010
MotorCreated,
1111
MotorRetrieved,
12+
MotorDrawingGeometryView,
1213
)
1314
from src.models.motor import MotorModel
1415
from src.dependencies import MotorControllerDep
@@ -138,3 +139,24 @@ async def get_motor_simulation(
138139
"""
139140
with tracer.start_as_current_span("get_motor_simulation"):
140141
return await controller.get_motor_simulation(motor_id)
142+
143+
144+
@router.get("/{motor_id}/drawing-geometry")
145+
async def get_motor_drawing_geometry(
146+
motor_id: str,
147+
controller: MotorControllerDep,
148+
) -> MotorDrawingGeometryView:
149+
"""
150+
Returns motor-only drawing geometry so a frontend can render the
151+
motor in isolation. The payload mirrors what the motor portion of
152+
`rocketpy.Rocket.draw()` would produce, but at the motor's own
153+
coordinate origin rather than embedded inside a rocket.
154+
155+
Use `GET /rockets/{rocket_id}/drawing-geometry` instead when the
156+
motor should be shown inside a complete rocket.
157+
158+
## Args
159+
``` motor_id: Motor ID ```
160+
"""
161+
with tracer.start_as_current_span("get_motor_drawing_geometry"):
162+
return await controller.get_motor_drawing_geometry(motor_id)

src/routes/rocket.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
RocketSimulation,
1010
RocketCreated,
1111
RocketRetrieved,
12+
RocketDrawingGeometry,
1213
)
1314
from src.models.rocket import (
1415
RocketModel,
@@ -181,3 +182,23 @@ async def simulate_rocket(
181182
"""
182183
with tracer.start_as_current_span("get_rocket_simulation"):
183184
return await controller.get_rocket_simulation(rocket_id)
185+
186+
187+
@router.get("/{rocket_id}/drawing-geometry")
188+
async def get_rocket_drawing_geometry(
189+
rocket_id: str,
190+
controller: RocketControllerDep,
191+
) -> RocketDrawingGeometry:
192+
"""
193+
Returns structured drawing geometry for the rocket so that a frontend
194+
can redraw exactly what rocketpy.Rocket.draw() would render.
195+
196+
Response contains shape coordinate arrays for each aerodynamic surface,
197+
tube segments, motor polygons (nozzle, chamber, grains, tanks, outline),
198+
rail-button positions, CG/CP at t=0, sensors, and overall drawing bounds.
199+
200+
## Args
201+
``` rocket_id: Rocket ID ```
202+
"""
203+
with tracer.start_as_current_span("get_rocket_drawing_geometry"):
204+
return await controller.get_rocket_drawing_geometry(rocket_id)

0 commit comments

Comments
 (0)