symcad.parts.endcaps.Semiellipsoid
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 EndcapShape 22import math 23 24class Semiellipsoid(EndcapShape): 25 """Model representing a hollow, parametric, semiellipsoidal endcap. 26 27 By default, the endcap is oriented such that its base is perpendicular to the z-axis: 28 29  30 31 The minor axis of this shape spans the open face of the endcap to its tip, while the major 32 axis spans the radius of the open face itself. 33 34 The `geometry` of this shape includes the following parameters: 35 36 - `major_radius`: Major radius (in `m`) of the Semiellipsoid along the x- and y-axis 37 - `minor_radius`: Minor radius (in `m`) of the Semiellipsoid along the z-axis 38 - `thickness`: Thickness (in `m`) of the shell of the Semiellipsoid 39 40 Note that the above dimensions should be interpreted as if the Semiellipsoid is unrotated. 41 In other words, any shape rotation takes place *after* the Semiellipsoid dimensions have been 42 specified. 43 """ 44 45 # Constructor ---------------------------------------------------------------------------------- 46 47 def __init__(self, identifier: str, 48 material_density_kg_m3: Optional[float] = 1.0, 49 major_minor_axis_ratio: Optional[float] = 2.0, 50 minor_depends_on_major: bool = True) -> None: 51 """Initializes a hollow, parametric, ellipsoidal endcap object. 52 53 The `major_minor_axis_ratio` and `minor_depends_on_major` parameters are used to determine 54 the relative axis lengths of the Semiellipsoid when one or more of its geometric parameters 55 are symbolic. If all parameters are concretely defined, then these parameters are 56 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 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 minor_depends_on_major : `bool`, optional, default=True 70 Whether the radius of the minor axis depends on the major axis or vice versa. 71 """ 72 super().__init__(identifier, 73 self.__create_cad__, 74 'Semiellipsoid.tar.xz', 75 material_density_kg_m3) 76 setattr(self.geometry, 'major_radius', Symbol(self.name + '_major_radius')) 77 setattr(self.geometry, 'minor_radius', Symbol(self.name + '_minor_radius')) 78 setattr(self.geometry, 'thickness', Symbol(self.name + '_thickness')) 79 self.set_geometry(major_axis_radius_m=None, 80 minor_axis_radius_m=None, 81 thickness_m=None, 82 major_minor_axis_ratio=major_minor_axis_ratio, 83 minor_depends_on_major=minor_depends_on_major) 84 85 86 # CAD generation function ---------------------------------------------------------------------- 87 88 @staticmethod 89 def __create_cad__(params: Dict[str, float], fully_displace: bool) -> Part.Solid: 90 """Scripted CAD generation method for a `Semiellipsoid`.""" 91 doc = FreeCAD.newDocument('Temp') 92 thickness_mm = 1000.0 * params['thickness'] 93 outer_major_radius_mm = 1000.0 * params['major_radius'] 94 outer_minor_radius_mm = 1000.0 * params['minor_radius'] 95 inner_major_radius_mm = outer_major_radius_mm - thickness_mm 96 inner_minor_radius_mm = outer_minor_radius_mm - thickness_mm 97 outer = doc.addObject('Part::Ellipsoid', 'Ellipsoid') 98 outer.Radius1 = outer_minor_radius_mm 99 outer.Radius2 = outer_major_radius_mm 100 outer.Radius3 = outer_major_radius_mm 101 outer.Angle1 = 0.0 102 if not fully_displace: 103 inner = doc.addObject('Part::Ellipsoid', 'Ellipsoid') 104 inner.Radius1 = inner_minor_radius_mm 105 inner.Radius2 = inner_major_radius_mm 106 inner.Radius3 = inner_major_radius_mm 107 inner.Angle1 = 0.0 108 doc.recompute() 109 endcap = outer.Shape.cut(inner.Shape) 110 else: 111 doc.recompute() 112 endcap = outer.Shape 113 FreeCAD.closeDocument(doc.Name) 114 return endcap 115 116 117 # Geometry setter ------------------------------------------------------------------------------ 118 119 def set_geometry(self, *, major_axis_radius_m: Union[float, None], 120 minor_axis_radius_m: Union[float, None], 121 thickness_m: Union[float, None], 122 major_minor_axis_ratio: float = 2.0, 123 minor_depends_on_major: bool = True) -> Semiellipsoid: 124 """Sets the physical geometry of the current `Semiellipsoid` object. 125 126 The `major_minor_axis_ratio` and `minor_depends_on_major` parameters are used to determine 127 the relative axis lengths of the Semiellipsoid when one or more of its geometric parameters 128 are symbolic. If all parameters are concretely defined, then these parameters are 129 meaningless. 130 131 See the `Semiellipsoid` class documentation for a description of each geometric parameter. 132 """ 133 self.geometry.set(major_radius=major_axis_radius_m, 134 minor_radius=minor_axis_radius_m, 135 thickness=thickness_m) 136 if major_axis_radius_m is not None and minor_axis_radius_m is None: 137 self.geometry.minor_radius = major_axis_radius_m / major_minor_axis_ratio 138 elif major_axis_radius_m is None and minor_axis_radius_m is not None: 139 self.geometry.major_radius = minor_axis_radius_m * major_minor_axis_ratio 140 elif major_axis_radius_m is None and minor_axis_radius_m is None: 141 if minor_depends_on_major: 142 self.geometry.minor_radius = self.geometry.major_radius / major_minor_axis_ratio 143 else: 144 self.geometry.major_radius = self.geometry.minor_radius * major_minor_axis_ratio 145 return self 146 147 def get_geometric_parameter_bounds(self, parameter: str) -> Tuple[float, float]: 148 parameter_bounds = { 149 'major_radius': (0.0, 2.0), 150 'minor_radius': (0.0, 2.0), 151 'thickness': (0.0, 0.05) 152 } 153 return parameter_bounds.get(parameter, (0.0, 0.0)) 154 155 156 # Geometric properties ------------------------------------------------------------------------- 157 158 @property 159 def material_volume(self) -> Union[float, Expr]: 160 volume = self.displaced_volume 161 volume -= (2.0 * math.pi * (self.geometry.major_radius - self.geometry.thickness)**2 162 * (self.geometry.minor_radius - self.geometry.thickness) / 3.0) 163 return volume 164 165 @property 166 def displaced_volume(self) -> Union[float, Expr]: 167 return 2.0 * math.pi * self.geometry.major_radius**2 * self.geometry.minor_radius / 3.0 168 169 @property 170 def surface_area(self) -> Union[float, Expr]: 171 numerator = (2.0 * (self.geometry.minor_radius * self.geometry.major_radius)**1.6) + \ 172 self.geometry.major_radius**3.2 173 return 2.0 * math.pi * (numerator / 3.0)**(1.0 / 1.6) 174 175 @property 176 def unoriented_center_of_gravity(self) -> Tuple[Union[float, Expr], 177 Union[float, Expr], 178 Union[float, Expr]]: 179 return (self.geometry.major_radius, 180 self.geometry.major_radius, 181 self.__neural_net__.evaluate('cg_z', **self.geometry.as_dict())) 182 183 @property 184 def unoriented_center_of_buoyancy(self) -> Tuple[Union[float, Expr], 185 Union[float, Expr], 186 Union[float, Expr]]: 187 return (self.geometry.major_radius, 188 self.geometry.major_radius, 189 3.0 * self.geometry.minor_radius / 8.0) 190 191 @property 192 def unoriented_length(self) -> Union[float, Expr]: 193 return 2.0 * self.geometry.major_radius 194 195 @property 196 def unoriented_width(self) -> Union[float, Expr]: 197 return self.unoriented_length 198 199 @property 200 def unoriented_height(self) -> Union[float, Expr]: 201 return self.geometry.minor_radius 202 203 @property 204 def oriented_length(self) -> Union[float, Expr]: 205 # TODO: Implement this 206 return 0 207 208 @property 209 def oriented_width(self) -> Union[float, Expr]: 210 # TODO: Implement this 211 return 0 212 213 @property 214 def oriented_height(self) -> Union[float, Expr]: 215 # TODO: Implement this 216 return 0
25class Semiellipsoid(EndcapShape): 26 """Model representing a hollow, parametric, semiellipsoidal endcap. 27 28 By default, the endcap is oriented such that its base is perpendicular to the z-axis: 29 30  31 32 The minor axis of this shape spans the open face of the endcap to its tip, while the major 33 axis spans the radius of the open face itself. 34 35 The `geometry` of this shape includes the following parameters: 36 37 - `major_radius`: Major radius (in `m`) of the Semiellipsoid along the x- and y-axis 38 - `minor_radius`: Minor radius (in `m`) of the Semiellipsoid along the z-axis 39 - `thickness`: Thickness (in `m`) of the shell of the Semiellipsoid 40 41 Note that the above dimensions should be interpreted as if the Semiellipsoid is unrotated. 42 In other words, any shape rotation takes place *after* the Semiellipsoid dimensions have been 43 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, 51 minor_depends_on_major: bool = True) -> None: 52 """Initializes a hollow, parametric, ellipsoidal endcap object. 53 54 The `major_minor_axis_ratio` and `minor_depends_on_major` parameters are used to determine 55 the relative axis lengths of the Semiellipsoid when one or more of its geometric parameters 56 are symbolic. If all parameters are concretely defined, then these parameters are 57 meaningless. 58 59 The minor axis of this shape spans the open face of the endcap to its tip, while the major 60 axis spans the radius of the open face itself. 61 62 Parameters 63 ---------- 64 identifier : `str` 65 Unique identifying name for the object. 66 material_density_kg_m3 : `float`, optional, default=1.0 67 Uniform material density in `kg/m^3` to be used in mass property calculations. 68 major_minor_axis_ratio : `float`, optional, default=2.0 69 Desired major-to-minor axis ratio of the semiellipsoid. 70 minor_depends_on_major : `bool`, optional, default=True 71 Whether the radius of the minor axis depends on the major axis or vice versa. 72 """ 73 super().__init__(identifier, 74 self.__create_cad__, 75 'Semiellipsoid.tar.xz', 76 material_density_kg_m3) 77 setattr(self.geometry, 'major_radius', Symbol(self.name + '_major_radius')) 78 setattr(self.geometry, 'minor_radius', Symbol(self.name + '_minor_radius')) 79 setattr(self.geometry, 'thickness', Symbol(self.name + '_thickness')) 80 self.set_geometry(major_axis_radius_m=None, 81 minor_axis_radius_m=None, 82 thickness_m=None, 83 major_minor_axis_ratio=major_minor_axis_ratio, 84 minor_depends_on_major=minor_depends_on_major) 85 86 87 # CAD generation function ---------------------------------------------------------------------- 88 89 @staticmethod 90 def __create_cad__(params: Dict[str, float], fully_displace: bool) -> Part.Solid: 91 """Scripted CAD generation method for a `Semiellipsoid`.""" 92 doc = FreeCAD.newDocument('Temp') 93 thickness_mm = 1000.0 * params['thickness'] 94 outer_major_radius_mm = 1000.0 * params['major_radius'] 95 outer_minor_radius_mm = 1000.0 * params['minor_radius'] 96 inner_major_radius_mm = outer_major_radius_mm - thickness_mm 97 inner_minor_radius_mm = outer_minor_radius_mm - thickness_mm 98 outer = doc.addObject('Part::Ellipsoid', 'Ellipsoid') 99 outer.Radius1 = outer_minor_radius_mm 100 outer.Radius2 = outer_major_radius_mm 101 outer.Radius3 = outer_major_radius_mm 102 outer.Angle1 = 0.0 103 if not fully_displace: 104 inner = doc.addObject('Part::Ellipsoid', 'Ellipsoid') 105 inner.Radius1 = inner_minor_radius_mm 106 inner.Radius2 = inner_major_radius_mm 107 inner.Radius3 = inner_major_radius_mm 108 inner.Angle1 = 0.0 109 doc.recompute() 110 endcap = outer.Shape.cut(inner.Shape) 111 else: 112 doc.recompute() 113 endcap = outer.Shape 114 FreeCAD.closeDocument(doc.Name) 115 return endcap 116 117 118 # Geometry setter ------------------------------------------------------------------------------ 119 120 def set_geometry(self, *, major_axis_radius_m: Union[float, None], 121 minor_axis_radius_m: Union[float, None], 122 thickness_m: Union[float, None], 123 major_minor_axis_ratio: float = 2.0, 124 minor_depends_on_major: bool = True) -> Semiellipsoid: 125 """Sets the physical geometry of the current `Semiellipsoid` object. 126 127 The `major_minor_axis_ratio` and `minor_depends_on_major` parameters are used to determine 128 the relative axis lengths of the Semiellipsoid when one or more of its geometric parameters 129 are symbolic. If all parameters are concretely defined, then these parameters are 130 meaningless. 131 132 See the `Semiellipsoid` class documentation for a description of each geometric parameter. 133 """ 134 self.geometry.set(major_radius=major_axis_radius_m, 135 minor_radius=minor_axis_radius_m, 136 thickness=thickness_m) 137 if major_axis_radius_m is not None and minor_axis_radius_m is None: 138 self.geometry.minor_radius = major_axis_radius_m / major_minor_axis_ratio 139 elif major_axis_radius_m is None and minor_axis_radius_m is not None: 140 self.geometry.major_radius = minor_axis_radius_m * major_minor_axis_ratio 141 elif major_axis_radius_m is None and minor_axis_radius_m is None: 142 if minor_depends_on_major: 143 self.geometry.minor_radius = self.geometry.major_radius / major_minor_axis_ratio 144 else: 145 self.geometry.major_radius = self.geometry.minor_radius * major_minor_axis_ratio 146 return self 147 148 def get_geometric_parameter_bounds(self, parameter: str) -> Tuple[float, float]: 149 parameter_bounds = { 150 'major_radius': (0.0, 2.0), 151 'minor_radius': (0.0, 2.0), 152 'thickness': (0.0, 0.05) 153 } 154 return parameter_bounds.get(parameter, (0.0, 0.0)) 155 156 157 # Geometric properties ------------------------------------------------------------------------- 158 159 @property 160 def material_volume(self) -> Union[float, Expr]: 161 volume = self.displaced_volume 162 volume -= (2.0 * math.pi * (self.geometry.major_radius - self.geometry.thickness)**2 163 * (self.geometry.minor_radius - self.geometry.thickness) / 3.0) 164 return volume 165 166 @property 167 def displaced_volume(self) -> Union[float, Expr]: 168 return 2.0 * math.pi * self.geometry.major_radius**2 * self.geometry.minor_radius / 3.0 169 170 @property 171 def surface_area(self) -> Union[float, Expr]: 172 numerator = (2.0 * (self.geometry.minor_radius * self.geometry.major_radius)**1.6) + \ 173 self.geometry.major_radius**3.2 174 return 2.0 * math.pi * (numerator / 3.0)**(1.0 / 1.6) 175 176 @property 177 def unoriented_center_of_gravity(self) -> Tuple[Union[float, Expr], 178 Union[float, Expr], 179 Union[float, Expr]]: 180 return (self.geometry.major_radius, 181 self.geometry.major_radius, 182 self.__neural_net__.evaluate('cg_z', **self.geometry.as_dict())) 183 184 @property 185 def unoriented_center_of_buoyancy(self) -> Tuple[Union[float, Expr], 186 Union[float, Expr], 187 Union[float, Expr]]: 188 return (self.geometry.major_radius, 189 self.geometry.major_radius, 190 3.0 * self.geometry.minor_radius / 8.0) 191 192 @property 193 def unoriented_length(self) -> Union[float, Expr]: 194 return 2.0 * self.geometry.major_radius 195 196 @property 197 def unoriented_width(self) -> Union[float, Expr]: 198 return self.unoriented_length 199 200 @property 201 def unoriented_height(self) -> Union[float, Expr]: 202 return self.geometry.minor_radius 203 204 @property 205 def oriented_length(self) -> Union[float, Expr]: 206 # TODO: Implement this 207 return 0 208 209 @property 210 def oriented_width(self) -> Union[float, Expr]: 211 # TODO: Implement this 212 return 0 213 214 @property 215 def oriented_height(self) -> Union[float, Expr]: 216 # TODO: Implement this 217 return 0
Model representing a hollow, parametric, semiellipsoidal endcap.
By default, the endcap is oriented such that its base is perpendicular to the z-axis:

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 itself.
The geometry of this shape includes the following parameters:
major_radius: Major radius (inm) of the Semiellipsoid along the x- and y-axisminor_radius: Minor radius (inm) of the Semiellipsoid along the z-axisthickness: Thickness (inm) of the shell of the Semiellipsoid
Note that the above dimensions should be interpreted as if the Semiellipsoid is unrotated. In other words, any shape rotation takes place after the Semiellipsoid dimensions have been specified.
48 def __init__(self, identifier: str, 49 material_density_kg_m3: Optional[float] = 1.0, 50 major_minor_axis_ratio: Optional[float] = 2.0, 51 minor_depends_on_major: bool = True) -> None: 52 """Initializes a hollow, parametric, ellipsoidal endcap object. 53 54 The `major_minor_axis_ratio` and `minor_depends_on_major` parameters are used to determine 55 the relative axis lengths of the Semiellipsoid when one or more of its geometric parameters 56 are symbolic. If all parameters are concretely defined, then these parameters are 57 meaningless. 58 59 The minor axis of this shape spans the open face of the endcap to its tip, while the major 60 axis spans the radius of the open face itself. 61 62 Parameters 63 ---------- 64 identifier : `str` 65 Unique identifying name for the object. 66 material_density_kg_m3 : `float`, optional, default=1.0 67 Uniform material density in `kg/m^3` to be used in mass property calculations. 68 major_minor_axis_ratio : `float`, optional, default=2.0 69 Desired major-to-minor axis ratio of the semiellipsoid. 70 minor_depends_on_major : `bool`, optional, default=True 71 Whether the radius of the minor axis depends on the major axis or vice versa. 72 """ 73 super().__init__(identifier, 74 self.__create_cad__, 75 'Semiellipsoid.tar.xz', 76 material_density_kg_m3) 77 setattr(self.geometry, 'major_radius', Symbol(self.name + '_major_radius')) 78 setattr(self.geometry, 'minor_radius', Symbol(self.name + '_minor_radius')) 79 setattr(self.geometry, 'thickness', Symbol(self.name + '_thickness')) 80 self.set_geometry(major_axis_radius_m=None, 81 minor_axis_radius_m=None, 82 thickness_m=None, 83 major_minor_axis_ratio=major_minor_axis_ratio, 84 minor_depends_on_major=minor_depends_on_major)
Initializes a hollow, parametric, ellipsoidal endcap object.
The major_minor_axis_ratio and minor_depends_on_major parameters are used to determine
the relative axis lengths of the Semiellipsoid when one or more of its geometric parameters
are symbolic. If all parameters are concretely defined, then these parameters are
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 itself.
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. - major_minor_axis_ratio (
float, optional, default=2.0): Desired major-to-minor axis ratio of the semiellipsoid. - minor_depends_on_major (
bool, optional, default=True): Whether the radius of the minor axis depends on the major axis or vice versa.
120 def set_geometry(self, *, major_axis_radius_m: Union[float, None], 121 minor_axis_radius_m: Union[float, None], 122 thickness_m: Union[float, None], 123 major_minor_axis_ratio: float = 2.0, 124 minor_depends_on_major: bool = True) -> Semiellipsoid: 125 """Sets the physical geometry of the current `Semiellipsoid` object. 126 127 The `major_minor_axis_ratio` and `minor_depends_on_major` parameters are used to determine 128 the relative axis lengths of the Semiellipsoid when one or more of its geometric parameters 129 are symbolic. If all parameters are concretely defined, then these parameters are 130 meaningless. 131 132 See the `Semiellipsoid` class documentation for a description of each geometric parameter. 133 """ 134 self.geometry.set(major_radius=major_axis_radius_m, 135 minor_radius=minor_axis_radius_m, 136 thickness=thickness_m) 137 if major_axis_radius_m is not None and minor_axis_radius_m is None: 138 self.geometry.minor_radius = major_axis_radius_m / major_minor_axis_ratio 139 elif major_axis_radius_m is None and minor_axis_radius_m is not None: 140 self.geometry.major_radius = minor_axis_radius_m * major_minor_axis_ratio 141 elif major_axis_radius_m is None and minor_axis_radius_m is None: 142 if minor_depends_on_major: 143 self.geometry.minor_radius = self.geometry.major_radius / major_minor_axis_ratio 144 else: 145 self.geometry.major_radius = self.geometry.minor_radius * major_minor_axis_ratio 146 return self
Sets the physical geometry of the current Semiellipsoid object.
The major_minor_axis_ratio and minor_depends_on_major parameters are used to determine
the relative axis lengths of the Semiellipsoid when one or more of its geometric parameters
are symbolic. If all parameters are concretely defined, then these parameters are
meaningless.
See the Semiellipsoid class documentation for a description of each geometric parameter.
148 def get_geometric_parameter_bounds(self, parameter: str) -> Tuple[float, float]: 149 parameter_bounds = { 150 'major_radius': (0.0, 2.0), 151 'minor_radius': (0.0, 2.0), 152 'thickness': (0.0, 0.05) 153 } 154 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