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   ![FlangedFlatCapsule](https://symbench.github.io/SymCAD/images/FlangedFlatCapsule.png)
 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
class FlangedFlatCapsule(symcad.parts.composite.CompositeShape):
 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   ![FlangedFlatCapsule](https://symbench.github.io/SymCAD/images/FlangedFlatCapsule.png)
 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:

FlangedFlatCapsule

The geometry of this shape includes the following parameters:

  • cylinder_radius: Outer radius (in m) of the center cylindrical part of the Capsule
  • cylinder_length: Length (in m) of the center cylindrical part of the Capsule
  • cylinder_thickness: Thickness (in m) of the cylindrical shell of the Capsule
  • endcap_thickness: Thickness (in m) 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.

FlangedFlatCapsule(identifier: str, material_density_kg_m3: Optional[float] = 1.0)
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 in kg/m^3 to be used in mass property calculations.
def set_geometry( self, *, cylinder_radius_m: Optional[float], cylinder_length_m: Optional[float], cylinder_thickness_m: Optional[float], endcap_thickness_m: Optional[float]) -> FlangedFlatCapsule:
 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.

def get_geometric_parameter_bounds(self, parameter: str) -> Tuple[float, float]:
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 geometric parameter.
material_volume: Union[float, sympy.core.expr.Expr]

Material volume (in m^3) of the SymPart (read-only).

displaced_volume: Union[float, sympy.core.expr.Expr]

Displaced volume (in m^3) of the SymPart (read-only).

surface_area: Union[float, sympy.core.expr.Expr]

Surface/wetted area (in m^2) of the SymPart (read-only).

unoriented_center_of_gravity: Tuple[Union[float, sympy.core.expr.Expr], Union[float, sympy.core.expr.Expr], Union[float, sympy.core.expr.Expr]]

Center of gravity (in m) of the unoriented SymPart (read-only).

unoriented_center_of_buoyancy: Tuple[Union[float, sympy.core.expr.Expr], Union[float, sympy.core.expr.Expr], Union[float, sympy.core.expr.Expr]]

Center of buoyancy (in m) of the unoriented SymPart (read-only).

unoriented_length: Union[float, sympy.core.expr.Expr]

X-axis length (in m) of the bounding box of the unoriented SymPart (read-only).

unoriented_width: Union[float, sympy.core.expr.Expr]

Y-axis width (in m) of the bounding box of the unoriented SymPart (read-only).

unoriented_height: Union[float, sympy.core.expr.Expr]

Z-axis height (in m) of the bounding box of the unoriented SymPart (read-only).

oriented_length: Union[float, sympy.core.expr.Expr]

X-axis length (in m) of the bounding box of the oriented SymPart (read-only).

oriented_width: Union[float, sympy.core.expr.Expr]

Y-axis length (in m) of the bounding box of the oriented SymPart (read-only).

oriented_height: Union[float, sympy.core.expr.Expr]

Z-axis length (in m) of the bounding box of the oriented SymPart (read-only).