symcad.parts.endcaps.Torisphere

  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 typing import Optional, Tuple, Union
 19from sympy import Expr, Symbol, sqrt, asin, cos
 20from . import EndcapShape
 21import math
 22
 23class Torisphere(EndcapShape):
 24   """Model representing a hollow, parametric, torispherical endcap.
 25
 26   By default, the endcap is oriented such that its base is perpendicular to the z-axis:
 27
 28   ![Torisphere](https://symbench.github.io/SymCAD/images/Torisphere.png)
 29
 30   The `geometry` of this shape includes the following parameters:
 31
 32   - `base_radius`: Radius (in `m`) of the base of the Torisphere
 33   - `crown_ratio`: Ratio (in `%`) of the radius of the crown of the Torisphere
 34                    to its `base_radius`
 35   - `knuckle_ratio`: Ratio (in `%`) of the radius of the knuckle of the Torisphere
 36                      to its `base_radius`
 37   - `thickness`: Thickness (in `m`) of the shell of the Torisphere
 38
 39   ![TorisphereGeometry](https://symbench.github.io/SymCAD/images/TorisphereGeometry.png)
 40
 41   Note that the above dimensions should be interpreted as if the Torisphere is unrotated.
 42   In other words, any shape rotation takes place *after* the Torisphere dimensions have been
 43   specified.
 44   """
 45
 46   # Constructor ----------------------------------------------------------------------------------
 47
 48   def __init__(self, identifier: str, material_density_kg_m3: Optional[float] = 1.0) -> None:
 49      """Initializes a hollow, parametric,torispherical endcap object.
 50
 51      Parameters
 52      ----------
 53      identifier : `str`
 54         Unique identifying name for the object.
 55      material_density_kg_m3 : `float`, optional, default=1.0
 56         Uniform material density in `kg/m^3` to be used in mass property calculations.
 57      """
 58      super().__init__(identifier, 'Torisphere.FCStd', 'Torisphere.tar.xz', material_density_kg_m3)
 59      setattr(self.geometry, 'base_radius', Symbol(self.name + '_base_radius'))
 60      setattr(self.geometry, 'crown_ratio', Symbol(self.name + '_crown_ratio'))
 61      setattr(self.geometry, 'knuckle_ratio', Symbol(self.name + '_knuckle_ratio'))
 62      setattr(self.geometry, 'thickness', Symbol(self.name + '_thickness'))
 63
 64
 65   # SymPart function overrides ------------------------------------------------------------------
 66
 67   def __imul__(self, value: float) -> Torisphere:
 68      crown_ratio = self.geometry.crown_ratio
 69      knuckle_ratio = self.geometry.knuckle_ratio
 70      super().__imul__(value)
 71      self.geometry.crown_ratio = crown_ratio
 72      self.geometry.knuckle_ratio = knuckle_ratio
 73      return self
 74
 75   def __itruediv__(self, value: float) -> Torisphere:
 76      crown_ratio = self.geometry.crown_ratio
 77      knuckle_ratio = self.geometry.knuckle_ratio
 78      super().__itruediv__(value)
 79      self.geometry.crown_ratio = crown_ratio
 80      self.geometry.knuckle_ratio = knuckle_ratio
 81      return self
 82
 83
 84   # Geometry setter ------------------------------------------------------------------------------
 85
 86   def set_geometry(self, *, base_radius_m: Union[float, None],
 87                             thickness_m: Union[float, None],
 88                             crown_ratio_percent: Union[float, None] = 1.0,
 89                             knuckle_ratio_percent: Union[float, None] = 0.06) -> Torisphere:
 90      """Sets the physical geometry of the current `Torisphere` object.
 91
 92      See the `Torisphere` class documentation for a description of each geometric parameter.
 93      """
 94      if crown_ratio_percent is not None and crown_ratio_percent > 1.0:
 95         raise ValueError('crown_ratio_percent ({}) is not a percentage between 0.0 - 1.0'
 96                          .format(crown_ratio_percent))
 97      if knuckle_ratio_percent is not None and (knuckle_ratio_percent < 0.06 or
 98                                                knuckle_ratio_percent > 1.0):
 99         raise ValueError('knuckle_ratio_percent ({}) is not a percentage between 0.06 - 1.0'
100                          .format(knuckle_ratio_percent))
101      self.geometry.set(base_radius=base_radius_m,
102                        crown_ratio=crown_ratio_percent,
103                        knuckle_ratio=knuckle_ratio_percent,
104                        thickness=thickness_m)
105      return self
106
107   def get_geometric_parameter_bounds(self, parameter: str) -> Tuple[float, float]:
108      parameter_bounds = {
109         'base_radius': (0.0, 2.0),
110         'crown_ratio': (0.0, 1.0),
111         'knuckle_ratio': (0.06, 1.0),
112         'thickness': (0.0, 0.05)
113      }
114      return parameter_bounds.get(parameter, (0.0, 0.0))
115
116
117   # Geometric properties -------------------------------------------------------------------------
118
119   @property
120   def material_volume(self) -> Union[float, Expr]:
121      knuckle_radius = (2.0 * self.geometry.knuckle_ratio * self.geometry.base_radius) - \
122                       self.geometry.thickness
123      crown_radius = (2.0 * self.geometry.crown_ratio * self.geometry.base_radius) - \
124                     self.geometry.thickness
125      c = self.geometry.base_radius - self.geometry.thickness - knuckle_radius
126      h = crown_radius - sqrt((knuckle_radius + c - crown_radius) *
127                              (knuckle_radius - c - crown_radius))
128      volume = self.displaced_volume
129      volume -= ((math.pi / 3.0) * \
130                 ((2.0 * h * crown_radius**2) -
131                  (((2.0 * knuckle_radius**2) + c**2 +
132                    (2.0 * knuckle_radius * crown_radius)) * (crown_radius - h)) +
133                  (3.0 * knuckle_radius**2 * c *
134                         asin((crown_radius - h) / (crown_radius - knuckle_radius)))))
135      return volume
136
137   @property
138   def displaced_volume(self) -> Union[float, Expr]:
139      knuckle_radius = 2.0 * self.geometry.knuckle_ratio * self.geometry.base_radius
140      crown_radius = 2.0 * self.geometry.crown_ratio * self.geometry.base_radius
141      c = self.geometry.base_radius - knuckle_radius
142      h = crown_radius - sqrt((knuckle_radius + c - crown_radius) *
143                              (knuckle_radius - c - crown_radius))
144      return (math.pi / 3.0) * \
145             ((2.0 * h * crown_radius**2) -
146              (((2.0 * knuckle_radius**2) + c**2 +
147                (2.0 * knuckle_radius * crown_radius)) * (crown_radius - h)) +
148              (3.0 * knuckle_radius**2 * c *
149                     asin((crown_radius - h) / (crown_radius - knuckle_radius))))
150
151   @property
152   def surface_area(self) -> Union[float, Expr]:
153      knuckle_radius = 2.0 * self.geometry.knuckle_ratio * self.geometry.base_radius
154      crown_radius = 2.0 * self.geometry.crown_ratio * self.geometry.base_radius
155      cos_alpha = cos(asin((1.0 - (2.0 * self.geometry.knuckle_ratio)) /
156                           (2.0 * (self.geometry.crown_ratio - self.geometry.knuckle_ratio))))
157      a2 = knuckle_radius * cos_alpha
158      return (4.0 * math.pi * crown_radius**2 * (1.0 - cos_alpha)) + \
159             (4.0 * math.pi * knuckle_radius *
160                  (a2 + ((self.geometry.base_radius - knuckle_radius) * asin(cos_alpha))))
161
162   @property
163   def unoriented_center_of_gravity(self) -> Tuple[Union[float, Expr],
164                                                   Union[float, Expr],
165                                                   Union[float, Expr]]:
166      return (self.geometry.base_radius,
167              self.geometry.base_radius,
168              self.__neural_net__.evaluate('cg_z', **self.geometry.as_dict()))
169
170   @property
171   def unoriented_center_of_buoyancy(self) -> Tuple[Union[float, Expr],
172                                                    Union[float, Expr],
173                                                    Union[float, Expr]]:
174      return (self.geometry.base_radius,
175              self.geometry.base_radius,
176              self.__neural_net__.evaluate('cb_z', **self.geometry.as_dict()))
177
178   @property
179   def unoriented_length(self) -> Union[float, Expr]:
180      return 2.0 * self.geometry.base_radius
181
182   @property
183   def unoriented_width(self) -> Union[float, Expr]:
184      return self.unoriented_length
185
186   @property
187   def unoriented_height(self) -> Union[float, Expr]:
188      knuckle_radius = 2.0 * self.geometry.knuckle_ratio * self.geometry.base_radius
189      crown_radius = 2.0 * self.geometry.crown_ratio * self.geometry.base_radius
190      c = self.geometry.base_radius - knuckle_radius
191      return crown_radius - sqrt((knuckle_radius + c - crown_radius) *
192                                 (knuckle_radius - c - crown_radius))
193
194   @property
195   def oriented_length(self) -> Union[float, Expr]:
196      # TODO: Implement this
197      return 0
198
199   @property
200   def oriented_width(self) -> Union[float, Expr]:
201      # TODO: Implement this
202      return 0
203
204   @property
205   def oriented_height(self) -> Union[float, Expr]:
206      # TODO: Implement this
207      return 0
class Torisphere(symcad.parts.endcaps.EndcapShape):
 24class Torisphere(EndcapShape):
 25   """Model representing a hollow, parametric, torispherical endcap.
 26
 27   By default, the endcap is oriented such that its base is perpendicular to the z-axis:
 28
 29   ![Torisphere](https://symbench.github.io/SymCAD/images/Torisphere.png)
 30
 31   The `geometry` of this shape includes the following parameters:
 32
 33   - `base_radius`: Radius (in `m`) of the base of the Torisphere
 34   - `crown_ratio`: Ratio (in `%`) of the radius of the crown of the Torisphere
 35                    to its `base_radius`
 36   - `knuckle_ratio`: Ratio (in `%`) of the radius of the knuckle of the Torisphere
 37                      to its `base_radius`
 38   - `thickness`: Thickness (in `m`) of the shell of the Torisphere
 39
 40   ![TorisphereGeometry](https://symbench.github.io/SymCAD/images/TorisphereGeometry.png)
 41
 42   Note that the above dimensions should be interpreted as if the Torisphere is unrotated.
 43   In other words, any shape rotation takes place *after* the Torisphere dimensions have been
 44   specified.
 45   """
 46
 47   # Constructor ----------------------------------------------------------------------------------
 48
 49   def __init__(self, identifier: str, material_density_kg_m3: Optional[float] = 1.0) -> None:
 50      """Initializes a hollow, parametric,torispherical endcap object.
 51
 52      Parameters
 53      ----------
 54      identifier : `str`
 55         Unique identifying name for the object.
 56      material_density_kg_m3 : `float`, optional, default=1.0
 57         Uniform material density in `kg/m^3` to be used in mass property calculations.
 58      """
 59      super().__init__(identifier, 'Torisphere.FCStd', 'Torisphere.tar.xz', material_density_kg_m3)
 60      setattr(self.geometry, 'base_radius', Symbol(self.name + '_base_radius'))
 61      setattr(self.geometry, 'crown_ratio', Symbol(self.name + '_crown_ratio'))
 62      setattr(self.geometry, 'knuckle_ratio', Symbol(self.name + '_knuckle_ratio'))
 63      setattr(self.geometry, 'thickness', Symbol(self.name + '_thickness'))
 64
 65
 66   # SymPart function overrides ------------------------------------------------------------------
 67
 68   def __imul__(self, value: float) -> Torisphere:
 69      crown_ratio = self.geometry.crown_ratio
 70      knuckle_ratio = self.geometry.knuckle_ratio
 71      super().__imul__(value)
 72      self.geometry.crown_ratio = crown_ratio
 73      self.geometry.knuckle_ratio = knuckle_ratio
 74      return self
 75
 76   def __itruediv__(self, value: float) -> Torisphere:
 77      crown_ratio = self.geometry.crown_ratio
 78      knuckle_ratio = self.geometry.knuckle_ratio
 79      super().__itruediv__(value)
 80      self.geometry.crown_ratio = crown_ratio
 81      self.geometry.knuckle_ratio = knuckle_ratio
 82      return self
 83
 84
 85   # Geometry setter ------------------------------------------------------------------------------
 86
 87   def set_geometry(self, *, base_radius_m: Union[float, None],
 88                             thickness_m: Union[float, None],
 89                             crown_ratio_percent: Union[float, None] = 1.0,
 90                             knuckle_ratio_percent: Union[float, None] = 0.06) -> Torisphere:
 91      """Sets the physical geometry of the current `Torisphere` object.
 92
 93      See the `Torisphere` class documentation for a description of each geometric parameter.
 94      """
 95      if crown_ratio_percent is not None and crown_ratio_percent > 1.0:
 96         raise ValueError('crown_ratio_percent ({}) is not a percentage between 0.0 - 1.0'
 97                          .format(crown_ratio_percent))
 98      if knuckle_ratio_percent is not None and (knuckle_ratio_percent < 0.06 or
 99                                                knuckle_ratio_percent > 1.0):
100         raise ValueError('knuckle_ratio_percent ({}) is not a percentage between 0.06 - 1.0'
101                          .format(knuckle_ratio_percent))
102      self.geometry.set(base_radius=base_radius_m,
103                        crown_ratio=crown_ratio_percent,
104                        knuckle_ratio=knuckle_ratio_percent,
105                        thickness=thickness_m)
106      return self
107
108   def get_geometric_parameter_bounds(self, parameter: str) -> Tuple[float, float]:
109      parameter_bounds = {
110         'base_radius': (0.0, 2.0),
111         'crown_ratio': (0.0, 1.0),
112         'knuckle_ratio': (0.06, 1.0),
113         'thickness': (0.0, 0.05)
114      }
115      return parameter_bounds.get(parameter, (0.0, 0.0))
116
117
118   # Geometric properties -------------------------------------------------------------------------
119
120   @property
121   def material_volume(self) -> Union[float, Expr]:
122      knuckle_radius = (2.0 * self.geometry.knuckle_ratio * self.geometry.base_radius) - \
123                       self.geometry.thickness
124      crown_radius = (2.0 * self.geometry.crown_ratio * self.geometry.base_radius) - \
125                     self.geometry.thickness
126      c = self.geometry.base_radius - self.geometry.thickness - knuckle_radius
127      h = crown_radius - sqrt((knuckle_radius + c - crown_radius) *
128                              (knuckle_radius - c - crown_radius))
129      volume = self.displaced_volume
130      volume -= ((math.pi / 3.0) * \
131                 ((2.0 * h * crown_radius**2) -
132                  (((2.0 * knuckle_radius**2) + c**2 +
133                    (2.0 * knuckle_radius * crown_radius)) * (crown_radius - h)) +
134                  (3.0 * knuckle_radius**2 * c *
135                         asin((crown_radius - h) / (crown_radius - knuckle_radius)))))
136      return volume
137
138   @property
139   def displaced_volume(self) -> Union[float, Expr]:
140      knuckle_radius = 2.0 * self.geometry.knuckle_ratio * self.geometry.base_radius
141      crown_radius = 2.0 * self.geometry.crown_ratio * self.geometry.base_radius
142      c = self.geometry.base_radius - knuckle_radius
143      h = crown_radius - sqrt((knuckle_radius + c - crown_radius) *
144                              (knuckle_radius - c - crown_radius))
145      return (math.pi / 3.0) * \
146             ((2.0 * h * crown_radius**2) -
147              (((2.0 * knuckle_radius**2) + c**2 +
148                (2.0 * knuckle_radius * crown_radius)) * (crown_radius - h)) +
149              (3.0 * knuckle_radius**2 * c *
150                     asin((crown_radius - h) / (crown_radius - knuckle_radius))))
151
152   @property
153   def surface_area(self) -> Union[float, Expr]:
154      knuckle_radius = 2.0 * self.geometry.knuckle_ratio * self.geometry.base_radius
155      crown_radius = 2.0 * self.geometry.crown_ratio * self.geometry.base_radius
156      cos_alpha = cos(asin((1.0 - (2.0 * self.geometry.knuckle_ratio)) /
157                           (2.0 * (self.geometry.crown_ratio - self.geometry.knuckle_ratio))))
158      a2 = knuckle_radius * cos_alpha
159      return (4.0 * math.pi * crown_radius**2 * (1.0 - cos_alpha)) + \
160             (4.0 * math.pi * knuckle_radius *
161                  (a2 + ((self.geometry.base_radius - knuckle_radius) * asin(cos_alpha))))
162
163   @property
164   def unoriented_center_of_gravity(self) -> Tuple[Union[float, Expr],
165                                                   Union[float, Expr],
166                                                   Union[float, Expr]]:
167      return (self.geometry.base_radius,
168              self.geometry.base_radius,
169              self.__neural_net__.evaluate('cg_z', **self.geometry.as_dict()))
170
171   @property
172   def unoriented_center_of_buoyancy(self) -> Tuple[Union[float, Expr],
173                                                    Union[float, Expr],
174                                                    Union[float, Expr]]:
175      return (self.geometry.base_radius,
176              self.geometry.base_radius,
177              self.__neural_net__.evaluate('cb_z', **self.geometry.as_dict()))
178
179   @property
180   def unoriented_length(self) -> Union[float, Expr]:
181      return 2.0 * self.geometry.base_radius
182
183   @property
184   def unoriented_width(self) -> Union[float, Expr]:
185      return self.unoriented_length
186
187   @property
188   def unoriented_height(self) -> Union[float, Expr]:
189      knuckle_radius = 2.0 * self.geometry.knuckle_ratio * self.geometry.base_radius
190      crown_radius = 2.0 * self.geometry.crown_ratio * self.geometry.base_radius
191      c = self.geometry.base_radius - knuckle_radius
192      return crown_radius - sqrt((knuckle_radius + c - crown_radius) *
193                                 (knuckle_radius - c - crown_radius))
194
195   @property
196   def oriented_length(self) -> Union[float, Expr]:
197      # TODO: Implement this
198      return 0
199
200   @property
201   def oriented_width(self) -> Union[float, Expr]:
202      # TODO: Implement this
203      return 0
204
205   @property
206   def oriented_height(self) -> Union[float, Expr]:
207      # TODO: Implement this
208      return 0

Model representing a hollow, parametric, torispherical endcap.

By default, the endcap is oriented such that its base is perpendicular to the z-axis:

Torisphere

The geometry of this shape includes the following parameters:

  • base_radius: Radius (in m) of the base of the Torisphere
  • crown_ratio: Ratio (in %) of the radius of the crown of the Torisphere to its base_radius
  • knuckle_ratio: Ratio (in %) of the radius of the knuckle of the Torisphere to its base_radius
  • thickness: Thickness (in m) of the shell of the Torisphere

TorisphereGeometry

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

Torisphere(identifier: str, material_density_kg_m3: Optional[float] = 1.0)
49   def __init__(self, identifier: str, material_density_kg_m3: Optional[float] = 1.0) -> None:
50      """Initializes a hollow, parametric,torispherical endcap object.
51
52      Parameters
53      ----------
54      identifier : `str`
55         Unique identifying name for the object.
56      material_density_kg_m3 : `float`, optional, default=1.0
57         Uniform material density in `kg/m^3` to be used in mass property calculations.
58      """
59      super().__init__(identifier, 'Torisphere.FCStd', 'Torisphere.tar.xz', material_density_kg_m3)
60      setattr(self.geometry, 'base_radius', Symbol(self.name + '_base_radius'))
61      setattr(self.geometry, 'crown_ratio', Symbol(self.name + '_crown_ratio'))
62      setattr(self.geometry, 'knuckle_ratio', Symbol(self.name + '_knuckle_ratio'))
63      setattr(self.geometry, 'thickness', Symbol(self.name + '_thickness'))

Initializes a hollow, parametric,torispherical endcap object.

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, *, base_radius_m: Optional[float], thickness_m: Optional[float], crown_ratio_percent: Optional[float] = 1.0, knuckle_ratio_percent: Optional[float] = 0.06) -> Torisphere:
 87   def set_geometry(self, *, base_radius_m: Union[float, None],
 88                             thickness_m: Union[float, None],
 89                             crown_ratio_percent: Union[float, None] = 1.0,
 90                             knuckle_ratio_percent: Union[float, None] = 0.06) -> Torisphere:
 91      """Sets the physical geometry of the current `Torisphere` object.
 92
 93      See the `Torisphere` class documentation for a description of each geometric parameter.
 94      """
 95      if crown_ratio_percent is not None and crown_ratio_percent > 1.0:
 96         raise ValueError('crown_ratio_percent ({}) is not a percentage between 0.0 - 1.0'
 97                          .format(crown_ratio_percent))
 98      if knuckle_ratio_percent is not None and (knuckle_ratio_percent < 0.06 or
 99                                                knuckle_ratio_percent > 1.0):
100         raise ValueError('knuckle_ratio_percent ({}) is not a percentage between 0.06 - 1.0'
101                          .format(knuckle_ratio_percent))
102      self.geometry.set(base_radius=base_radius_m,
103                        crown_ratio=crown_ratio_percent,
104                        knuckle_ratio=knuckle_ratio_percent,
105                        thickness=thickness_m)
106      return self

Sets the physical geometry of the current Torisphere object.

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

def get_geometric_parameter_bounds(self, parameter: str) -> Tuple[float, float]:
108   def get_geometric_parameter_bounds(self, parameter: str) -> Tuple[float, float]:
109      parameter_bounds = {
110         'base_radius': (0.0, 2.0),
111         'crown_ratio': (0.0, 1.0),
112         'knuckle_ratio': (0.06, 1.0),
113         'thickness': (0.0, 0.05)
114      }
115      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).