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  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  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
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  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  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:

The geometry of this shape includes the following parameters:
base_radius: Radius (inm) of the base of the Torispherecrown_ratio: Ratio (in%) of the radius of the crown of the Torisphere to itsbase_radiusknuckle_ratio: Ratio (in%) of the radius of the knuckle of the Torisphere to itsbase_radiusthickness: Thickness (inm) of the shell of the Torisphere

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.
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 inkg/m^3to be used in mass property calculations.
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.
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 geometricparameter.
Material volume (in m^3) of the SymPart (read-only).
Displaced volume (in m^3) of the SymPart (read-only).
Surface/wetted area (in m^2) of the SymPart (read-only).
Center of gravity (in m) of the unoriented SymPart (read-only).
Center of buoyancy (in m) of the unoriented SymPart (read-only).
X-axis length (in m) of the bounding box of the unoriented SymPart (read-only).
Y-axis width (in m) of the bounding box of the unoriented SymPart (read-only).
Z-axis height (in m) of the bounding box of the unoriented SymPart (read-only).
X-axis length (in m) of the bounding box of the oriented SymPart (read-only).
Y-axis length (in m) of the bounding box of the oriented SymPart (read-only).
Z-axis length (in m) of the bounding box of the oriented SymPart (read-only).
Inherited Members
- symcad.core.SymPart.SymPart
- name
- geometry
- attachment_points
- attachments
- connection_ports
- connections
- static_origin
- static_placement
- orientation
- material_density
- current_states
- is_exposed
- clone
- set_placement
- set_orientation
- set_state
- set_unexposed
- set_material_density
- add_attachment_point
- add_connection_port
- attach
- connect
- get_cad_physical_properties
- export
- get_valid_states
- mass
- oriented_center_of_gravity
- oriented_center_of_buoyancy