symcad.parts.composite.HemisphericalCapsule
1#!/usr/bin/env python3 2# Copyright (C) 2022, Will Hedgecock 3# 4# This program is free software: you can redistribute it and/or modify 5# it under the terms of the GNU General Public License as published by 6# the Free Software Foundation, either version 3 of the License, or 7# (at your option) any later version. 8# 9# This program is distributed in the hope that it will be useful, 10# but WITHOUT ANY WARRANTY; without even the implied warranty of 11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12# GNU General Public License for more details. 13# 14# You should have received a copy of the GNU General Public License 15# along with this program. If not, see <http://www.gnu.org/licenses/>. 16 17from __future__ import annotations 18from PyFreeCAD.FreeCAD import FreeCAD, Part 19from typing import Dict, Optional, Tuple, Union 20from sympy import Expr, Symbol 21from . import CompositeShape 22import math 23 24class HemisphericalCapsule(CompositeShape): 25 """Model representing a parameteric capsule with hemispherical endcaps. 26 27 By default, the capsule is oriented such that the endcaps are aligned with the x-axis: 28 29  30 31 The `geometry` of this shape includes the following parameters: 32 33 - `cylinder_radius`: Outer radius (in `m`) of the center cylindrical part of the Capsule 34 - `cylinder_length`: Length (in `m`) of the center cylindrical part of the Capsule 35 - `cylinder_thickness`: Thickness (in `m`) of the cylindrical shell of the Capsule 36 - `endcap_thickness`: Thickness (in `m`) of the hemispherical endcaps of the Capsule 37 38 Note that the above dimensions should be interpreted as if the capsule is unrotated. In other 39 words, any shape rotation takes place *after* the capsule dimensions have been specified. 40 """ 41 42 # Constructor ---------------------------------------------------------------------------------- 43 44 def __init__(self, identifier: str, material_density_kg_m3: Optional[float] = 1.0) -> None: 45 """Initializes a parametric capsule object with hemispheric endcaps. 46 47 Parameters 48 ---------- 49 identifier : `str` 50 Unique identifying name for the object. 51 material_density_kg_m3 : `float`, optional, default=1.0 52 Uniform material density in `kg/m^3` to be used in mass property calculations. 53 """ 54 super().__init__(identifier, self.__create_cad__, None, material_density_kg_m3) 55 setattr(self.geometry, 'cylinder_radius', Symbol(self.name + '_cylinder_radius')) 56 setattr(self.geometry, 'cylinder_length', Symbol(self.name + '_cylinder_length')) 57 setattr(self.geometry, 'cylinder_thickness', Symbol(self.name + '_cylinder_thickness')) 58 setattr(self.geometry, 'endcap_thickness', Symbol(self.name + '_endcap_thickness')) 59 60 61 # CAD generation function ---------------------------------------------------------------------- 62 63 @staticmethod 64 def __create_cad__(params: Dict[str, float], fully_displace: bool) -> Part.Solid: 65 """Scripted CAD generation method for `HemisphericalCapsule`.""" 66 cylinder_length_mm = 1000.0 * params['cylinder_length'] 67 outer_cylinder_radius_mm = 1000.0 * params['cylinder_radius'] 68 inner_cylinder_radius_mm = 1000.0 * (params['cylinder_radius'] - params['cylinder_thickness']) 69 inner_endcap_radius_mm = 1000.0 * (params['cylinder_radius'] - params['endcap_thickness']) 70 front = Part.makeSphere(outer_cylinder_radius_mm, 71 FreeCAD.Vector(0, 0, 0), 72 FreeCAD.Vector(-1, 0, 0), 0, 90, 360) 73 rear = Part.makeSphere(outer_cylinder_radius_mm, 74 FreeCAD.Vector(0, 0, 0), 75 FreeCAD.Vector(1, 0, 0), 0, 90, 360) 76 if fully_displace: 77 pipe = Part.makeCylinder(outer_cylinder_radius_mm, cylinder_length_mm) 78 else: 79 pipe2d = Part.makeRuledSurface(Part.makeCircle(outer_cylinder_radius_mm), 80 Part.makeCircle(inner_cylinder_radius_mm)) 81 pipe = pipe2d.extrude(FreeCAD.Vector(0, 0, cylinder_length_mm)) 82 inner_front = Part.makeSphere(inner_endcap_radius_mm, 83 FreeCAD.Vector(0, 0, 0), 84 FreeCAD.Vector(-1, 0, 0), 0, 90, 360) 85 inner_rear = Part.makeSphere(inner_endcap_radius_mm, 86 FreeCAD.Vector(0, 0, 0), 87 FreeCAD.Vector(1, 0, 0), 0, 90, 360) 88 front = front.cut(inner_front) 89 rear = rear.cut(inner_rear) 90 pipe.Placement = FreeCAD.Placement(FreeCAD.Vector(0, 0, 0), 91 FreeCAD.Rotation(0, 90, 0)) 92 rear.Placement = \ 93 FreeCAD.Placement(FreeCAD.Vector(cylinder_length_mm, 0, 0), 94 FreeCAD.Rotation(0, 0, 0)) 95 return front.generalFuse([pipe, rear])[0] 96 97 98 # Geometry setter ------------------------------------------------------------------------------ 99 100 def set_geometry(self, *, cylinder_radius_m: Union[float, None], 101 cylinder_length_m: Union[float, None], 102 cylinder_thickness_m: Union[float, None], 103 endcap_thickness_m: Union[float, None]) -> HemisphericalCapsule: 104 """Sets the physical geometry of the current `HemisphericalCapsule` object. 105 106 See the `HemisphericalCapsule` class documentation for a description of each geometric 107 parameter. 108 """ 109 self.geometry.set(cylinder_radius=cylinder_radius_m, 110 cylinder_length=cylinder_length_m, 111 cylinder_thickness=cylinder_thickness_m, 112 endcap_thickness=endcap_thickness_m) 113 return self 114 115 def get_geometric_parameter_bounds(self, parameter: str) -> Tuple[float, float]: 116 parameter_bounds = { 117 'cylinder_radius': (0.01, 2.0), 118 'cylinder_length': (0.01, 2.0), 119 'cylinder_thickness': (0.001, 0.05), 120 'endcap_thickness': (0.001, 0.05) 121 } 122 return parameter_bounds.get(parameter, (0.0, 0.0)) 123 124 125 # Geometric properties ------------------------------------------------------------------------- 126 127 @property 128 def material_volume(self) -> Union[float, Expr]: 129 volume = self.displaced_volume 130 volume -= (math.pi 131 * (self.geometry.cylinder_radius - self.geometry.cylinder_thickness)**2 132 * self.geometry.cylinder_length) 133 volume -= ((4.0 * math.pi / 3.0) * (self.geometry.cylinder_radius 134 - self.geometry.endcap_thickness)**3) 135 return volume 136 137 @property 138 def displaced_volume(self) -> Union[float, Expr]: 139 return (math.pi * self.geometry.cylinder_radius**2 * self.geometry.cylinder_length) + \ 140 ((4.0 * math.pi / 3.0) * self.geometry.cylinder_radius**3) 141 142 @property 143 def surface_area(self) -> Union[float, Expr]: 144 return (2.0 * math.pi * self.geometry.cylinder_radius * self.geometry.cylinder_length) + \ 145 (4.0 * math.pi * self.geometry.cylinder_radius**2) 146 147 @property 148 def unoriented_center_of_gravity(self) -> Tuple[Union[float, Expr], 149 Union[float, Expr], 150 Union[float, Expr]]: 151 return (0.5 * self.unoriented_length, 152 0.5 * self.unoriented_width, 153 0.5 * self.unoriented_height) 154 155 @property 156 def unoriented_center_of_buoyancy(self) -> Tuple[Union[float, Expr], 157 Union[float, Expr], 158 Union[float, Expr]]: 159 return self.unoriented_center_of_gravity 160 161 @property 162 def unoriented_length(self) -> Union[float, Expr]: 163 return (2.0 * self.geometry.cylinder_radius) + self.geometry.cylinder_length 164 165 @property 166 def unoriented_width(self) -> Union[float, Expr]: 167 return 2.0 * self.geometry.cylinder_radius 168 169 @property 170 def unoriented_height(self) -> Union[float, Expr]: 171 return self.unoriented_width 172 173 @property 174 def oriented_length(self) -> Union[float, Expr]: 175 # TODO: Implement this 176 return 0 177 178 @property 179 def oriented_width(self) -> Union[float, Expr]: 180 # TODO: Implement this 181 return 0 182 183 @property 184 def oriented_height(self) -> Union[float, Expr]: 185 # TODO: Implement this 186 return 0
25class HemisphericalCapsule(CompositeShape): 26 """Model representing a parameteric capsule with hemispherical endcaps. 27 28 By default, the capsule is oriented such that the endcaps are aligned with the x-axis: 29 30  31 32 The `geometry` of this shape includes the following parameters: 33 34 - `cylinder_radius`: Outer radius (in `m`) of the center cylindrical part of the Capsule 35 - `cylinder_length`: Length (in `m`) of the center cylindrical part of the Capsule 36 - `cylinder_thickness`: Thickness (in `m`) of the cylindrical shell of the Capsule 37 - `endcap_thickness`: Thickness (in `m`) of the hemispherical endcaps of the Capsule 38 39 Note that the above dimensions should be interpreted as if the capsule is unrotated. In other 40 words, any shape rotation takes place *after* the capsule dimensions have been specified. 41 """ 42 43 # Constructor ---------------------------------------------------------------------------------- 44 45 def __init__(self, identifier: str, material_density_kg_m3: Optional[float] = 1.0) -> None: 46 """Initializes a parametric capsule object with hemispheric endcaps. 47 48 Parameters 49 ---------- 50 identifier : `str` 51 Unique identifying name for the object. 52 material_density_kg_m3 : `float`, optional, default=1.0 53 Uniform material density in `kg/m^3` to be used in mass property calculations. 54 """ 55 super().__init__(identifier, self.__create_cad__, None, material_density_kg_m3) 56 setattr(self.geometry, 'cylinder_radius', Symbol(self.name + '_cylinder_radius')) 57 setattr(self.geometry, 'cylinder_length', Symbol(self.name + '_cylinder_length')) 58 setattr(self.geometry, 'cylinder_thickness', Symbol(self.name + '_cylinder_thickness')) 59 setattr(self.geometry, 'endcap_thickness', Symbol(self.name + '_endcap_thickness')) 60 61 62 # CAD generation function ---------------------------------------------------------------------- 63 64 @staticmethod 65 def __create_cad__(params: Dict[str, float], fully_displace: bool) -> Part.Solid: 66 """Scripted CAD generation method for `HemisphericalCapsule`.""" 67 cylinder_length_mm = 1000.0 * params['cylinder_length'] 68 outer_cylinder_radius_mm = 1000.0 * params['cylinder_radius'] 69 inner_cylinder_radius_mm = 1000.0 * (params['cylinder_radius'] - params['cylinder_thickness']) 70 inner_endcap_radius_mm = 1000.0 * (params['cylinder_radius'] - params['endcap_thickness']) 71 front = Part.makeSphere(outer_cylinder_radius_mm, 72 FreeCAD.Vector(0, 0, 0), 73 FreeCAD.Vector(-1, 0, 0), 0, 90, 360) 74 rear = Part.makeSphere(outer_cylinder_radius_mm, 75 FreeCAD.Vector(0, 0, 0), 76 FreeCAD.Vector(1, 0, 0), 0, 90, 360) 77 if fully_displace: 78 pipe = Part.makeCylinder(outer_cylinder_radius_mm, cylinder_length_mm) 79 else: 80 pipe2d = Part.makeRuledSurface(Part.makeCircle(outer_cylinder_radius_mm), 81 Part.makeCircle(inner_cylinder_radius_mm)) 82 pipe = pipe2d.extrude(FreeCAD.Vector(0, 0, cylinder_length_mm)) 83 inner_front = Part.makeSphere(inner_endcap_radius_mm, 84 FreeCAD.Vector(0, 0, 0), 85 FreeCAD.Vector(-1, 0, 0), 0, 90, 360) 86 inner_rear = Part.makeSphere(inner_endcap_radius_mm, 87 FreeCAD.Vector(0, 0, 0), 88 FreeCAD.Vector(1, 0, 0), 0, 90, 360) 89 front = front.cut(inner_front) 90 rear = rear.cut(inner_rear) 91 pipe.Placement = FreeCAD.Placement(FreeCAD.Vector(0, 0, 0), 92 FreeCAD.Rotation(0, 90, 0)) 93 rear.Placement = \ 94 FreeCAD.Placement(FreeCAD.Vector(cylinder_length_mm, 0, 0), 95 FreeCAD.Rotation(0, 0, 0)) 96 return front.generalFuse([pipe, rear])[0] 97 98 99 # Geometry setter ------------------------------------------------------------------------------ 100 101 def set_geometry(self, *, cylinder_radius_m: Union[float, None], 102 cylinder_length_m: Union[float, None], 103 cylinder_thickness_m: Union[float, None], 104 endcap_thickness_m: Union[float, None]) -> HemisphericalCapsule: 105 """Sets the physical geometry of the current `HemisphericalCapsule` object. 106 107 See the `HemisphericalCapsule` class documentation for a description of each geometric 108 parameter. 109 """ 110 self.geometry.set(cylinder_radius=cylinder_radius_m, 111 cylinder_length=cylinder_length_m, 112 cylinder_thickness=cylinder_thickness_m, 113 endcap_thickness=endcap_thickness_m) 114 return self 115 116 def get_geometric_parameter_bounds(self, parameter: str) -> Tuple[float, float]: 117 parameter_bounds = { 118 'cylinder_radius': (0.01, 2.0), 119 'cylinder_length': (0.01, 2.0), 120 'cylinder_thickness': (0.001, 0.05), 121 'endcap_thickness': (0.001, 0.05) 122 } 123 return parameter_bounds.get(parameter, (0.0, 0.0)) 124 125 126 # Geometric properties ------------------------------------------------------------------------- 127 128 @property 129 def material_volume(self) -> Union[float, Expr]: 130 volume = self.displaced_volume 131 volume -= (math.pi 132 * (self.geometry.cylinder_radius - self.geometry.cylinder_thickness)**2 133 * self.geometry.cylinder_length) 134 volume -= ((4.0 * math.pi / 3.0) * (self.geometry.cylinder_radius 135 - self.geometry.endcap_thickness)**3) 136 return volume 137 138 @property 139 def displaced_volume(self) -> Union[float, Expr]: 140 return (math.pi * self.geometry.cylinder_radius**2 * self.geometry.cylinder_length) + \ 141 ((4.0 * math.pi / 3.0) * self.geometry.cylinder_radius**3) 142 143 @property 144 def surface_area(self) -> Union[float, Expr]: 145 return (2.0 * math.pi * self.geometry.cylinder_radius * self.geometry.cylinder_length) + \ 146 (4.0 * math.pi * self.geometry.cylinder_radius**2) 147 148 @property 149 def unoriented_center_of_gravity(self) -> Tuple[Union[float, Expr], 150 Union[float, Expr], 151 Union[float, Expr]]: 152 return (0.5 * self.unoriented_length, 153 0.5 * self.unoriented_width, 154 0.5 * self.unoriented_height) 155 156 @property 157 def unoriented_center_of_buoyancy(self) -> Tuple[Union[float, Expr], 158 Union[float, Expr], 159 Union[float, Expr]]: 160 return self.unoriented_center_of_gravity 161 162 @property 163 def unoriented_length(self) -> Union[float, Expr]: 164 return (2.0 * self.geometry.cylinder_radius) + self.geometry.cylinder_length 165 166 @property 167 def unoriented_width(self) -> Union[float, Expr]: 168 return 2.0 * self.geometry.cylinder_radius 169 170 @property 171 def unoriented_height(self) -> Union[float, Expr]: 172 return self.unoriented_width 173 174 @property 175 def oriented_length(self) -> Union[float, Expr]: 176 # TODO: Implement this 177 return 0 178 179 @property 180 def oriented_width(self) -> Union[float, Expr]: 181 # TODO: Implement this 182 return 0 183 184 @property 185 def oriented_height(self) -> Union[float, Expr]: 186 # TODO: Implement this 187 return 0
Model representing a parameteric capsule with hemispherical endcaps.
By default, the capsule is oriented such that the endcaps are aligned with the x-axis:

The geometry of this shape includes the following parameters:
cylinder_radius: Outer radius (inm) of the center cylindrical part of the Capsulecylinder_length: Length (inm) of the center cylindrical part of the Capsulecylinder_thickness: Thickness (inm) of the cylindrical shell of the Capsuleendcap_thickness: Thickness (inm) of the hemispherical endcaps of the Capsule
Note that the above dimensions should be interpreted as if the capsule is unrotated. In other words, any shape rotation takes place after the capsule dimensions have been specified.
45 def __init__(self, identifier: str, material_density_kg_m3: Optional[float] = 1.0) -> None: 46 """Initializes a parametric capsule object with hemispheric endcaps. 47 48 Parameters 49 ---------- 50 identifier : `str` 51 Unique identifying name for the object. 52 material_density_kg_m3 : `float`, optional, default=1.0 53 Uniform material density in `kg/m^3` to be used in mass property calculations. 54 """ 55 super().__init__(identifier, self.__create_cad__, None, material_density_kg_m3) 56 setattr(self.geometry, 'cylinder_radius', Symbol(self.name + '_cylinder_radius')) 57 setattr(self.geometry, 'cylinder_length', Symbol(self.name + '_cylinder_length')) 58 setattr(self.geometry, 'cylinder_thickness', Symbol(self.name + '_cylinder_thickness')) 59 setattr(self.geometry, 'endcap_thickness', Symbol(self.name + '_endcap_thickness'))
Initializes a parametric capsule object with hemispheric endcaps.
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.
101 def set_geometry(self, *, cylinder_radius_m: Union[float, None], 102 cylinder_length_m: Union[float, None], 103 cylinder_thickness_m: Union[float, None], 104 endcap_thickness_m: Union[float, None]) -> HemisphericalCapsule: 105 """Sets the physical geometry of the current `HemisphericalCapsule` object. 106 107 See the `HemisphericalCapsule` class documentation for a description of each geometric 108 parameter. 109 """ 110 self.geometry.set(cylinder_radius=cylinder_radius_m, 111 cylinder_length=cylinder_length_m, 112 cylinder_thickness=cylinder_thickness_m, 113 endcap_thickness=endcap_thickness_m) 114 return self
Sets the physical geometry of the current HemisphericalCapsule object.
See the HemisphericalCapsule class documentation for a description of each geometric
parameter.
116 def get_geometric_parameter_bounds(self, parameter: str) -> Tuple[float, float]: 117 parameter_bounds = { 118 'cylinder_radius': (0.01, 2.0), 119 'cylinder_length': (0.01, 2.0), 120 'cylinder_thickness': (0.001, 0.05), 121 'endcap_thickness': (0.001, 0.05) 122 } 123 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