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

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 inkg/m^3to be used in mass property calculations.
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.
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 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