symcad.parts.generic.Custom
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 typing import Callable, Optional, Tuple, Union 19from ...core.CAD import CadGeneral 20from ...core.ML import NeuralNet 21from sympy import Expr, Symbol 22from . import GenericShape 23from pathlib import Path 24 25class Custom(GenericShape): 26 """Model representing a custom generic part. 27 28 This class should be used to define a new type of CAD model from within an external library or 29 application, without the need to alter the built-in library of parts within SymCAD. 30 31 Regardless of whether an existing CAD model (parameteric or fixed-geometry) or a CAD creation 32 function is used to create this part, SymCAD will automatically retrieve any geometric free 33 parameters specified in the model and create symbols for them. 34 """ 35 36 # Constructor ---------------------------------------------------------------------------------- 37 38 def __init__(self, type_name: str, 39 identifier: str, 40 cad_representation: Union[str, Callable], 41 pretrained_geometric_properties_model: Union[str, None] = None, 42 material_density_kg_m3: Optional[float] = 1.0, 43 auto_train_missing_property_model: bool = True) -> None: 44 """Initializes a custom `GenericShape` part object. 45 46 If the `Custom` part is parametric and requires a trained neural network to evaluate its 47 geometric or mass properties, this network should be trained using the accompanying 48 `NeuralNetTrainer` tool, and the full path to the resulting neural network model should be 49 passed in using the `pretrained_geometric_properties_model` parameter. Alternately, a value 50 of `None` may be passed in for this parameter, and a new model will automatically be 51 trained if the `auto_train_missing_property_model` parameter is set to `True`. 52 53 Parameters 54 ---------- 55 type_name : `str` 56 Name identifying the type of part of this object. 57 identifier : `str` 58 Unique identifying name for this instance of the object. 59 cad_representation : `Union[str, Callable]` 60 Either the path to a representative CAD model for the given part or a callable 61 method that can create such a model. 62 pretrained_geometric_properties_model : `Union[str, None]`, default=None 63 Path to a neural network that represents the underlying geometric properties for 64 the given `Custom` part, or `None` if no model is required. 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 auto_train_missing_property_model : `bool`, default=True 68 Whether to automatically train new a neural network model to evaluate the geometric 69 properties of the `Custom` part. 70 """ 71 72 # Initialize the Custom part and detect its underlying free parameters 73 super().__init__(identifier, cad_representation, None, material_density_kg_m3) 74 free_params = CadGeneral.get_free_parameters_from_model(self.__cad__.cad_file_path, None) \ 75 if isinstance(cad_representation, str) else \ 76 CadGeneral.get_free_parameters_from_method(self.__cad__.creation_callback) 77 for param in free_params: 78 setattr(self.geometry, param, Symbol(self.name + '_' + param)) 79 80 # Retrieve a physical property representation based on whether the part is fully concrete 81 if len(free_params) == 0: 82 self.__neural_net__ = None 83 self.__cad_props__ = self.__cad__.get_physical_properties(self.geometry.as_dict(), 84 (0.0, 0.0, 0.0), 85 (0.0, 0.0, 0.0), 86 self.material_density, 87 True) 88 else: 89 self.__cad_props__ = None 90 self.__neural_net__ = NeuralNet(type_name, 91 pretrained_geometric_properties_model 92 if pretrained_geometric_properties_model else 93 Path('converted').joinpath(type_name + '.tar.xz'), 94 self, 95 auto_train_missing_property_model) 96 97 98 # Geometry setter ------------------------------------------------------------------------------ 99 100 def set_geometry(self, **kwargs) -> Custom: 101 """Sets the physical geometry of the current `Custom` object. 102 103 The `**kwargs` argument should include key-value pairs containing the name and desired 104 concrete value for any geometric parameters of interest. In order to determine the parameters 105 available, you should inspect the `geometry` attribute of the current `Custom` instance 106 (e.g., `print(custom_shape.geometry)`). 107 108 Parameters 109 ---------- 110 **kwargs : `Dict` 111 Set of named parameters that define the geometry of this Custom part. If the named 112 parameter is missing or set to `None` for any geometric parameter, that parameter will be 113 treated as a symbol. 114 """ 115 116 # Re-initialize the physical property representation of the part based on whether it is 117 # fully concrete after the update to its underlying geometry 118 free_params = [] 119 self.geometry.set(**kwargs) 120 for key in self.geometry.__dict__: 121 if key != 'name' and isinstance(getattr(self.geometry, key), Expr): 122 free_params.append(key) 123 if len(free_params) == 0: 124 self.__cad_props__ = self.__cad__.get_physical_properties(self.geometry.as_dict(), 125 (0.0, 0.0, 0.0), 126 (0.0, 0.0, 0.0), 127 self.material_density, 128 True) 129 else: 130 self.__cad_props__ = None 131 return self 132 133 def get_geometric_parameter_bounds(self, _parameter: str) -> Tuple[float, float]: 134 return 0.0, 2.0 135 136 137 # Geometric properties ------------------------------------------------------------------------- 138 139 @property 140 def material_volume(self) -> Union[float, Expr]: 141 return self.__cad_props__['material_volume'] if self.__cad_props__ else \ 142 self.__neural_net__.evaluate('material_volume', **self.geometry.as_dict()) 143 144 @property 145 def displaced_volume(self) -> Union[float, Expr]: 146 return self.__cad_props__['displaced_volume'] if self.__cad_props__ else \ 147 self.__neural_net__.evaluate('displaced_volume', **self.geometry.as_dict()) 148 149 @property 150 def surface_area(self) -> Union[float, Expr]: 151 return self.__cad_props__['surface_area'] if self.__cad_props__ else \ 152 self.__neural_net__.evaluate('surface_area', **self.geometry.as_dict()) 153 154 @property 155 def unoriented_center_of_gravity(self) -> Tuple[Union[float, Expr], 156 Union[float, Expr], 157 Union[float, Expr]]: 158 return (self.__cad_props__['cg_x'], self.__cad_props__['cg_y'], self.__cad_props__['cg_z']) \ 159 if self.__cad_props__ else \ 160 (self.__neural_net__.evaluate('cg_x', **self.geometry.as_dict()), 161 self.__neural_net__.evaluate('cg_y', **self.geometry.as_dict()), 162 self.__neural_net__.evaluate('cg_z', **self.geometry.as_dict())) 163 164 @property 165 def unoriented_center_of_buoyancy(self) -> Tuple[Union[float, Expr], 166 Union[float, Expr], 167 Union[float, Expr]]: 168 return (self.__cad_props__['cb_x'], self.__cad_props__['cb_y'], self.__cad_props__['cb_z']) \ 169 if self.__cad_props__ else \ 170 (self.__neural_net__.evaluate('cb_x', **self.geometry.as_dict()), 171 self.__neural_net__.evaluate('cb_y', **self.geometry.as_dict()), 172 self.__neural_net__.evaluate('cb_z', **self.geometry.as_dict())) 173 174 @property 175 def unoriented_length(self) -> Union[float, Expr]: 176 return self.__cad_props__['xlen'] if self.__cad_props__ else \ 177 self.__neural_net__.evaluate('xlen', **self.geometry.as_dict()) 178 179 @property 180 def unoriented_width(self) -> Union[float, Expr]: 181 return self.__cad_props__['ylen'] if self.__cad_props__ else \ 182 self.__neural_net__.evaluate('ylen', **self.geometry.as_dict()) 183 184 @property 185 def unoriented_height(self) -> Union[float, Expr]: 186 return self.__cad_props__['zlen'] if self.__cad_props__ else \ 187 self.__neural_net__.evaluate('zlen', **self.geometry.as_dict()) 188 189 @property 190 def oriented_length(self) -> Union[float, Expr]: 191 # TODO: Implement this 192 return 0 193 194 @property 195 def oriented_width(self) -> Union[float, Expr]: 196 # TODO: Implement this 197 return 0 198 199 @property 200 def oriented_height(self) -> Union[float, Expr]: 201 # TODO: Implement this 202 return 0
26class Custom(GenericShape): 27 """Model representing a custom generic part. 28 29 This class should be used to define a new type of CAD model from within an external library or 30 application, without the need to alter the built-in library of parts within SymCAD. 31 32 Regardless of whether an existing CAD model (parameteric or fixed-geometry) or a CAD creation 33 function is used to create this part, SymCAD will automatically retrieve any geometric free 34 parameters specified in the model and create symbols for them. 35 """ 36 37 # Constructor ---------------------------------------------------------------------------------- 38 39 def __init__(self, type_name: str, 40 identifier: str, 41 cad_representation: Union[str, Callable], 42 pretrained_geometric_properties_model: Union[str, None] = None, 43 material_density_kg_m3: Optional[float] = 1.0, 44 auto_train_missing_property_model: bool = True) -> None: 45 """Initializes a custom `GenericShape` part object. 46 47 If the `Custom` part is parametric and requires a trained neural network to evaluate its 48 geometric or mass properties, this network should be trained using the accompanying 49 `NeuralNetTrainer` tool, and the full path to the resulting neural network model should be 50 passed in using the `pretrained_geometric_properties_model` parameter. Alternately, a value 51 of `None` may be passed in for this parameter, and a new model will automatically be 52 trained if the `auto_train_missing_property_model` parameter is set to `True`. 53 54 Parameters 55 ---------- 56 type_name : `str` 57 Name identifying the type of part of this object. 58 identifier : `str` 59 Unique identifying name for this instance of the object. 60 cad_representation : `Union[str, Callable]` 61 Either the path to a representative CAD model for the given part or a callable 62 method that can create such a model. 63 pretrained_geometric_properties_model : `Union[str, None]`, default=None 64 Path to a neural network that represents the underlying geometric properties for 65 the given `Custom` part, or `None` if no model is required. 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 auto_train_missing_property_model : `bool`, default=True 69 Whether to automatically train new a neural network model to evaluate the geometric 70 properties of the `Custom` part. 71 """ 72 73 # Initialize the Custom part and detect its underlying free parameters 74 super().__init__(identifier, cad_representation, None, material_density_kg_m3) 75 free_params = CadGeneral.get_free_parameters_from_model(self.__cad__.cad_file_path, None) \ 76 if isinstance(cad_representation, str) else \ 77 CadGeneral.get_free_parameters_from_method(self.__cad__.creation_callback) 78 for param in free_params: 79 setattr(self.geometry, param, Symbol(self.name + '_' + param)) 80 81 # Retrieve a physical property representation based on whether the part is fully concrete 82 if len(free_params) == 0: 83 self.__neural_net__ = None 84 self.__cad_props__ = self.__cad__.get_physical_properties(self.geometry.as_dict(), 85 (0.0, 0.0, 0.0), 86 (0.0, 0.0, 0.0), 87 self.material_density, 88 True) 89 else: 90 self.__cad_props__ = None 91 self.__neural_net__ = NeuralNet(type_name, 92 pretrained_geometric_properties_model 93 if pretrained_geometric_properties_model else 94 Path('converted').joinpath(type_name + '.tar.xz'), 95 self, 96 auto_train_missing_property_model) 97 98 99 # Geometry setter ------------------------------------------------------------------------------ 100 101 def set_geometry(self, **kwargs) -> Custom: 102 """Sets the physical geometry of the current `Custom` object. 103 104 The `**kwargs` argument should include key-value pairs containing the name and desired 105 concrete value for any geometric parameters of interest. In order to determine the parameters 106 available, you should inspect the `geometry` attribute of the current `Custom` instance 107 (e.g., `print(custom_shape.geometry)`). 108 109 Parameters 110 ---------- 111 **kwargs : `Dict` 112 Set of named parameters that define the geometry of this Custom part. If the named 113 parameter is missing or set to `None` for any geometric parameter, that parameter will be 114 treated as a symbol. 115 """ 116 117 # Re-initialize the physical property representation of the part based on whether it is 118 # fully concrete after the update to its underlying geometry 119 free_params = [] 120 self.geometry.set(**kwargs) 121 for key in self.geometry.__dict__: 122 if key != 'name' and isinstance(getattr(self.geometry, key), Expr): 123 free_params.append(key) 124 if len(free_params) == 0: 125 self.__cad_props__ = self.__cad__.get_physical_properties(self.geometry.as_dict(), 126 (0.0, 0.0, 0.0), 127 (0.0, 0.0, 0.0), 128 self.material_density, 129 True) 130 else: 131 self.__cad_props__ = None 132 return self 133 134 def get_geometric_parameter_bounds(self, _parameter: str) -> Tuple[float, float]: 135 return 0.0, 2.0 136 137 138 # Geometric properties ------------------------------------------------------------------------- 139 140 @property 141 def material_volume(self) -> Union[float, Expr]: 142 return self.__cad_props__['material_volume'] if self.__cad_props__ else \ 143 self.__neural_net__.evaluate('material_volume', **self.geometry.as_dict()) 144 145 @property 146 def displaced_volume(self) -> Union[float, Expr]: 147 return self.__cad_props__['displaced_volume'] if self.__cad_props__ else \ 148 self.__neural_net__.evaluate('displaced_volume', **self.geometry.as_dict()) 149 150 @property 151 def surface_area(self) -> Union[float, Expr]: 152 return self.__cad_props__['surface_area'] if self.__cad_props__ else \ 153 self.__neural_net__.evaluate('surface_area', **self.geometry.as_dict()) 154 155 @property 156 def unoriented_center_of_gravity(self) -> Tuple[Union[float, Expr], 157 Union[float, Expr], 158 Union[float, Expr]]: 159 return (self.__cad_props__['cg_x'], self.__cad_props__['cg_y'], self.__cad_props__['cg_z']) \ 160 if self.__cad_props__ else \ 161 (self.__neural_net__.evaluate('cg_x', **self.geometry.as_dict()), 162 self.__neural_net__.evaluate('cg_y', **self.geometry.as_dict()), 163 self.__neural_net__.evaluate('cg_z', **self.geometry.as_dict())) 164 165 @property 166 def unoriented_center_of_buoyancy(self) -> Tuple[Union[float, Expr], 167 Union[float, Expr], 168 Union[float, Expr]]: 169 return (self.__cad_props__['cb_x'], self.__cad_props__['cb_y'], self.__cad_props__['cb_z']) \ 170 if self.__cad_props__ else \ 171 (self.__neural_net__.evaluate('cb_x', **self.geometry.as_dict()), 172 self.__neural_net__.evaluate('cb_y', **self.geometry.as_dict()), 173 self.__neural_net__.evaluate('cb_z', **self.geometry.as_dict())) 174 175 @property 176 def unoriented_length(self) -> Union[float, Expr]: 177 return self.__cad_props__['xlen'] if self.__cad_props__ else \ 178 self.__neural_net__.evaluate('xlen', **self.geometry.as_dict()) 179 180 @property 181 def unoriented_width(self) -> Union[float, Expr]: 182 return self.__cad_props__['ylen'] if self.__cad_props__ else \ 183 self.__neural_net__.evaluate('ylen', **self.geometry.as_dict()) 184 185 @property 186 def unoriented_height(self) -> Union[float, Expr]: 187 return self.__cad_props__['zlen'] if self.__cad_props__ else \ 188 self.__neural_net__.evaluate('zlen', **self.geometry.as_dict()) 189 190 @property 191 def oriented_length(self) -> Union[float, Expr]: 192 # TODO: Implement this 193 return 0 194 195 @property 196 def oriented_width(self) -> Union[float, Expr]: 197 # TODO: Implement this 198 return 0 199 200 @property 201 def oriented_height(self) -> Union[float, Expr]: 202 # TODO: Implement this 203 return 0
Model representing a custom generic part.
This class should be used to define a new type of CAD model from within an external library or application, without the need to alter the built-in library of parts within SymCAD.
Regardless of whether an existing CAD model (parameteric or fixed-geometry) or a CAD creation function is used to create this part, SymCAD will automatically retrieve any geometric free parameters specified in the model and create symbols for them.
39 def __init__(self, type_name: str, 40 identifier: str, 41 cad_representation: Union[str, Callable], 42 pretrained_geometric_properties_model: Union[str, None] = None, 43 material_density_kg_m3: Optional[float] = 1.0, 44 auto_train_missing_property_model: bool = True) -> None: 45 """Initializes a custom `GenericShape` part object. 46 47 If the `Custom` part is parametric and requires a trained neural network to evaluate its 48 geometric or mass properties, this network should be trained using the accompanying 49 `NeuralNetTrainer` tool, and the full path to the resulting neural network model should be 50 passed in using the `pretrained_geometric_properties_model` parameter. Alternately, a value 51 of `None` may be passed in for this parameter, and a new model will automatically be 52 trained if the `auto_train_missing_property_model` parameter is set to `True`. 53 54 Parameters 55 ---------- 56 type_name : `str` 57 Name identifying the type of part of this object. 58 identifier : `str` 59 Unique identifying name for this instance of the object. 60 cad_representation : `Union[str, Callable]` 61 Either the path to a representative CAD model for the given part or a callable 62 method that can create such a model. 63 pretrained_geometric_properties_model : `Union[str, None]`, default=None 64 Path to a neural network that represents the underlying geometric properties for 65 the given `Custom` part, or `None` if no model is required. 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 auto_train_missing_property_model : `bool`, default=True 69 Whether to automatically train new a neural network model to evaluate the geometric 70 properties of the `Custom` part. 71 """ 72 73 # Initialize the Custom part and detect its underlying free parameters 74 super().__init__(identifier, cad_representation, None, material_density_kg_m3) 75 free_params = CadGeneral.get_free_parameters_from_model(self.__cad__.cad_file_path, None) \ 76 if isinstance(cad_representation, str) else \ 77 CadGeneral.get_free_parameters_from_method(self.__cad__.creation_callback) 78 for param in free_params: 79 setattr(self.geometry, param, Symbol(self.name + '_' + param)) 80 81 # Retrieve a physical property representation based on whether the part is fully concrete 82 if len(free_params) == 0: 83 self.__neural_net__ = None 84 self.__cad_props__ = self.__cad__.get_physical_properties(self.geometry.as_dict(), 85 (0.0, 0.0, 0.0), 86 (0.0, 0.0, 0.0), 87 self.material_density, 88 True) 89 else: 90 self.__cad_props__ = None 91 self.__neural_net__ = NeuralNet(type_name, 92 pretrained_geometric_properties_model 93 if pretrained_geometric_properties_model else 94 Path('converted').joinpath(type_name + '.tar.xz'), 95 self, 96 auto_train_missing_property_model)
Initializes a custom GenericShape part object.
If the Custom part is parametric and requires a trained neural network to evaluate its
geometric or mass properties, this network should be trained using the accompanying
NeuralNetTrainer tool, and the full path to the resulting neural network model should be
passed in using the pretrained_geometric_properties_model parameter. Alternately, a value
of None may be passed in for this parameter, and a new model will automatically be
trained if the auto_train_missing_property_model parameter is set to True.
Parameters
- type_name (
str): Name identifying the type of part of this object. - identifier (
str): Unique identifying name for this instance of the object. - cad_representation (
Union[str, Callable]): Either the path to a representative CAD model for the given part or a callable method that can create such a model. - pretrained_geometric_properties_model (
Union[str, None], default=None): Path to a neural network that represents the underlying geometric properties for the givenCustompart, orNoneif no model is required. - material_density_kg_m3 (
float, optional, default=1.0): Uniform material density inkg/m^3to be used in mass property calculations. - auto_train_missing_property_model (
bool, default=True): Whether to automatically train new a neural network model to evaluate the geometric properties of theCustompart.
101 def set_geometry(self, **kwargs) -> Custom: 102 """Sets the physical geometry of the current `Custom` object. 103 104 The `**kwargs` argument should include key-value pairs containing the name and desired 105 concrete value for any geometric parameters of interest. In order to determine the parameters 106 available, you should inspect the `geometry` attribute of the current `Custom` instance 107 (e.g., `print(custom_shape.geometry)`). 108 109 Parameters 110 ---------- 111 **kwargs : `Dict` 112 Set of named parameters that define the geometry of this Custom part. If the named 113 parameter is missing or set to `None` for any geometric parameter, that parameter will be 114 treated as a symbol. 115 """ 116 117 # Re-initialize the physical property representation of the part based on whether it is 118 # fully concrete after the update to its underlying geometry 119 free_params = [] 120 self.geometry.set(**kwargs) 121 for key in self.geometry.__dict__: 122 if key != 'name' and isinstance(getattr(self.geometry, key), Expr): 123 free_params.append(key) 124 if len(free_params) == 0: 125 self.__cad_props__ = self.__cad__.get_physical_properties(self.geometry.as_dict(), 126 (0.0, 0.0, 0.0), 127 (0.0, 0.0, 0.0), 128 self.material_density, 129 True) 130 else: 131 self.__cad_props__ = None 132 return self
Sets the physical geometry of the current Custom object.
The **kwargs argument should include key-value pairs containing the name and desired
concrete value for any geometric parameters of interest. In order to determine the parameters
available, you should inspect the geometry attribute of the current Custom instance
(e.g., print(custom_shape.geometry)).
Parameters
- **kwargs (
Dict): Set of named parameters that define the geometry of this Custom part. If the named parameter is missing or set toNonefor any geometric parameter, that parameter will be treated as a symbol.
134 def get_geometric_parameter_bounds(self, _parameter: str) -> Tuple[float, float]: 135 return 0.0, 2.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