symcad.parts.composite.FlangedFlatCapsule
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 FlangedFlatCapsule(CompositeShape): 25 """Model representing a parameteric capsule with flanged flat 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 flat 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 flanged flat 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 `FlangedFlatCapsule`.""" 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 endcap_thickness_mm = 1000.0 * params['endcap_thickness'] 70 front = Part.makeCylinder(outer_cylinder_radius_mm, endcap_thickness_mm) 71 front = front.makeFillet(endcap_thickness_mm - 0.001, front.Edges[0:1]) 72 rear = Part.makeCylinder(outer_cylinder_radius_mm, endcap_thickness_mm) 73 rear = rear.makeFillet(endcap_thickness_mm - 0.001, rear.Edges[0:1]) 74 if fully_displace: 75 pipe = Part.makeCylinder(outer_cylinder_radius_mm, cylinder_length_mm) 76 else: 77 pipe2d = Part.makeRuledSurface(Part.makeCircle(outer_cylinder_radius_mm), 78 Part.makeCircle(inner_cylinder_radius_mm)) 79 pipe = pipe2d.extrude(FreeCAD.Vector(0, 0, cylinder_length_mm)) 80 pipe.Placement = FreeCAD.Placement(FreeCAD.Vector(0, 0, 0), 81 FreeCAD.Rotation(0, 90, 0)) 82 front.Placement = \ 83 FreeCAD.Placement(FreeCAD.Vector(0, 0, 0), 84 FreeCAD.Rotation(0, -90, 0)) 85 rear.Placement = \ 86 FreeCAD.Placement(FreeCAD.Vector(cylinder_length_mm, 0, 0), 87 FreeCAD.Rotation(0, 90, 0)) 88 return front.generalFuse([pipe, rear])[0] 89 90 91 # Geometry setter ------------------------------------------------------------------------------ 92 93 def set_geometry(self, *, cylinder_radius_m: Union[float, None], 94 cylinder_length_m: Union[float, None], 95 cylinder_thickness_m: Union[float, None], 96 endcap_thickness_m: Union[float, None]) -> FlangedFlatCapsule: 97 """Sets the physical geometry of the current `FlangedFlatCapsule` object. 98 99 See the `FlangedFlatCapsule` class documentation for a description of each geometric 100 parameter. 101 """ 102 self.geometry.set(cylinder_radius=cylinder_radius_m, 103 cylinder_length=cylinder_length_m, 104 cylinder_thickness=cylinder_thickness_m, 105 endcap_thickness=endcap_thickness_m) 106 return self 107 108 def get_geometric_parameter_bounds(self, parameter: str) -> Tuple[float, float]: 109 parameter_bounds = { 110 'cylinder_radius': (0.01, 2.0), 111 'cylinder_length': (0.01, 2.0), 112 'cylinder_thickness': (0.001, 0.05), 113 'endcap_thickness': (0.001, 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 volume = self.displaced_volume 123 volume -= (math.pi 124 * (self.geometry.cylinder_radius - self.geometry.cylinder_thickness)**2 125 * self.geometry.cylinder_length) 126 return volume 127 128 @property 129 def displaced_volume(self) -> Union[float, Expr]: 130 return (math.pi * self.geometry.cylinder_radius**2 * self.geometry.cylinder_length) + \ 131 (2.0 * math.pi * self.geometry.endcap_thickness * self.geometry.cylinder_radius**2) 132 133 @property 134 def surface_area(self) -> Union[float, Expr]: 135 return (2.0 * math.pi * self.geometry.cylinder_radius * self.geometry.cylinder_length) + \ 136 (2.0 * math.pi * self.geometry.cylinder_radius * self.geometry.endcap_thickness) 137 138 @property 139 def unoriented_center_of_gravity(self) -> Tuple[Union[float, Expr], 140 Union[float, Expr], 141 Union[float, Expr]]: 142 return (0.5 * self.unoriented_length, 143 0.5 * self.unoriented_width, 144 0.5 * self.unoriented_height) 145 146 @property 147 def unoriented_center_of_buoyancy(self) -> Tuple[Union[float, Expr], 148 Union[float, Expr], 149 Union[float, Expr]]: 150 return self.unoriented_center_of_gravity 151 152 @property 153 def unoriented_length(self) -> Union[float, Expr]: 154 return (2.0 * self.geometry.endcap_thickness) + self.geometry.cylinder_length 155 156 @property 157 def unoriented_width(self) -> Union[float, Expr]: 158 return 2.0 * self.geometry.cylinder_radius 159 160 @property 161 def unoriented_height(self) -> Union[float, Expr]: 162 return self.unoriented_width 163 164 @property 165 def oriented_length(self) -> Union[float, Expr]: 166 # TODO: Implement this 167 return 0 168 169 @property 170 def oriented_width(self) -> Union[float, Expr]: 171 # TODO: Implement this 172 return 0 173 174 @property 175 def oriented_height(self) -> Union[float, Expr]: 176 # TODO: Implement this 177 return 0
25class FlangedFlatCapsule(CompositeShape): 26 """Model representing a parameteric capsule with flanged flat 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 flat 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 flanged flat 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 `FlangedFlatCapsule`.""" 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 endcap_thickness_mm = 1000.0 * params['endcap_thickness'] 71 front = Part.makeCylinder(outer_cylinder_radius_mm, endcap_thickness_mm) 72 front = front.makeFillet(endcap_thickness_mm - 0.001, front.Edges[0:1]) 73 rear = Part.makeCylinder(outer_cylinder_radius_mm, endcap_thickness_mm) 74 rear = rear.makeFillet(endcap_thickness_mm - 0.001, rear.Edges[0:1]) 75 if fully_displace: 76 pipe = Part.makeCylinder(outer_cylinder_radius_mm, cylinder_length_mm) 77 else: 78 pipe2d = Part.makeRuledSurface(Part.makeCircle(outer_cylinder_radius_mm), 79 Part.makeCircle(inner_cylinder_radius_mm)) 80 pipe = pipe2d.extrude(FreeCAD.Vector(0, 0, cylinder_length_mm)) 81 pipe.Placement = FreeCAD.Placement(FreeCAD.Vector(0, 0, 0), 82 FreeCAD.Rotation(0, 90, 0)) 83 front.Placement = \ 84 FreeCAD.Placement(FreeCAD.Vector(0, 0, 0), 85 FreeCAD.Rotation(0, -90, 0)) 86 rear.Placement = \ 87 FreeCAD.Placement(FreeCAD.Vector(cylinder_length_mm, 0, 0), 88 FreeCAD.Rotation(0, 90, 0)) 89 return front.generalFuse([pipe, rear])[0] 90 91 92 # Geometry setter ------------------------------------------------------------------------------ 93 94 def set_geometry(self, *, cylinder_radius_m: Union[float, None], 95 cylinder_length_m: Union[float, None], 96 cylinder_thickness_m: Union[float, None], 97 endcap_thickness_m: Union[float, None]) -> FlangedFlatCapsule: 98 """Sets the physical geometry of the current `FlangedFlatCapsule` object. 99 100 See the `FlangedFlatCapsule` class documentation for a description of each geometric 101 parameter. 102 """ 103 self.geometry.set(cylinder_radius=cylinder_radius_m, 104 cylinder_length=cylinder_length_m, 105 cylinder_thickness=cylinder_thickness_m, 106 endcap_thickness=endcap_thickness_m) 107 return self 108 109 def get_geometric_parameter_bounds(self, parameter: str) -> Tuple[float, float]: 110 parameter_bounds = { 111 'cylinder_radius': (0.01, 2.0), 112 'cylinder_length': (0.01, 2.0), 113 'cylinder_thickness': (0.001, 0.05), 114 'endcap_thickness': (0.001, 0.05) 115 } 116 return parameter_bounds.get(parameter, (0.0, 0.0)) 117 118 119 # Geometric properties ------------------------------------------------------------------------- 120 121 @property 122 def material_volume(self) -> Union[float, Expr]: 123 volume = self.displaced_volume 124 volume -= (math.pi 125 * (self.geometry.cylinder_radius - self.geometry.cylinder_thickness)**2 126 * self.geometry.cylinder_length) 127 return volume 128 129 @property 130 def displaced_volume(self) -> Union[float, Expr]: 131 return (math.pi * self.geometry.cylinder_radius**2 * self.geometry.cylinder_length) + \ 132 (2.0 * math.pi * self.geometry.endcap_thickness * self.geometry.cylinder_radius**2) 133 134 @property 135 def surface_area(self) -> Union[float, Expr]: 136 return (2.0 * math.pi * self.geometry.cylinder_radius * self.geometry.cylinder_length) + \ 137 (2.0 * math.pi * self.geometry.cylinder_radius * self.geometry.endcap_thickness) 138 139 @property 140 def unoriented_center_of_gravity(self) -> Tuple[Union[float, Expr], 141 Union[float, Expr], 142 Union[float, Expr]]: 143 return (0.5 * self.unoriented_length, 144 0.5 * self.unoriented_width, 145 0.5 * self.unoriented_height) 146 147 @property 148 def unoriented_center_of_buoyancy(self) -> Tuple[Union[float, Expr], 149 Union[float, Expr], 150 Union[float, Expr]]: 151 return self.unoriented_center_of_gravity 152 153 @property 154 def unoriented_length(self) -> Union[float, Expr]: 155 return (2.0 * self.geometry.endcap_thickness) + self.geometry.cylinder_length 156 157 @property 158 def unoriented_width(self) -> Union[float, Expr]: 159 return 2.0 * self.geometry.cylinder_radius 160 161 @property 162 def unoriented_height(self) -> Union[float, Expr]: 163 return self.unoriented_width 164 165 @property 166 def oriented_length(self) -> Union[float, Expr]: 167 # TODO: Implement this 168 return 0 169 170 @property 171 def oriented_width(self) -> Union[float, Expr]: 172 # TODO: Implement this 173 return 0 174 175 @property 176 def oriented_height(self) -> Union[float, Expr]: 177 # TODO: Implement this 178 return 0
Model representing a parameteric capsule with flanged flat 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 flat 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 flanged flat 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 flanged flat 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.
94 def set_geometry(self, *, cylinder_radius_m: Union[float, None], 95 cylinder_length_m: Union[float, None], 96 cylinder_thickness_m: Union[float, None], 97 endcap_thickness_m: Union[float, None]) -> FlangedFlatCapsule: 98 """Sets the physical geometry of the current `FlangedFlatCapsule` object. 99 100 See the `FlangedFlatCapsule` class documentation for a description of each geometric 101 parameter. 102 """ 103 self.geometry.set(cylinder_radius=cylinder_radius_m, 104 cylinder_length=cylinder_length_m, 105 cylinder_thickness=cylinder_thickness_m, 106 endcap_thickness=endcap_thickness_m) 107 return self
Sets the physical geometry of the current FlangedFlatCapsule object.
See the FlangedFlatCapsule class documentation for a description of each geometric
parameter.
109 def get_geometric_parameter_bounds(self, parameter: str) -> Tuple[float, float]: 110 parameter_bounds = { 111 'cylinder_radius': (0.01, 2.0), 112 'cylinder_length': (0.01, 2.0), 113 'cylinder_thickness': (0.001, 0.05), 114 'endcap_thickness': (0.001, 0.05) 115 } 116 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