symcad.parts.fairing.CylinderWithConicalEnds

  1#!/usr/bin/env python3
  2# Copyright (C) 2022, Will Hedgecock
  3#
  4# This program is free software: you can redistribute it and/or modify
  5# it under the terms of the GNU General Public License as published by
  6# the Free Software Foundation, either version 3 of the License, or
  7# (at your option) any later version.
  8#
  9# This program is distributed in the hope that it will be useful,
 10# but WITHOUT ANY WARRANTY; without even the implied warranty of
 11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 12# GNU General Public License for more details.
 13#
 14# You should have received a copy of the GNU General Public License
 15# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 16
 17from __future__ import annotations
 18from PyFreeCAD.FreeCAD import FreeCAD, Part
 19from typing import Dict, Optional, Tuple, Union
 20from sympy import Expr, Symbol, sqrt, atan2, sin, tan
 21from . import FairingShape
 22import math
 23
 24class CylinderWithConicalEnds(FairingShape):
 25   """Model representing a cylindrical fairing shape with conical end sections.
 26
 27   By default, the part is oriented in the following way:
 28
 29   ![FairingCylinderWithConicalEnds](https://symbench.github.io/SymCAD/images/FairingCylinderWithConicalEnds.png)
 30   """
 31
 32   # Constructor ----------------------------------------------------------------------------------
 33
 34   def __init__(self, identifier: str, material_density_kg_m3: Optional[float] = 1.0) -> None:
 35      """Initializes a fairing shape defined by a cylindrical center with conical end sections.
 36
 37      Parameters
 38      ----------
 39      identifier : `str`
 40         Unique identifying name for the object.
 41      material_density_kg_m3 : `float`, optional, default=1.0
 42         Uniform material density in `kg/m^3` to be used in mass property calculations.
 43      """
 44      super().__init__(identifier, self.__create_cad__, None, material_density_kg_m3)
 45      setattr(self.geometry, 'nose_tip_radius', Symbol(self.name + '_nose_tip_radius'))
 46      setattr(self.geometry, 'nose_length', Symbol(self.name + '_nose_length'))
 47      setattr(self.geometry, 'body_radius', Symbol(self.name + '_body_radius'))
 48      setattr(self.geometry, 'body_length', Symbol(self.name + '_body_length'))
 49      setattr(self.geometry, 'tail_tip_radius', Symbol(self.name + '_tail_tip_radius'))
 50      setattr(self.geometry, 'tail_length', Symbol(self.name + '_tail_length'))
 51      setattr(self.geometry, 'thickness', Symbol(self.name + '_thickness'))
 52
 53
 54   # CAD generation function ----------------------------------------------------------------------
 55
 56   @staticmethod
 57   def __create_cad__(params: Dict[str, float], _fully_displace: bool) -> Part.Solid:
 58      """Scripted CAD generation method for a `CylinderWithConicalEnds`."""
 59      nose_length_angle = math.atan2(params['nose_length'],
 60                                     params['body_radius'] - params['nose_tip_radius'])
 61      tail_length_angle = math.atan2(params['tail_length'],
 62                                     params['body_radius'] - params['tail_tip_radius'])
 63      outer_nose_tip_radius_mm = 1000.0 * params['nose_tip_radius']
 64      outer_nose_length_mm = 1000.0 * params['nose_length']
 65      outer_body_radius_mm = 1000.0 * params['body_radius']
 66      body_length_mm = 1000.0 * params['body_length']
 67      outer_tail_tip_radius_mm = 1000.0 * params['tail_tip_radius']
 68      outer_tail_length_mm = 1000.0 * params['tail_length']
 69      thickness_mm = 1000.0 * params['thickness']
 70      inner_nose_length_mm = outer_nose_length_mm - thickness_mm
 71      inner_tail_length_mm = outer_tail_length_mm - thickness_mm
 72      inner_body_radius_mm = outer_body_radius_mm - thickness_mm
 73      inner_nose_body_radius_mm = outer_body_radius_mm - \
 74                                  (thickness_mm / math.sin(nose_length_angle))
 75      inner_tail_body_radius_mm = outer_body_radius_mm - \
 76                                  (thickness_mm / math.sin(tail_length_angle))
 77      inner_nose_tip_radius_mm = inner_nose_body_radius_mm - \
 78                                 (inner_nose_length_mm / math.tan(nose_length_angle))
 79      inner_tail_tip_radius_mm = inner_tail_body_radius_mm - \
 80                                 (inner_tail_length_mm / math.tan(tail_length_angle))
 81      outer_nose = \
 82         Part.makeCone(outer_body_radius_mm, outer_nose_tip_radius_mm, outer_nose_length_mm)
 83      outer_tail = \
 84         Part.makeCone(outer_body_radius_mm, outer_tail_tip_radius_mm, outer_tail_length_mm)
 85      body2d = Part.makeRuledSurface(Part.makeCircle(outer_body_radius_mm),
 86                                       Part.makeCircle(inner_body_radius_mm))
 87      body = body2d.extrude(FreeCAD.Vector(0, 0, body_length_mm))
 88      inner_nose = \
 89         Part.makeCone(inner_nose_body_radius_mm, inner_nose_tip_radius_mm, inner_nose_length_mm)
 90      inner_tail = \
 91         Part.makeCone(inner_tail_body_radius_mm, inner_tail_tip_radius_mm, inner_tail_length_mm)
 92      nose = outer_nose.cut(inner_nose)
 93      tail = outer_tail.cut(inner_tail)
 94      nose.Placement = FreeCAD.Placement(FreeCAD.Vector(0, 0, 0), FreeCAD.Rotation(0, 270.0, 0))
 95      body.Placement = FreeCAD.Placement(FreeCAD.Vector(0, 0, 0), FreeCAD.Rotation(0, 90.0, 0))
 96      tail.Placement = FreeCAD.Placement(FreeCAD.Vector(body_length_mm, 0, 0),
 97                                         FreeCAD.Rotation(0, 90.0, 0))
 98      return nose.generalFuse([body, tail])[0]
 99
100
101   # Geometry setter ------------------------------------------------------------------------------
102
103   def set_geometry(self, *, nose_tip_radius_m: Union[float, None],
104                             nose_length_m: Union[float, None],
105                             body_radius_m: Union[float, None],
106                             body_length_m: Union[float, None],
107                             tail_tip_radius_m: Union[float, None],
108                             tail_length_m: Union[float, None],
109                             thickness_m: Union[float, None]) -> CylinderWithConicalEnds:
110      """Sets the physical geometry of the current `CylinderWithConicalEnds` object.
111
112      See the `CylinderWithConicalEnds` class documentation for a description of each
113      geometric parameter.
114      """
115      self.geometry.set(nose_tip_radius=nose_tip_radius_m,
116                        nose_length=nose_length_m,
117                        body_radius=body_radius_m,
118                        body_length=body_length_m,
119                        tail_tip_radius=tail_tip_radius_m,
120                        tail_length=tail_length_m,
121                        thickness=thickness_m)
122      return self
123
124   def get_geometric_parameter_bounds(self, parameter: str) -> Tuple[float, float]:
125      parameter_bounds = {
126         'nose_tip_radius': (0.01, 1.5),
127         'nose_length': (0.1, 3.0),
128         'body_radius': (0.250, 1.5),
129         'body_length': (0.015, 3.0),
130         'tail_tip_radius': (0.01, 1.5),
131         'tail_length': (0.1, 3.0),
132         'thickness': (0.0, 0.05)
133      }
134      return parameter_bounds.get(parameter, (0.0, 0.0))
135
136
137   # Geometric properties -------------------------------------------------------------------------
138
139   @property
140   def material_volume(self) -> Union[float, Expr]:
141      nose_volume = (math.pi * self.geometry.nose_length / 3.0) * \
142             (self.geometry.body_radius**2 + self.geometry.nose_tip_radius**2 +
143              self.geometry.body_radius * self.geometry.nose_tip_radius)
144      tail_volume = (math.pi * self.geometry.tail_length / 3.0) * \
145             (self.geometry.body_radius**2 + self.geometry.tail_tip_radius**2 +
146              self.geometry.body_radius * self.geometry.tail_tip_radius)
147      body_volume = math.pi * self.geometry.body_radius**2 * self.geometry.body_length
148      nose_angle = atan2(self.geometry.nose_length,
149                         self.geometry.body_radius - self.geometry.nose_tip_radius)
150      nose_inner_height = self.geometry.nose_length - self.geometry.thickness
151      nose_inner_body_radius = self.geometry.body_radius - \
152                               (self.geometry.thickness / sin(nose_angle))
153      nose_inner_tip_radius = nose_inner_body_radius - (nose_inner_height / tan(nose_angle))
154      nose_inner_volume = ((nose_inner_height * math.pi / 3.0) * \
155                           (nose_inner_body_radius**2 + nose_inner_tip_radius**2 +
156                              (nose_inner_body_radius * nose_inner_tip_radius)))
157      tail_angle = atan2(self.geometry.tail_length,
158                         self.geometry.body_radius - self.geometry.tail_tip_radius)
159      tail_inner_height = self.geometry.tail_length - self.geometry.thickness
160      tail_inner_body_radius = self.geometry.body_radius - \
161                               (self.geometry.thickness / sin(tail_angle))
162      tail_inner_tip_radius = tail_inner_body_radius - (tail_inner_height / tan(tail_angle))
163      tail_inner_volume = ((tail_inner_height * math.pi / 3.0) * \
164                           (tail_inner_body_radius**2 + tail_inner_tip_radius**2 +
165                              (tail_inner_body_radius * tail_inner_tip_radius)))
166      body_inner_volume = math.pi * (self.geometry.body_radius - self.geometry.thickness)**2 * \
167                          self.geometry.body_length
168      return nose_volume + tail_volume + body_volume - \
169         nose_inner_volume - tail_inner_volume - body_inner_volume
170
171   @property
172   def displaced_volume(self) -> Union[float, Expr]:
173      return self.material_volume
174
175   @property
176   def surface_area(self) -> Union[float, Expr]:
177      nose_area = (math.pi * self.geometry.nose_tip_radius**2) + \
178                  (math.pi * (self.geometry.body_radius + self.geometry.nose_tip_radius) *
179                  sqrt(self.geometry.nose_length**2 +
180                              (self.geometry.body_radius - self.geometry.nose_tip_radius)**2))
181      tail_area = (math.pi * self.geometry.tail_tip_radius**2) + \
182                  (math.pi * (self.geometry.body_radius + self.geometry.tail_tip_radius) *
183                  sqrt(self.geometry.tail_length**2 +
184                              (self.geometry.body_radius - self.geometry.tail_tip_radius)**2))
185      body_area = 2.0 * math.pi * self.geometry.body_radius * self.geometry.body_length
186      return nose_area + tail_area + body_area
187
188   @property
189   def unoriented_center_of_gravity(self) -> Tuple[Union[float, Expr],
190                                                   Union[float, Expr],
191                                                   Union[float, Expr]]:
192      nose_volume = (math.pi * self.geometry.nose_length / 3.0) * \
193             (self.geometry.body_radius**2 + self.geometry.nose_tip_radius**2 +
194              self.geometry.body_radius * self.geometry.nose_tip_radius)
195      tail_volume = (math.pi * self.geometry.tail_length / 3.0) * \
196             (self.geometry.body_radius**2 + self.geometry.tail_tip_radius**2 +
197              self.geometry.body_radius * self.geometry.tail_tip_radius)
198      body_volume = math.pi * self.geometry.body_radius**2 * self.geometry.body_length
199      nose_angle = atan2(self.geometry.nose_length,
200                         self.geometry.body_radius - self.geometry.nose_tip_radius)
201      nose_inner_height = self.geometry.nose_length - self.geometry.thickness
202      nose_inner_body_radius = self.geometry.body_radius - \
203                               (self.geometry.thickness / sin(nose_angle))
204      nose_inner_tip_radius = nose_inner_body_radius - (nose_inner_height / tan(nose_angle))
205      nose_inner_volume = ((nose_inner_height * math.pi / 3.0) * \
206                           (nose_inner_body_radius**2 + nose_inner_tip_radius**2 +
207                              (nose_inner_body_radius * nose_inner_tip_radius)))
208      tail_angle = atan2(self.geometry.tail_length,
209                         self.geometry.body_radius - self.geometry.tail_tip_radius)
210      tail_inner_height = self.geometry.tail_length - self.geometry.thickness
211      tail_inner_body_radius = self.geometry.body_radius - \
212                               (self.geometry.thickness / sin(tail_angle))
213      tail_inner_tip_radius = tail_inner_body_radius - (tail_inner_height / tan(tail_angle))
214      tail_inner_volume = ((tail_inner_height * math.pi / 3.0) * \
215                           (tail_inner_body_radius**2 + tail_inner_tip_radius**2 +
216                              (tail_inner_body_radius * tail_inner_tip_radius)))
217      body_inner_volume = math.pi * (self.geometry.body_radius - self.geometry.thickness)**2 * \
218                          self.geometry.body_length
219      nose_volume = nose_volume - nose_inner_volume
220      body_volume = body_volume - body_inner_volume
221      tail_volume = tail_volume - tail_inner_volume
222      total_volume = nose_volume + body_volume + tail_volume
223      nose_cg_x = self.geometry.nose_length - \
224         ((self.geometry.nose_length *
225             (self.geometry.body_radius +
226                (0.5 * self.geometry.nose_tip_radius**2) +
227                (2.0 * self.geometry.nose_tip_radius))) /
228          (3.0 * (self.geometry.body_radius + self.geometry.nose_tip_radius)))
229      tail_cg_x = self.geometry.nose_length + self.geometry.body_length + \
230         ((self.geometry.tail_length *
231             (self.geometry.body_radius +
232                (0.5 * self.geometry.tail_tip_radius**2) +
233                (2.0 * self.geometry.tail_tip_radius))) /
234          (3.0 * (self.geometry.body_radius + self.geometry.tail_tip_radius)))
235      body_cg_x = self.geometry.nose_length + (0.5 * self.geometry.body_length)
236      cg_x = ((nose_cg_x * nose_volume) + (body_cg_x * body_volume) + (tail_cg_x * tail_volume)) \
237             / total_volume
238      return (cg_x,
239              self.geometry.body_radius,
240              self.geometry.body_radius)
241
242   @property
243   def unoriented_center_of_buoyancy(self) -> Tuple[Union[float, Expr],
244                                                    Union[float, Expr],
245                                                    Union[float, Expr]]:
246      return self.unoriented_center_of_gravity
247
248   @property
249   def unoriented_length(self) -> Union[float, Expr]:
250      return self.geometry.nose_length + self.geometry.body_length + self.geometry.tail_length
251
252   @property
253   def unoriented_width(self) -> Union[float, Expr]:
254      return 2.0 * self.geometry.body_radius
255
256   @property
257   def unoriented_height(self) -> Union[float, Expr]:
258      return self.unoriented_width
259
260   @property
261   def oriented_length(self) -> Union[float, Expr]:
262      # TODO: Implement this
263      return 0
264
265   @property
266   def oriented_width(self) -> Union[float, Expr]:
267      # TODO: Implement this
268      return 0
269
270   @property
271   def oriented_height(self) -> Union[float, Expr]:
272      # TODO: Implement this
273      return 0
class CylinderWithConicalEnds(symcad.parts.fairing.FairingShape):
 25class CylinderWithConicalEnds(FairingShape):
 26   """Model representing a cylindrical fairing shape with conical end sections.
 27
 28   By default, the part is oriented in the following way:
 29
 30   ![FairingCylinderWithConicalEnds](https://symbench.github.io/SymCAD/images/FairingCylinderWithConicalEnds.png)
 31   """
 32
 33   # Constructor ----------------------------------------------------------------------------------
 34
 35   def __init__(self, identifier: str, material_density_kg_m3: Optional[float] = 1.0) -> None:
 36      """Initializes a fairing shape defined by a cylindrical center with conical end sections.
 37
 38      Parameters
 39      ----------
 40      identifier : `str`
 41         Unique identifying name for the object.
 42      material_density_kg_m3 : `float`, optional, default=1.0
 43         Uniform material density in `kg/m^3` to be used in mass property calculations.
 44      """
 45      super().__init__(identifier, self.__create_cad__, None, material_density_kg_m3)
 46      setattr(self.geometry, 'nose_tip_radius', Symbol(self.name + '_nose_tip_radius'))
 47      setattr(self.geometry, 'nose_length', Symbol(self.name + '_nose_length'))
 48      setattr(self.geometry, 'body_radius', Symbol(self.name + '_body_radius'))
 49      setattr(self.geometry, 'body_length', Symbol(self.name + '_body_length'))
 50      setattr(self.geometry, 'tail_tip_radius', Symbol(self.name + '_tail_tip_radius'))
 51      setattr(self.geometry, 'tail_length', Symbol(self.name + '_tail_length'))
 52      setattr(self.geometry, 'thickness', Symbol(self.name + '_thickness'))
 53
 54
 55   # CAD generation function ----------------------------------------------------------------------
 56
 57   @staticmethod
 58   def __create_cad__(params: Dict[str, float], _fully_displace: bool) -> Part.Solid:
 59      """Scripted CAD generation method for a `CylinderWithConicalEnds`."""
 60      nose_length_angle = math.atan2(params['nose_length'],
 61                                     params['body_radius'] - params['nose_tip_radius'])
 62      tail_length_angle = math.atan2(params['tail_length'],
 63                                     params['body_radius'] - params['tail_tip_radius'])
 64      outer_nose_tip_radius_mm = 1000.0 * params['nose_tip_radius']
 65      outer_nose_length_mm = 1000.0 * params['nose_length']
 66      outer_body_radius_mm = 1000.0 * params['body_radius']
 67      body_length_mm = 1000.0 * params['body_length']
 68      outer_tail_tip_radius_mm = 1000.0 * params['tail_tip_radius']
 69      outer_tail_length_mm = 1000.0 * params['tail_length']
 70      thickness_mm = 1000.0 * params['thickness']
 71      inner_nose_length_mm = outer_nose_length_mm - thickness_mm
 72      inner_tail_length_mm = outer_tail_length_mm - thickness_mm
 73      inner_body_radius_mm = outer_body_radius_mm - thickness_mm
 74      inner_nose_body_radius_mm = outer_body_radius_mm - \
 75                                  (thickness_mm / math.sin(nose_length_angle))
 76      inner_tail_body_radius_mm = outer_body_radius_mm - \
 77                                  (thickness_mm / math.sin(tail_length_angle))
 78      inner_nose_tip_radius_mm = inner_nose_body_radius_mm - \
 79                                 (inner_nose_length_mm / math.tan(nose_length_angle))
 80      inner_tail_tip_radius_mm = inner_tail_body_radius_mm - \
 81                                 (inner_tail_length_mm / math.tan(tail_length_angle))
 82      outer_nose = \
 83         Part.makeCone(outer_body_radius_mm, outer_nose_tip_radius_mm, outer_nose_length_mm)
 84      outer_tail = \
 85         Part.makeCone(outer_body_radius_mm, outer_tail_tip_radius_mm, outer_tail_length_mm)
 86      body2d = Part.makeRuledSurface(Part.makeCircle(outer_body_radius_mm),
 87                                       Part.makeCircle(inner_body_radius_mm))
 88      body = body2d.extrude(FreeCAD.Vector(0, 0, body_length_mm))
 89      inner_nose = \
 90         Part.makeCone(inner_nose_body_radius_mm, inner_nose_tip_radius_mm, inner_nose_length_mm)
 91      inner_tail = \
 92         Part.makeCone(inner_tail_body_radius_mm, inner_tail_tip_radius_mm, inner_tail_length_mm)
 93      nose = outer_nose.cut(inner_nose)
 94      tail = outer_tail.cut(inner_tail)
 95      nose.Placement = FreeCAD.Placement(FreeCAD.Vector(0, 0, 0), FreeCAD.Rotation(0, 270.0, 0))
 96      body.Placement = FreeCAD.Placement(FreeCAD.Vector(0, 0, 0), FreeCAD.Rotation(0, 90.0, 0))
 97      tail.Placement = FreeCAD.Placement(FreeCAD.Vector(body_length_mm, 0, 0),
 98                                         FreeCAD.Rotation(0, 90.0, 0))
 99      return nose.generalFuse([body, tail])[0]
100
101
102   # Geometry setter ------------------------------------------------------------------------------
103
104   def set_geometry(self, *, nose_tip_radius_m: Union[float, None],
105                             nose_length_m: Union[float, None],
106                             body_radius_m: Union[float, None],
107                             body_length_m: Union[float, None],
108                             tail_tip_radius_m: Union[float, None],
109                             tail_length_m: Union[float, None],
110                             thickness_m: Union[float, None]) -> CylinderWithConicalEnds:
111      """Sets the physical geometry of the current `CylinderWithConicalEnds` object.
112
113      See the `CylinderWithConicalEnds` class documentation for a description of each
114      geometric parameter.
115      """
116      self.geometry.set(nose_tip_radius=nose_tip_radius_m,
117                        nose_length=nose_length_m,
118                        body_radius=body_radius_m,
119                        body_length=body_length_m,
120                        tail_tip_radius=tail_tip_radius_m,
121                        tail_length=tail_length_m,
122                        thickness=thickness_m)
123      return self
124
125   def get_geometric_parameter_bounds(self, parameter: str) -> Tuple[float, float]:
126      parameter_bounds = {
127         'nose_tip_radius': (0.01, 1.5),
128         'nose_length': (0.1, 3.0),
129         'body_radius': (0.250, 1.5),
130         'body_length': (0.015, 3.0),
131         'tail_tip_radius': (0.01, 1.5),
132         'tail_length': (0.1, 3.0),
133         'thickness': (0.0, 0.05)
134      }
135      return parameter_bounds.get(parameter, (0.0, 0.0))
136
137
138   # Geometric properties -------------------------------------------------------------------------
139
140   @property
141   def material_volume(self) -> Union[float, Expr]:
142      nose_volume = (math.pi * self.geometry.nose_length / 3.0) * \
143             (self.geometry.body_radius**2 + self.geometry.nose_tip_radius**2 +
144              self.geometry.body_radius * self.geometry.nose_tip_radius)
145      tail_volume = (math.pi * self.geometry.tail_length / 3.0) * \
146             (self.geometry.body_radius**2 + self.geometry.tail_tip_radius**2 +
147              self.geometry.body_radius * self.geometry.tail_tip_radius)
148      body_volume = math.pi * self.geometry.body_radius**2 * self.geometry.body_length
149      nose_angle = atan2(self.geometry.nose_length,
150                         self.geometry.body_radius - self.geometry.nose_tip_radius)
151      nose_inner_height = self.geometry.nose_length - self.geometry.thickness
152      nose_inner_body_radius = self.geometry.body_radius - \
153                               (self.geometry.thickness / sin(nose_angle))
154      nose_inner_tip_radius = nose_inner_body_radius - (nose_inner_height / tan(nose_angle))
155      nose_inner_volume = ((nose_inner_height * math.pi / 3.0) * \
156                           (nose_inner_body_radius**2 + nose_inner_tip_radius**2 +
157                              (nose_inner_body_radius * nose_inner_tip_radius)))
158      tail_angle = atan2(self.geometry.tail_length,
159                         self.geometry.body_radius - self.geometry.tail_tip_radius)
160      tail_inner_height = self.geometry.tail_length - self.geometry.thickness
161      tail_inner_body_radius = self.geometry.body_radius - \
162                               (self.geometry.thickness / sin(tail_angle))
163      tail_inner_tip_radius = tail_inner_body_radius - (tail_inner_height / tan(tail_angle))
164      tail_inner_volume = ((tail_inner_height * math.pi / 3.0) * \
165                           (tail_inner_body_radius**2 + tail_inner_tip_radius**2 +
166                              (tail_inner_body_radius * tail_inner_tip_radius)))
167      body_inner_volume = math.pi * (self.geometry.body_radius - self.geometry.thickness)**2 * \
168                          self.geometry.body_length
169      return nose_volume + tail_volume + body_volume - \
170         nose_inner_volume - tail_inner_volume - body_inner_volume
171
172   @property
173   def displaced_volume(self) -> Union[float, Expr]:
174      return self.material_volume
175
176   @property
177   def surface_area(self) -> Union[float, Expr]:
178      nose_area = (math.pi * self.geometry.nose_tip_radius**2) + \
179                  (math.pi * (self.geometry.body_radius + self.geometry.nose_tip_radius) *
180                  sqrt(self.geometry.nose_length**2 +
181                              (self.geometry.body_radius - self.geometry.nose_tip_radius)**2))
182      tail_area = (math.pi * self.geometry.tail_tip_radius**2) + \
183                  (math.pi * (self.geometry.body_radius + self.geometry.tail_tip_radius) *
184                  sqrt(self.geometry.tail_length**2 +
185                              (self.geometry.body_radius - self.geometry.tail_tip_radius)**2))
186      body_area = 2.0 * math.pi * self.geometry.body_radius * self.geometry.body_length
187      return nose_area + tail_area + body_area
188
189   @property
190   def unoriented_center_of_gravity(self) -> Tuple[Union[float, Expr],
191                                                   Union[float, Expr],
192                                                   Union[float, Expr]]:
193      nose_volume = (math.pi * self.geometry.nose_length / 3.0) * \
194             (self.geometry.body_radius**2 + self.geometry.nose_tip_radius**2 +
195              self.geometry.body_radius * self.geometry.nose_tip_radius)
196      tail_volume = (math.pi * self.geometry.tail_length / 3.0) * \
197             (self.geometry.body_radius**2 + self.geometry.tail_tip_radius**2 +
198              self.geometry.body_radius * self.geometry.tail_tip_radius)
199      body_volume = math.pi * self.geometry.body_radius**2 * self.geometry.body_length
200      nose_angle = atan2(self.geometry.nose_length,
201                         self.geometry.body_radius - self.geometry.nose_tip_radius)
202      nose_inner_height = self.geometry.nose_length - self.geometry.thickness
203      nose_inner_body_radius = self.geometry.body_radius - \
204                               (self.geometry.thickness / sin(nose_angle))
205      nose_inner_tip_radius = nose_inner_body_radius - (nose_inner_height / tan(nose_angle))
206      nose_inner_volume = ((nose_inner_height * math.pi / 3.0) * \
207                           (nose_inner_body_radius**2 + nose_inner_tip_radius**2 +
208                              (nose_inner_body_radius * nose_inner_tip_radius)))
209      tail_angle = atan2(self.geometry.tail_length,
210                         self.geometry.body_radius - self.geometry.tail_tip_radius)
211      tail_inner_height = self.geometry.tail_length - self.geometry.thickness
212      tail_inner_body_radius = self.geometry.body_radius - \
213                               (self.geometry.thickness / sin(tail_angle))
214      tail_inner_tip_radius = tail_inner_body_radius - (tail_inner_height / tan(tail_angle))
215      tail_inner_volume = ((tail_inner_height * math.pi / 3.0) * \
216                           (tail_inner_body_radius**2 + tail_inner_tip_radius**2 +
217                              (tail_inner_body_radius * tail_inner_tip_radius)))
218      body_inner_volume = math.pi * (self.geometry.body_radius - self.geometry.thickness)**2 * \
219                          self.geometry.body_length
220      nose_volume = nose_volume - nose_inner_volume
221      body_volume = body_volume - body_inner_volume
222      tail_volume = tail_volume - tail_inner_volume
223      total_volume = nose_volume + body_volume + tail_volume
224      nose_cg_x = self.geometry.nose_length - \
225         ((self.geometry.nose_length *
226             (self.geometry.body_radius +
227                (0.5 * self.geometry.nose_tip_radius**2) +
228                (2.0 * self.geometry.nose_tip_radius))) /
229          (3.0 * (self.geometry.body_radius + self.geometry.nose_tip_radius)))
230      tail_cg_x = self.geometry.nose_length + self.geometry.body_length + \
231         ((self.geometry.tail_length *
232             (self.geometry.body_radius +
233                (0.5 * self.geometry.tail_tip_radius**2) +
234                (2.0 * self.geometry.tail_tip_radius))) /
235          (3.0 * (self.geometry.body_radius + self.geometry.tail_tip_radius)))
236      body_cg_x = self.geometry.nose_length + (0.5 * self.geometry.body_length)
237      cg_x = ((nose_cg_x * nose_volume) + (body_cg_x * body_volume) + (tail_cg_x * tail_volume)) \
238             / total_volume
239      return (cg_x,
240              self.geometry.body_radius,
241              self.geometry.body_radius)
242
243   @property
244   def unoriented_center_of_buoyancy(self) -> Tuple[Union[float, Expr],
245                                                    Union[float, Expr],
246                                                    Union[float, Expr]]:
247      return self.unoriented_center_of_gravity
248
249   @property
250   def unoriented_length(self) -> Union[float, Expr]:
251      return self.geometry.nose_length + self.geometry.body_length + self.geometry.tail_length
252
253   @property
254   def unoriented_width(self) -> Union[float, Expr]:
255      return 2.0 * self.geometry.body_radius
256
257   @property
258   def unoriented_height(self) -> Union[float, Expr]:
259      return self.unoriented_width
260
261   @property
262   def oriented_length(self) -> Union[float, Expr]:
263      # TODO: Implement this
264      return 0
265
266   @property
267   def oriented_width(self) -> Union[float, Expr]:
268      # TODO: Implement this
269      return 0
270
271   @property
272   def oriented_height(self) -> Union[float, Expr]:
273      # TODO: Implement this
274      return 0

Model representing a cylindrical fairing shape with conical end sections.

By default, the part is oriented in the following way:

FairingCylinderWithConicalEnds

CylinderWithConicalEnds(identifier: str, material_density_kg_m3: Optional[float] = 1.0)
35   def __init__(self, identifier: str, material_density_kg_m3: Optional[float] = 1.0) -> None:
36      """Initializes a fairing shape defined by a cylindrical center with conical end sections.
37
38      Parameters
39      ----------
40      identifier : `str`
41         Unique identifying name for the object.
42      material_density_kg_m3 : `float`, optional, default=1.0
43         Uniform material density in `kg/m^3` to be used in mass property calculations.
44      """
45      super().__init__(identifier, self.__create_cad__, None, material_density_kg_m3)
46      setattr(self.geometry, 'nose_tip_radius', Symbol(self.name + '_nose_tip_radius'))
47      setattr(self.geometry, 'nose_length', Symbol(self.name + '_nose_length'))
48      setattr(self.geometry, 'body_radius', Symbol(self.name + '_body_radius'))
49      setattr(self.geometry, 'body_length', Symbol(self.name + '_body_length'))
50      setattr(self.geometry, 'tail_tip_radius', Symbol(self.name + '_tail_tip_radius'))
51      setattr(self.geometry, 'tail_length', Symbol(self.name + '_tail_length'))
52      setattr(self.geometry, 'thickness', Symbol(self.name + '_thickness'))

Initializes a fairing shape defined by a cylindrical center with conical end sections.

Parameters
  • identifier (str): Unique identifying name for the object.
  • material_density_kg_m3 (float, optional, default=1.0): Uniform material density in kg/m^3 to be used in mass property calculations.
def set_geometry( self, *, nose_tip_radius_m: Optional[float], nose_length_m: Optional[float], body_radius_m: Optional[float], body_length_m: Optional[float], tail_tip_radius_m: Optional[float], tail_length_m: Optional[float], thickness_m: Optional[float]) -> CylinderWithConicalEnds:
104   def set_geometry(self, *, nose_tip_radius_m: Union[float, None],
105                             nose_length_m: Union[float, None],
106                             body_radius_m: Union[float, None],
107                             body_length_m: Union[float, None],
108                             tail_tip_radius_m: Union[float, None],
109                             tail_length_m: Union[float, None],
110                             thickness_m: Union[float, None]) -> CylinderWithConicalEnds:
111      """Sets the physical geometry of the current `CylinderWithConicalEnds` object.
112
113      See the `CylinderWithConicalEnds` class documentation for a description of each
114      geometric parameter.
115      """
116      self.geometry.set(nose_tip_radius=nose_tip_radius_m,
117                        nose_length=nose_length_m,
118                        body_radius=body_radius_m,
119                        body_length=body_length_m,
120                        tail_tip_radius=tail_tip_radius_m,
121                        tail_length=tail_length_m,
122                        thickness=thickness_m)
123      return self

Sets the physical geometry of the current CylinderWithConicalEnds object.

See the CylinderWithConicalEnds class documentation for a description of each geometric parameter.

def get_geometric_parameter_bounds(self, parameter: str) -> Tuple[float, float]:
125   def get_geometric_parameter_bounds(self, parameter: str) -> Tuple[float, float]:
126      parameter_bounds = {
127         'nose_tip_radius': (0.01, 1.5),
128         'nose_length': (0.1, 3.0),
129         'body_radius': (0.250, 1.5),
130         'body_length': (0.015, 3.0),
131         'tail_tip_radius': (0.01, 1.5),
132         'tail_length': (0.1, 3.0),
133         'thickness': (0.0, 0.05)
134      }
135      return parameter_bounds.get(parameter, (0.0, 0.0))

Abstract method that must be overridden by a concrete SymPart class to return the minimum and maximum expected bounds for a given geometric parameter.

Parameters
  • parameter (str): Name of the geometric parameter for which to return the minimum and maximum bounds.
Returns
  • Tuple[float, float]: Minimum and maximum bounds for the specified geometric parameter.
material_volume: Union[float, sympy.core.expr.Expr]

Material volume (in m^3) of the SymPart (read-only).

displaced_volume: Union[float, sympy.core.expr.Expr]

Displaced volume (in m^3) of the SymPart (read-only).

surface_area: Union[float, sympy.core.expr.Expr]

Surface/wetted area (in m^2) of the SymPart (read-only).

unoriented_center_of_gravity: Tuple[Union[float, sympy.core.expr.Expr], Union[float, sympy.core.expr.Expr], Union[float, sympy.core.expr.Expr]]

Center of gravity (in m) of the unoriented SymPart (read-only).

unoriented_center_of_buoyancy: Tuple[Union[float, sympy.core.expr.Expr], Union[float, sympy.core.expr.Expr], Union[float, sympy.core.expr.Expr]]

Center of buoyancy (in m) of the unoriented SymPart (read-only).

unoriented_length: Union[float, sympy.core.expr.Expr]

X-axis length (in m) of the bounding box of the unoriented SymPart (read-only).

unoriented_width: Union[float, sympy.core.expr.Expr]

Y-axis width (in m) of the bounding box of the unoriented SymPart (read-only).

unoriented_height: Union[float, sympy.core.expr.Expr]

Z-axis height (in m) of the bounding box of the unoriented SymPart (read-only).

oriented_length: Union[float, sympy.core.expr.Expr]

X-axis length (in m) of the bounding box of the oriented SymPart (read-only).

oriented_width: Union[float, sympy.core.expr.Expr]

Y-axis length (in m) of the bounding box of the oriented SymPart (read-only).

oriented_height: Union[float, sympy.core.expr.Expr]

Z-axis length (in m) of the bounding box of the oriented SymPart (read-only).