symcad.parts.composite.SemiellipsoidalCapsule

  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
 21from . import CompositeShape
 22import math
 23
 24class SemiellipsoidalCapsule(CompositeShape):
 25   """Model representing a parameteric capsule with semiellipsoidal endcaps.
 26
 27   By default, the capsule is oriented such that the endcaps are aligned with the x-axis:
 28
 29   ![SemiellipsoidalCapsule](https://symbench.github.io/SymCAD/images/SemiellipsoidalCapsule.png)
 30
 31   The minor axis of each endcap spans the open face of the endcap to its tip, while the major
 32   axis spans the radius of the open face of the endcap itself.
 33
 34   The `geometry` of this shape includes the following parameters:
 35
 36   - `cylinder_radius`: Outer radius (in `m`) of the center cylindrical part of the Capsule
 37   - `cylinder_length`: Length (in `m`) of the center cylindrical part of the Capsule
 38   - `cylinder_thickness`: Thickness (in `m`) of the cylindrical shell of the Capsule
 39   - `endcap_radius`: Radius (in `m`) of the semiellipsoidal endcaps of the Capsule
 40   - `endcap_thickness`: Thickness (in `m`) of the semiellipsoidal endcaps of the Capsule
 41
 42   Note that the above dimensions should be interpreted as if the capsule is unrotated. In other
 43   words, any shape rotation takes place *after* the capsule dimensions have been specified.
 44   """
 45
 46   # Constructor ----------------------------------------------------------------------------------
 47
 48   def __init__(self, identifier: str,
 49                      material_density_kg_m3: Optional[float] = 1.0,
 50                      major_minor_axis_ratio: Optional[float] = 2.0) -> None:
 51      """Initializes a parametric capsule object with semiellipsoidal endcaps.
 52
 53      The `major_minor_axis_ratio` parameter is used to determine the relative axis lengths of a
 54      semiellipsoidal endcap when one or more of its geometric parameters are symbolic. If all
 55      parameters are concretely defined, then this parameter is meaningless.
 56
 57      The minor axis of this shape spans the open face of the endcap to its tip, while the major
 58      axis spans the radius of the open face of the endcap itself.
 59
 60      Parameters
 61      ----------
 62      identifier : `str`
 63         Unique identifying name for the object.
 64      material_density_kg_m3 : `float`, optional, default=1.0
 65         Uniform material density in `kg/m^3` to be used in mass property calculations.
 66      major_minor_axis_ratio : `float`, optional, default=2.0
 67         Desired major-to-minor axis ratio of the semiellipsoid.
 68      """
 69      super().__init__(identifier, self.__create_cad__, None, material_density_kg_m3)
 70      setattr(self.geometry, 'cylinder_radius', Symbol(self.name + '_cylinder_radius'))
 71      setattr(self.geometry, 'cylinder_length', Symbol(self.name + '_cylinder_length'))
 72      setattr(self.geometry, 'cylinder_thickness', Symbol(self.name + '_cylinder_thickness'))
 73      setattr(self.geometry, 'endcap_radius', Symbol(self.name + '_endcap_radius'))
 74      setattr(self.geometry, 'endcap_thickness', Symbol(self.name + '_endcap_thickness'))
 75      self.set_geometry(cylinder_radius_m=None,
 76                        cylinder_length_m=None,
 77                        cylinder_thickness_m=None,
 78                        endcap_radius_m=None,
 79                        endcap_thickness_m=None,
 80                        major_minor_axis_ratio=major_minor_axis_ratio)
 81
 82
 83   # CAD generation function ----------------------------------------------------------------------
 84
 85   @staticmethod
 86   def __create_cad__(params: Dict[str, float], fully_displace: bool) -> Part.Solid:
 87      """Scripted CAD generation method for `SemiellipsoidalCapsule`."""
 88      doc = FreeCAD.newDocument('Temp')
 89      cylinder_length_mm = 1000.0 * params['cylinder_length']
 90      outer_cylinder_radius_mm = 1000.0 * params['cylinder_radius']
 91      inner_cylinder_radius_mm = 1000.0 * (params['cylinder_radius'] - params['cylinder_thickness'])
 92      outer_endcap_radius_mm = 1000.0 * params['endcap_radius']
 93      inner_endcap_radius_mm = 1000.0 * (params['endcap_radius'] - params['endcap_thickness'])
 94      outer_front = doc.addObject('Part::Ellipsoid', 'FrontOuter')
 95      outer_front.Radius1 = int(outer_endcap_radius_mm)
 96      outer_front.Radius2 = int(outer_cylinder_radius_mm)
 97      outer_front.Radius3 = int(outer_cylinder_radius_mm)
 98      outer_front.Angle1 = 0.0
 99      outer_back = doc.addObject('Part::Ellipsoid', 'BackOuter')
100      outer_back.Radius1 = int(outer_endcap_radius_mm)
101      outer_back.Radius2 = int(outer_cylinder_radius_mm)
102      outer_back.Radius3 = int(outer_cylinder_radius_mm)
103      outer_back.Angle1 = 0.0
104      if not fully_displace:
105         inner_front = doc.addObject('Part::Ellipsoid', 'FrontInner')
106         inner_front.Radius1 = int(inner_endcap_radius_mm)
107         inner_front.Radius2 = int(inner_cylinder_radius_mm)
108         inner_front.Radius3 = int(inner_cylinder_radius_mm)
109         inner_front.Angle1 = 0.0
110         inner_back = doc.addObject('Part::Ellipsoid', 'BackInner')
111         inner_back.Radius1 = int(inner_endcap_radius_mm)
112         inner_back.Radius2 = int(inner_cylinder_radius_mm)
113         inner_back.Radius3 = int(inner_cylinder_radius_mm)
114         inner_back.Angle1 = 0.0
115         doc.recompute()
116         front = outer_front.Shape.cut(inner_front.Shape)
117         back = outer_back.Shape.cut(inner_back.Shape)
118         front.Placement = \
119            FreeCAD.Placement(FreeCAD.Vector(0, 0, 0),
120                              FreeCAD.Rotation(0, -90, 0))
121         back.Placement = \
122            FreeCAD.Placement(FreeCAD.Vector(cylinder_length_mm, 0, 0),
123                              FreeCAD.Rotation(0, 90, 0))
124         pipe2d = Part.makeRuledSurface(Part.makeCircle(outer_cylinder_radius_mm),
125                                        Part.makeCircle(inner_cylinder_radius_mm))
126         pipe = pipe2d.extrude(FreeCAD.Vector(0, 0, cylinder_length_mm))
127      else:
128         doc.recompute()
129         outer_front.Placement = \
130            FreeCAD.Placement(FreeCAD.Vector(0, 0, 0),
131                              FreeCAD.Rotation(0, -90, 0))
132         outer_back.Placement = \
133            FreeCAD.Placement(FreeCAD.Vector(cylinder_length_mm, 0, 0),
134                              FreeCAD.Rotation(0, 90, 0))
135         front = outer_front.Shape
136         back = outer_back.Shape
137         pipe = Part.makeCylinder(outer_cylinder_radius_mm, cylinder_length_mm)
138      pipe.Placement = FreeCAD.Placement(FreeCAD.Vector(0, 0, 0),
139                                         FreeCAD.Rotation(0, 90, 0))
140      FreeCAD.closeDocument(doc.Name)
141      return pipe.generalFuse([front, back])[0]
142
143
144   # Geometry setter ------------------------------------------------------------------------------
145
146   def set_geometry(self, *, cylinder_radius_m: Union[float, None],
147                             cylinder_length_m: Union[float, None],
148                             cylinder_thickness_m: Union[float, None],
149                             endcap_radius_m: Union[float, None],
150                             endcap_thickness_m: Union[float, None],
151                             major_minor_axis_ratio: float = 2.0) -> SemiellipsoidalCapsule:
152      """Sets the physical geometry of the current `SemiellipsoidalCapsule` object.
153
154      The `major_minor_axis_ratio` parameter is used to determine the relative axis lengths of a
155      semiellipsoidal endcap when one or more of its geometric parameters are symbolic. If all
156      parameters are concretely defined, then this parameter is meaningless.
157
158      See the `SemiellipsoidalCapsule` class documentation for a description of each geometric
159      parameter.
160      """
161      self.geometry.set(cylinder_radius=cylinder_radius_m,
162                        cylinder_length=cylinder_length_m,
163                        cylinder_thickness=cylinder_thickness_m,
164                        endcap_radius=endcap_radius_m,
165                        endcap_thickness=endcap_thickness_m)
166      if cylinder_radius_m is not None and endcap_radius_m is None:
167         self.geometry.endcap_radius = cylinder_radius_m / major_minor_axis_ratio
168      elif cylinder_radius_m is None and endcap_radius_m is not None:
169         self.geometry.cylinder_radius = endcap_radius_m * major_minor_axis_ratio
170      elif cylinder_radius_m is None and endcap_radius_m is None:
171         self.geometry.endcap_radius = self.geometry.cylinder_radius / major_minor_axis_ratio
172      return self
173
174   def get_geometric_parameter_bounds(self, parameter: str) -> Tuple[float, float]:
175      parameter_bounds = {
176         'cylinder_radius': (0.01, 2.0),
177         'cylinder_length': (0.01, 2.0),
178         'cylinder_thickness': (0.001, 0.05),
179         'endcap_radius': (0.01, 1.0),
180         'endcap_thickness': (0.001, 0.05)
181      }
182      return parameter_bounds.get(parameter, (0.0, 0.0))
183
184
185   # Geometric properties -------------------------------------------------------------------------
186
187   @property
188   def material_volume(self) -> Union[float, Expr]:
189      volume = self.displaced_volume
190      volume -= (math.pi
191                 * (self.geometry.cylinder_radius - self.geometry.cylinder_thickness)**2
192                 * self.geometry.cylinder_length)
193      volume -= ((4.0 * math.pi / 3.0) *
194                 (self.geometry.cylinder_radius - self.geometry.cylinder_thickness)**2 *
195                 (self.geometry.endcap_radius - self.geometry.endcap_thickness))
196      return volume
197
198   @property
199   def displaced_volume(self) -> Union[float, Expr]:
200      endcap_volume = (4.0 * math.pi / 3.0) * \
201                      self.geometry.cylinder_radius**2 * self.geometry.endcap_radius
202      return (math.pi * self.geometry.cylinder_radius**2 * self.geometry.cylinder_length) + \
203             endcap_volume
204
205   @property
206   def surface_area(self) -> Union[float, Expr]:
207      endcap_surface_area = 4.0 * math.pi * \
208         ((((self.geometry.endcap_radius * self.geometry.cylinder_radius)**1.6 +
209            (self.geometry.endcap_radius * self.geometry.cylinder_radius)**1.6 +
210            (self.geometry.cylinder_radius * self.geometry.cylinder_radius)**1.6) / 3.0)**(1.0/1.6))
211      return (2.0 * math.pi * self.geometry.cylinder_radius * self.geometry.cylinder_length) + \
212             endcap_surface_area
213
214   @property
215   def unoriented_center_of_gravity(self) -> Tuple[Union[float, Expr],
216                                                   Union[float, Expr],
217                                                   Union[float, Expr]]:
218      return (0.5 * self.unoriented_length,
219              0.5 * self.unoriented_width,
220              0.5 * self.unoriented_height)
221
222   @property
223   def unoriented_center_of_buoyancy(self) -> Tuple[Union[float, Expr],
224                                                    Union[float, Expr],
225                                                    Union[float, Expr]]:
226      return self.unoriented_center_of_gravity
227
228   @property
229   def unoriented_length(self) -> Union[float, Expr]:
230      return (2.0 * self.geometry.endcap_radius) + self.geometry.cylinder_length
231
232   @property
233   def unoriented_width(self) -> Union[float, Expr]:
234      return 2.0 * self.geometry.cylinder_radius
235
236   @property
237   def unoriented_height(self) -> Union[float, Expr]:
238      return self.unoriented_width
239
240   @property
241   def oriented_length(self) -> Union[float, Expr]:
242      # TODO: Implement this
243      return 0
244
245   @property
246   def oriented_width(self) -> Union[float, Expr]:
247      # TODO: Implement this
248      return 0
249
250   @property
251   def oriented_height(self) -> Union[float, Expr]:
252      # TODO: Implement this
253      return 0
class SemiellipsoidalCapsule(symcad.parts.composite.CompositeShape):
 25class SemiellipsoidalCapsule(CompositeShape):
 26   """Model representing a parameteric capsule with semiellipsoidal endcaps.
 27
 28   By default, the capsule is oriented such that the endcaps are aligned with the x-axis:
 29
 30   ![SemiellipsoidalCapsule](https://symbench.github.io/SymCAD/images/SemiellipsoidalCapsule.png)
 31
 32   The minor axis of each endcap spans the open face of the endcap to its tip, while the major
 33   axis spans the radius of the open face of the endcap itself.
 34
 35   The `geometry` of this shape includes the following parameters:
 36
 37   - `cylinder_radius`: Outer radius (in `m`) of the center cylindrical part of the Capsule
 38   - `cylinder_length`: Length (in `m`) of the center cylindrical part of the Capsule
 39   - `cylinder_thickness`: Thickness (in `m`) of the cylindrical shell of the Capsule
 40   - `endcap_radius`: Radius (in `m`) of the semiellipsoidal endcaps of the Capsule
 41   - `endcap_thickness`: Thickness (in `m`) of the semiellipsoidal endcaps of the Capsule
 42
 43   Note that the above dimensions should be interpreted as if the capsule is unrotated. In other
 44   words, any shape rotation takes place *after* the capsule dimensions have been specified.
 45   """
 46
 47   # Constructor ----------------------------------------------------------------------------------
 48
 49   def __init__(self, identifier: str,
 50                      material_density_kg_m3: Optional[float] = 1.0,
 51                      major_minor_axis_ratio: Optional[float] = 2.0) -> None:
 52      """Initializes a parametric capsule object with semiellipsoidal endcaps.
 53
 54      The `major_minor_axis_ratio` parameter is used to determine the relative axis lengths of a
 55      semiellipsoidal endcap when one or more of its geometric parameters are symbolic. If all
 56      parameters are concretely defined, then this parameter is meaningless.
 57
 58      The minor axis of this shape spans the open face of the endcap to its tip, while the major
 59      axis spans the radius of the open face of the endcap itself.
 60
 61      Parameters
 62      ----------
 63      identifier : `str`
 64         Unique identifying name for the object.
 65      material_density_kg_m3 : `float`, optional, default=1.0
 66         Uniform material density in `kg/m^3` to be used in mass property calculations.
 67      major_minor_axis_ratio : `float`, optional, default=2.0
 68         Desired major-to-minor axis ratio of the semiellipsoid.
 69      """
 70      super().__init__(identifier, self.__create_cad__, None, material_density_kg_m3)
 71      setattr(self.geometry, 'cylinder_radius', Symbol(self.name + '_cylinder_radius'))
 72      setattr(self.geometry, 'cylinder_length', Symbol(self.name + '_cylinder_length'))
 73      setattr(self.geometry, 'cylinder_thickness', Symbol(self.name + '_cylinder_thickness'))
 74      setattr(self.geometry, 'endcap_radius', Symbol(self.name + '_endcap_radius'))
 75      setattr(self.geometry, 'endcap_thickness', Symbol(self.name + '_endcap_thickness'))
 76      self.set_geometry(cylinder_radius_m=None,
 77                        cylinder_length_m=None,
 78                        cylinder_thickness_m=None,
 79                        endcap_radius_m=None,
 80                        endcap_thickness_m=None,
 81                        major_minor_axis_ratio=major_minor_axis_ratio)
 82
 83
 84   # CAD generation function ----------------------------------------------------------------------
 85
 86   @staticmethod
 87   def __create_cad__(params: Dict[str, float], fully_displace: bool) -> Part.Solid:
 88      """Scripted CAD generation method for `SemiellipsoidalCapsule`."""
 89      doc = FreeCAD.newDocument('Temp')
 90      cylinder_length_mm = 1000.0 * params['cylinder_length']
 91      outer_cylinder_radius_mm = 1000.0 * params['cylinder_radius']
 92      inner_cylinder_radius_mm = 1000.0 * (params['cylinder_radius'] - params['cylinder_thickness'])
 93      outer_endcap_radius_mm = 1000.0 * params['endcap_radius']
 94      inner_endcap_radius_mm = 1000.0 * (params['endcap_radius'] - params['endcap_thickness'])
 95      outer_front = doc.addObject('Part::Ellipsoid', 'FrontOuter')
 96      outer_front.Radius1 = int(outer_endcap_radius_mm)
 97      outer_front.Radius2 = int(outer_cylinder_radius_mm)
 98      outer_front.Radius3 = int(outer_cylinder_radius_mm)
 99      outer_front.Angle1 = 0.0
100      outer_back = doc.addObject('Part::Ellipsoid', 'BackOuter')
101      outer_back.Radius1 = int(outer_endcap_radius_mm)
102      outer_back.Radius2 = int(outer_cylinder_radius_mm)
103      outer_back.Radius3 = int(outer_cylinder_radius_mm)
104      outer_back.Angle1 = 0.0
105      if not fully_displace:
106         inner_front = doc.addObject('Part::Ellipsoid', 'FrontInner')
107         inner_front.Radius1 = int(inner_endcap_radius_mm)
108         inner_front.Radius2 = int(inner_cylinder_radius_mm)
109         inner_front.Radius3 = int(inner_cylinder_radius_mm)
110         inner_front.Angle1 = 0.0
111         inner_back = doc.addObject('Part::Ellipsoid', 'BackInner')
112         inner_back.Radius1 = int(inner_endcap_radius_mm)
113         inner_back.Radius2 = int(inner_cylinder_radius_mm)
114         inner_back.Radius3 = int(inner_cylinder_radius_mm)
115         inner_back.Angle1 = 0.0
116         doc.recompute()
117         front = outer_front.Shape.cut(inner_front.Shape)
118         back = outer_back.Shape.cut(inner_back.Shape)
119         front.Placement = \
120            FreeCAD.Placement(FreeCAD.Vector(0, 0, 0),
121                              FreeCAD.Rotation(0, -90, 0))
122         back.Placement = \
123            FreeCAD.Placement(FreeCAD.Vector(cylinder_length_mm, 0, 0),
124                              FreeCAD.Rotation(0, 90, 0))
125         pipe2d = Part.makeRuledSurface(Part.makeCircle(outer_cylinder_radius_mm),
126                                        Part.makeCircle(inner_cylinder_radius_mm))
127         pipe = pipe2d.extrude(FreeCAD.Vector(0, 0, cylinder_length_mm))
128      else:
129         doc.recompute()
130         outer_front.Placement = \
131            FreeCAD.Placement(FreeCAD.Vector(0, 0, 0),
132                              FreeCAD.Rotation(0, -90, 0))
133         outer_back.Placement = \
134            FreeCAD.Placement(FreeCAD.Vector(cylinder_length_mm, 0, 0),
135                              FreeCAD.Rotation(0, 90, 0))
136         front = outer_front.Shape
137         back = outer_back.Shape
138         pipe = Part.makeCylinder(outer_cylinder_radius_mm, cylinder_length_mm)
139      pipe.Placement = FreeCAD.Placement(FreeCAD.Vector(0, 0, 0),
140                                         FreeCAD.Rotation(0, 90, 0))
141      FreeCAD.closeDocument(doc.Name)
142      return pipe.generalFuse([front, back])[0]
143
144
145   # Geometry setter ------------------------------------------------------------------------------
146
147   def set_geometry(self, *, cylinder_radius_m: Union[float, None],
148                             cylinder_length_m: Union[float, None],
149                             cylinder_thickness_m: Union[float, None],
150                             endcap_radius_m: Union[float, None],
151                             endcap_thickness_m: Union[float, None],
152                             major_minor_axis_ratio: float = 2.0) -> SemiellipsoidalCapsule:
153      """Sets the physical geometry of the current `SemiellipsoidalCapsule` object.
154
155      The `major_minor_axis_ratio` parameter is used to determine the relative axis lengths of a
156      semiellipsoidal endcap when one or more of its geometric parameters are symbolic. If all
157      parameters are concretely defined, then this parameter is meaningless.
158
159      See the `SemiellipsoidalCapsule` class documentation for a description of each geometric
160      parameter.
161      """
162      self.geometry.set(cylinder_radius=cylinder_radius_m,
163                        cylinder_length=cylinder_length_m,
164                        cylinder_thickness=cylinder_thickness_m,
165                        endcap_radius=endcap_radius_m,
166                        endcap_thickness=endcap_thickness_m)
167      if cylinder_radius_m is not None and endcap_radius_m is None:
168         self.geometry.endcap_radius = cylinder_radius_m / major_minor_axis_ratio
169      elif cylinder_radius_m is None and endcap_radius_m is not None:
170         self.geometry.cylinder_radius = endcap_radius_m * major_minor_axis_ratio
171      elif cylinder_radius_m is None and endcap_radius_m is None:
172         self.geometry.endcap_radius = self.geometry.cylinder_radius / major_minor_axis_ratio
173      return self
174
175   def get_geometric_parameter_bounds(self, parameter: str) -> Tuple[float, float]:
176      parameter_bounds = {
177         'cylinder_radius': (0.01, 2.0),
178         'cylinder_length': (0.01, 2.0),
179         'cylinder_thickness': (0.001, 0.05),
180         'endcap_radius': (0.01, 1.0),
181         'endcap_thickness': (0.001, 0.05)
182      }
183      return parameter_bounds.get(parameter, (0.0, 0.0))
184
185
186   # Geometric properties -------------------------------------------------------------------------
187
188   @property
189   def material_volume(self) -> Union[float, Expr]:
190      volume = self.displaced_volume
191      volume -= (math.pi
192                 * (self.geometry.cylinder_radius - self.geometry.cylinder_thickness)**2
193                 * self.geometry.cylinder_length)
194      volume -= ((4.0 * math.pi / 3.0) *
195                 (self.geometry.cylinder_radius - self.geometry.cylinder_thickness)**2 *
196                 (self.geometry.endcap_radius - self.geometry.endcap_thickness))
197      return volume
198
199   @property
200   def displaced_volume(self) -> Union[float, Expr]:
201      endcap_volume = (4.0 * math.pi / 3.0) * \
202                      self.geometry.cylinder_radius**2 * self.geometry.endcap_radius
203      return (math.pi * self.geometry.cylinder_radius**2 * self.geometry.cylinder_length) + \
204             endcap_volume
205
206   @property
207   def surface_area(self) -> Union[float, Expr]:
208      endcap_surface_area = 4.0 * math.pi * \
209         ((((self.geometry.endcap_radius * self.geometry.cylinder_radius)**1.6 +
210            (self.geometry.endcap_radius * self.geometry.cylinder_radius)**1.6 +
211            (self.geometry.cylinder_radius * self.geometry.cylinder_radius)**1.6) / 3.0)**(1.0/1.6))
212      return (2.0 * math.pi * self.geometry.cylinder_radius * self.geometry.cylinder_length) + \
213             endcap_surface_area
214
215   @property
216   def unoriented_center_of_gravity(self) -> Tuple[Union[float, Expr],
217                                                   Union[float, Expr],
218                                                   Union[float, Expr]]:
219      return (0.5 * self.unoriented_length,
220              0.5 * self.unoriented_width,
221              0.5 * self.unoriented_height)
222
223   @property
224   def unoriented_center_of_buoyancy(self) -> Tuple[Union[float, Expr],
225                                                    Union[float, Expr],
226                                                    Union[float, Expr]]:
227      return self.unoriented_center_of_gravity
228
229   @property
230   def unoriented_length(self) -> Union[float, Expr]:
231      return (2.0 * self.geometry.endcap_radius) + self.geometry.cylinder_length
232
233   @property
234   def unoriented_width(self) -> Union[float, Expr]:
235      return 2.0 * self.geometry.cylinder_radius
236
237   @property
238   def unoriented_height(self) -> Union[float, Expr]:
239      return self.unoriented_width
240
241   @property
242   def oriented_length(self) -> Union[float, Expr]:
243      # TODO: Implement this
244      return 0
245
246   @property
247   def oriented_width(self) -> Union[float, Expr]:
248      # TODO: Implement this
249      return 0
250
251   @property
252   def oriented_height(self) -> Union[float, Expr]:
253      # TODO: Implement this
254      return 0

Model representing a parameteric capsule with semiellipsoidal endcaps.

By default, the capsule is oriented such that the endcaps are aligned with the x-axis:

SemiellipsoidalCapsule

The minor axis of each endcap spans the open face of the endcap to its tip, while the major axis spans the radius of the open face of the endcap itself.

The geometry of this shape includes the following parameters:

  • cylinder_radius: Outer radius (in m) of the center cylindrical part of the Capsule
  • cylinder_length: Length (in m) of the center cylindrical part of the Capsule
  • cylinder_thickness: Thickness (in m) of the cylindrical shell of the Capsule
  • endcap_radius: Radius (in m) of the semiellipsoidal endcaps of the Capsule
  • endcap_thickness: Thickness (in m) of the semiellipsoidal endcaps of the Capsule

Note that the above dimensions should be interpreted as if the capsule is unrotated. In other words, any shape rotation takes place after the capsule dimensions have been specified.

SemiellipsoidalCapsule( identifier: str, material_density_kg_m3: Optional[float] = 1.0, major_minor_axis_ratio: Optional[float] = 2.0)
49   def __init__(self, identifier: str,
50                      material_density_kg_m3: Optional[float] = 1.0,
51                      major_minor_axis_ratio: Optional[float] = 2.0) -> None:
52      """Initializes a parametric capsule object with semiellipsoidal endcaps.
53
54      The `major_minor_axis_ratio` parameter is used to determine the relative axis lengths of a
55      semiellipsoidal endcap when one or more of its geometric parameters are symbolic. If all
56      parameters are concretely defined, then this parameter is meaningless.
57
58      The minor axis of this shape spans the open face of the endcap to its tip, while the major
59      axis spans the radius of the open face of the endcap itself.
60
61      Parameters
62      ----------
63      identifier : `str`
64         Unique identifying name for the object.
65      material_density_kg_m3 : `float`, optional, default=1.0
66         Uniform material density in `kg/m^3` to be used in mass property calculations.
67      major_minor_axis_ratio : `float`, optional, default=2.0
68         Desired major-to-minor axis ratio of the semiellipsoid.
69      """
70      super().__init__(identifier, self.__create_cad__, None, material_density_kg_m3)
71      setattr(self.geometry, 'cylinder_radius', Symbol(self.name + '_cylinder_radius'))
72      setattr(self.geometry, 'cylinder_length', Symbol(self.name + '_cylinder_length'))
73      setattr(self.geometry, 'cylinder_thickness', Symbol(self.name + '_cylinder_thickness'))
74      setattr(self.geometry, 'endcap_radius', Symbol(self.name + '_endcap_radius'))
75      setattr(self.geometry, 'endcap_thickness', Symbol(self.name + '_endcap_thickness'))
76      self.set_geometry(cylinder_radius_m=None,
77                        cylinder_length_m=None,
78                        cylinder_thickness_m=None,
79                        endcap_radius_m=None,
80                        endcap_thickness_m=None,
81                        major_minor_axis_ratio=major_minor_axis_ratio)

Initializes a parametric capsule object with semiellipsoidal endcaps.

The major_minor_axis_ratio parameter is used to determine the relative axis lengths of a semiellipsoidal endcap when one or more of its geometric parameters are symbolic. If all parameters are concretely defined, then this parameter is meaningless.

The minor axis of this shape spans the open face of the endcap to its tip, while the major axis spans the radius of the open face of the endcap itself.

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.
  • major_minor_axis_ratio (float, optional, default=2.0): Desired major-to-minor axis ratio of the semiellipsoid.
def set_geometry( self, *, cylinder_radius_m: Optional[float], cylinder_length_m: Optional[float], cylinder_thickness_m: Optional[float], endcap_radius_m: Optional[float], endcap_thickness_m: Optional[float], major_minor_axis_ratio: float = 2.0) -> SemiellipsoidalCapsule:
147   def set_geometry(self, *, cylinder_radius_m: Union[float, None],
148                             cylinder_length_m: Union[float, None],
149                             cylinder_thickness_m: Union[float, None],
150                             endcap_radius_m: Union[float, None],
151                             endcap_thickness_m: Union[float, None],
152                             major_minor_axis_ratio: float = 2.0) -> SemiellipsoidalCapsule:
153      """Sets the physical geometry of the current `SemiellipsoidalCapsule` object.
154
155      The `major_minor_axis_ratio` parameter is used to determine the relative axis lengths of a
156      semiellipsoidal endcap when one or more of its geometric parameters are symbolic. If all
157      parameters are concretely defined, then this parameter is meaningless.
158
159      See the `SemiellipsoidalCapsule` class documentation for a description of each geometric
160      parameter.
161      """
162      self.geometry.set(cylinder_radius=cylinder_radius_m,
163                        cylinder_length=cylinder_length_m,
164                        cylinder_thickness=cylinder_thickness_m,
165                        endcap_radius=endcap_radius_m,
166                        endcap_thickness=endcap_thickness_m)
167      if cylinder_radius_m is not None and endcap_radius_m is None:
168         self.geometry.endcap_radius = cylinder_radius_m / major_minor_axis_ratio
169      elif cylinder_radius_m is None and endcap_radius_m is not None:
170         self.geometry.cylinder_radius = endcap_radius_m * major_minor_axis_ratio
171      elif cylinder_radius_m is None and endcap_radius_m is None:
172         self.geometry.endcap_radius = self.geometry.cylinder_radius / major_minor_axis_ratio
173      return self

Sets the physical geometry of the current SemiellipsoidalCapsule object.

The major_minor_axis_ratio parameter is used to determine the relative axis lengths of a semiellipsoidal endcap when one or more of its geometric parameters are symbolic. If all parameters are concretely defined, then this parameter is meaningless.

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

def get_geometric_parameter_bounds(self, parameter: str) -> Tuple[float, float]:
175   def get_geometric_parameter_bounds(self, parameter: str) -> Tuple[float, float]:
176      parameter_bounds = {
177         'cylinder_radius': (0.01, 2.0),
178         'cylinder_length': (0.01, 2.0),
179         'cylinder_thickness': (0.001, 0.05),
180         'endcap_radius': (0.01, 1.0),
181         'endcap_thickness': (0.001, 0.05)
182      }
183      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).