symcad.core.CAD.ScriptedCad

  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, Dict, Literal, Optional, Tuple
 19from PyFreeCAD.FreeCAD import FreeCAD, Part
 20from .CadGeneral import is_symbolic
 21from . import CadGeneral
 22from pathlib import Path
 23
 24TESSELATION_VALUE = 1.0
 25
 26class ScriptedCad(object):
 27   """Private helper class to generate a CAD representation from a `SymPart`."""
 28
 29   # Public attributes ----------------------------------------------------------------------------
 30
 31   creation_callback: Callable[[Dict[str, float], bool], Part.Solid]
 32   """Creation callback for a `SymPart` to generate a concrete OpenCascade model."""
 33
 34
 35   # Constructor ----------------------------------------------------------------------------------
 36
 37   def __init__(self, creation_callback: Callable[[Dict[str, float], bool], Part.Solid]) -> None:
 38      """Initializes a `ScriptedCad` object, where `creation_callback` is a shape-specific
 39      method that uses a dictionary of `geometry: value` entries to create an OpenCascade
 40      `Part.Solid` for a given `SymPart`.
 41      """
 42      self.creation_callback = creation_callback
 43
 44
 45   # Built-in method implementations --------------------------------------------------------------
 46
 47   def __eq__(self, other: ScriptedCad) -> bool:
 48      return self.creation_callback.__qualname__ == other.creation_callback.__qualname__
 49
 50
 51   # Public methods -------------------------------------------------------------------------------
 52
 53   def add_to_assembly(self, model_name: str,
 54                             assembly: FreeCAD.Document,
 55                             concrete_parameters: Dict[str, float],
 56                             placement_point: Tuple[float, float, float],
 57                             placement_m: Tuple[float, float, float],
 58                             yaw_pitch_roll_deg: Tuple[float, float, float],
 59                             fully_displace: Optional[bool] = False) -> None:
 60      """Adds a CAD model representation of the `SymPart` to the specified assembly.
 61
 62      Parameters
 63      ----------
 64      model_name : `str`
 65         Desired name for the CAD model.
 66      assembly : `FreeCAD.Document`
 67         FreeCAD assembly document to which the CAD model should be added.
 68      concrete_parameters : `Dict[str, float]`
 69         Dictionary of free variables along with their desired concrete values.
 70      placement_point : `Tuple[float, float, float]`
 71         Local coordinate (in percent length) to be used for the center of rotation and placement
 72         of the CAD model.
 73      placement_m : `Tuple[float, float, float]`
 74         Global xyz-placement (in `m`) of the `placement_point` of the CAD object in the assembly.
 75      yaw_pitch_roll_deg : `Tuple[float, float, float]`
 76         Global yaw-, pitch-, and roll-orientation in degrees of the CAD object.
 77      fully_displace : `bool`
 78         Whether to create a fully solid CAD model for displacement purposes.
 79      """
 80
 81      # Verify that all parameters are concrete
 82      if is_symbolic(yaw_pitch_roll_deg[0]) or is_symbolic(yaw_pitch_roll_deg[1]) or \
 83                                               is_symbolic(yaw_pitch_roll_deg[2]):
 84         raise RuntimeError('The orientation of the part ("{}") must not be symbolic to add it '
 85                            'to a CAD assembly'.format(yaw_pitch_roll_deg))
 86      for key, val in concrete_parameters.items():
 87         if key != 'name' and is_symbolic(val):
 88            raise RuntimeError('The geometric parameter "{}" of the part must not be symbolic to '
 89                               'add it to a CAD assembly'.format(key))
 90
 91      # Create and add a new CAD model to the assembly
 92      model = assembly.addObject(CadGeneral.PART_FEATURE_STRING, model_name)
 93      model.Shape = self.creation_callback(concrete_parameters, fully_displace)
 94      model.Shape.tessellate(TESSELATION_VALUE)
 95      assembly.recompute()
 96
 97      # Properly place and orient the CAD model in the assembly
 98      rotation_point = CadGeneral.compute_placement_point(model.Shape, placement_point)
 99      placement = FreeCAD.Vector((1000.0 * placement_m[0]) - rotation_point.x,
100                                 (1000.0 * placement_m[1]) - rotation_point.y,
101                                 (1000.0 * placement_m[2]) - rotation_point.z)
102      rotation = FreeCAD.Rotation(*yaw_pitch_roll_deg)
103      model.Placement = FreeCAD.Placement(placement, rotation, rotation_point)
104      model.Shape.tessellate(TESSELATION_VALUE)
105      assembly.recompute()
106
107
108   def get_physical_properties(self, concrete_parameters: Dict[str, float],
109                                     placement_point: Tuple[float, float, float],
110                                     yaw_pitch_roll_deg: Tuple[float, float, float],
111                                     material_density_kg_m3: float,
112                                     normalize_origin: bool) -> Dict[str, float]:
113      """Returns all physical properties of the CAD model.
114
115      Mass properties will be computed assuming a uniform material density as specified in
116      the `material_density_kg_m3` parameter.
117
118      All free parameters within the CAD model must be concretized by passing in an appropriate
119      `concrete_parameters` dictionary containing the names of the free parameters as its keys,
120      along with their corresponding concrete floating-point values.
121
122      Parameters
123      ----------
124      concrete_parameters : `Dict[str, float]`
125         Dictionary of free variables along with their desired concrete values.
126      placement_point : `Tuple[float, float, float]`
127         Local coordinate (in percent length) to be used for the center of placement
128         of the CAD model.
129      yaw_pitch_roll_deg : `Tuple[float, float, float]`
130         Global yaw-, pitch-, and roll-orientation in degrees of the CAD object.
131      material_density_kg_m3 : `float`
132         Uniform material density to be used in mass property calculations (in `kg/m^3`).
133      normalize_origin : `bool`
134         Return physical properties with respect to the front, left, bottom corner of the
135         underlying CAD model.
136
137      Returns
138      -------
139      `Dict[str, float]`
140         A dictionary containing all physical `SymPart` properties as computed using the
141         underlying CAD model.
142      """
143
144      # Verify that all parameters are concrete
145      if is_symbolic(yaw_pitch_roll_deg[0]) or is_symbolic(yaw_pitch_roll_deg[1]) or \
146                                               is_symbolic(yaw_pitch_roll_deg[2]):
147         raise RuntimeError('The orientation of the part ("{}") must not be symbolic to calculate '
148                            'its physical properties from CAD'.format(yaw_pitch_roll_deg))
149      for key, val in concrete_parameters.items():
150         if key != 'name' and is_symbolic(val):
151            raise RuntimeError('The geometric parameter "{}" of the part must not be symbolic to '
152                               'calculate its physical properties from CAD'.format(key))
153      placement_point = [0.0 if is_symbolic(p) else float(p) for p in placement_point]
154
155      # Create the scripted CAD model
156      doc = FreeCAD.newDocument()
157      model = doc.addObject(CadGeneral.PART_FEATURE_STRING, 'Model')
158      model.Shape = self.creation_callback(concrete_parameters, False)
159      model.Shape.tessellate(TESSELATION_VALUE)
160      rotation_point = CadGeneral.compute_placement_point(model.Shape, placement_point)
161      placement = FreeCAD.Vector(-rotation_point.x, -rotation_point.y, -rotation_point.z)
162      rotation = FreeCAD.Rotation(*yaw_pitch_roll_deg)
163      model.Placement = FreeCAD.Placement(placement, rotation, rotation_point)
164      model.Shape.tessellate(TESSELATION_VALUE)
165
166      # Create a separate displacement model
167      displaced_model = doc.addObject(CadGeneral.PART_FEATURE_STRING, 'DisplacedModel')
168      displaced_model.Shape = self.creation_callback(concrete_parameters, True)
169      displaced_model.Shape.tessellate(TESSELATION_VALUE)
170      displaced_model.Placement = FreeCAD.Placement(placement, rotation, rotation_point)
171      displaced_model.Shape.tessellate(TESSELATION_VALUE)
172
173      # Retrieve all physical model properties
174      doc.recompute()
175      properties = CadGeneral.fetch_model_physical_properties(model.Shape,
176                                                              displaced_model.Shape,
177                                                              material_density_kg_m3,
178                                                              normalize_origin)
179      FreeCAD.closeDocument(doc.Name)
180      return properties
181
182
183   def export_model(self, file_save_path: str,
184                          model_type: Literal['freecad', 'step', 'stl'],
185                          concrete_parameters: Dict[str, float],
186                          placement_point: Tuple[float, float, float],
187                          yaw_pitch_roll_deg: Tuple[float, float, float]) -> None:
188      """Creates a CAD model of the `SymPart` in the specified format.
189
190      Parameters
191      ----------
192      file_save_path : `str`
193         Output file path at which to store the generated CAD model.
194      model_type : {'freecad', 'step', 'stl'}
195         Format of the CAD model to export.
196      concrete_parameters : `Dict[str, float]`
197         Dictionary of free variables along with their desired concrete values.
198      placement_point : `Tuple[float, float, float]`
199         Local coordinate (in percent length) to be used for the center of placement
200         of the CAD model.
201      yaw_pitch_roll_deg : `Tuple[float, float, float]`
202         Global yaw-, pitch-, and roll-orientation in degrees of the CAD object.
203      """
204
205      # Verify that all parameters have concrete representations
206      if is_symbolic(yaw_pitch_roll_deg[0]) or is_symbolic(yaw_pitch_roll_deg[1]) or \
207                                               is_symbolic(yaw_pitch_roll_deg[2]):
208         raise RuntimeError('The orientation of the part ("{}") must not be symbolic to export '
209                            'it as a CAD model'.format(yaw_pitch_roll_deg))
210      for key, val in concrete_parameters.items():
211         if key != 'name' and is_symbolic(val):
212            raise RuntimeError('The geometric parameter "{}" of the part must not be symbolic to '
213                               'export it as a CAD model'.format(key))
214      placement_point = [0.0 if is_symbolic(p) else float(p) for p in placement_point]
215
216      # Create any necessary path directories
217      file_path = Path(file_save_path).absolute().resolve()
218      if not file_path.parent.exists():
219         file_path.parent.mkdir()
220
221      # Create and tessellate the scripted CAD model
222      doc = FreeCAD.newDocument()
223      model = doc.addObject(CadGeneral.PART_FEATURE_STRING, 'Model')
224      model.Shape = self.creation_callback(concrete_parameters, False)
225      model.Shape.tessellate(TESSELATION_VALUE)
226      rotation_point = CadGeneral.compute_placement_point(model.Shape, placement_point)
227      placement = FreeCAD.Vector(-rotation_point.x, -rotation_point.y, -rotation_point.z)
228      rotation = FreeCAD.Rotation(*yaw_pitch_roll_deg)
229      model.Placement = FreeCAD.Placement(placement, rotation, rotation_point)
230      model.Shape.tessellate(TESSELATION_VALUE)
231      doc.recompute()
232
233      # Create the requested CAD format of the model
234      CadGeneral.save_model(file_path, model_type, model)
235      FreeCAD.closeDocument(doc.Name)
TESSELATION_VALUE = 1.0
class ScriptedCad:
 27class ScriptedCad(object):
 28   """Private helper class to generate a CAD representation from a `SymPart`."""
 29
 30   # Public attributes ----------------------------------------------------------------------------
 31
 32   creation_callback: Callable[[Dict[str, float], bool], Part.Solid]
 33   """Creation callback for a `SymPart` to generate a concrete OpenCascade model."""
 34
 35
 36   # Constructor ----------------------------------------------------------------------------------
 37
 38   def __init__(self, creation_callback: Callable[[Dict[str, float], bool], Part.Solid]) -> None:
 39      """Initializes a `ScriptedCad` object, where `creation_callback` is a shape-specific
 40      method that uses a dictionary of `geometry: value` entries to create an OpenCascade
 41      `Part.Solid` for a given `SymPart`.
 42      """
 43      self.creation_callback = creation_callback
 44
 45
 46   # Built-in method implementations --------------------------------------------------------------
 47
 48   def __eq__(self, other: ScriptedCad) -> bool:
 49      return self.creation_callback.__qualname__ == other.creation_callback.__qualname__
 50
 51
 52   # Public methods -------------------------------------------------------------------------------
 53
 54   def add_to_assembly(self, model_name: str,
 55                             assembly: FreeCAD.Document,
 56                             concrete_parameters: Dict[str, float],
 57                             placement_point: Tuple[float, float, float],
 58                             placement_m: Tuple[float, float, float],
 59                             yaw_pitch_roll_deg: Tuple[float, float, float],
 60                             fully_displace: Optional[bool] = False) -> None:
 61      """Adds a CAD model representation of the `SymPart` to the specified assembly.
 62
 63      Parameters
 64      ----------
 65      model_name : `str`
 66         Desired name for the CAD model.
 67      assembly : `FreeCAD.Document`
 68         FreeCAD assembly document to which the CAD model should be added.
 69      concrete_parameters : `Dict[str, float]`
 70         Dictionary of free variables along with their desired concrete values.
 71      placement_point : `Tuple[float, float, float]`
 72         Local coordinate (in percent length) to be used for the center of rotation and placement
 73         of the CAD model.
 74      placement_m : `Tuple[float, float, float]`
 75         Global xyz-placement (in `m`) of the `placement_point` of the CAD object in the assembly.
 76      yaw_pitch_roll_deg : `Tuple[float, float, float]`
 77         Global yaw-, pitch-, and roll-orientation in degrees of the CAD object.
 78      fully_displace : `bool`
 79         Whether to create a fully solid CAD model for displacement purposes.
 80      """
 81
 82      # Verify that all parameters are concrete
 83      if is_symbolic(yaw_pitch_roll_deg[0]) or is_symbolic(yaw_pitch_roll_deg[1]) or \
 84                                               is_symbolic(yaw_pitch_roll_deg[2]):
 85         raise RuntimeError('The orientation of the part ("{}") must not be symbolic to add it '
 86                            'to a CAD assembly'.format(yaw_pitch_roll_deg))
 87      for key, val in concrete_parameters.items():
 88         if key != 'name' and is_symbolic(val):
 89            raise RuntimeError('The geometric parameter "{}" of the part must not be symbolic to '
 90                               'add it to a CAD assembly'.format(key))
 91
 92      # Create and add a new CAD model to the assembly
 93      model = assembly.addObject(CadGeneral.PART_FEATURE_STRING, model_name)
 94      model.Shape = self.creation_callback(concrete_parameters, fully_displace)
 95      model.Shape.tessellate(TESSELATION_VALUE)
 96      assembly.recompute()
 97
 98      # Properly place and orient the CAD model in the assembly
 99      rotation_point = CadGeneral.compute_placement_point(model.Shape, placement_point)
100      placement = FreeCAD.Vector((1000.0 * placement_m[0]) - rotation_point.x,
101                                 (1000.0 * placement_m[1]) - rotation_point.y,
102                                 (1000.0 * placement_m[2]) - rotation_point.z)
103      rotation = FreeCAD.Rotation(*yaw_pitch_roll_deg)
104      model.Placement = FreeCAD.Placement(placement, rotation, rotation_point)
105      model.Shape.tessellate(TESSELATION_VALUE)
106      assembly.recompute()
107
108
109   def get_physical_properties(self, concrete_parameters: Dict[str, float],
110                                     placement_point: Tuple[float, float, float],
111                                     yaw_pitch_roll_deg: Tuple[float, float, float],
112                                     material_density_kg_m3: float,
113                                     normalize_origin: bool) -> Dict[str, float]:
114      """Returns all physical properties of the CAD model.
115
116      Mass properties will be computed assuming a uniform material density as specified in
117      the `material_density_kg_m3` parameter.
118
119      All free parameters within the CAD model must be concretized by passing in an appropriate
120      `concrete_parameters` dictionary containing the names of the free parameters as its keys,
121      along with their corresponding concrete floating-point values.
122
123      Parameters
124      ----------
125      concrete_parameters : `Dict[str, float]`
126         Dictionary of free variables along with their desired concrete values.
127      placement_point : `Tuple[float, float, float]`
128         Local coordinate (in percent length) to be used for the center of placement
129         of the CAD model.
130      yaw_pitch_roll_deg : `Tuple[float, float, float]`
131         Global yaw-, pitch-, and roll-orientation in degrees of the CAD object.
132      material_density_kg_m3 : `float`
133         Uniform material density to be used in mass property calculations (in `kg/m^3`).
134      normalize_origin : `bool`
135         Return physical properties with respect to the front, left, bottom corner of the
136         underlying CAD model.
137
138      Returns
139      -------
140      `Dict[str, float]`
141         A dictionary containing all physical `SymPart` properties as computed using the
142         underlying CAD model.
143      """
144
145      # Verify that all parameters are concrete
146      if is_symbolic(yaw_pitch_roll_deg[0]) or is_symbolic(yaw_pitch_roll_deg[1]) or \
147                                               is_symbolic(yaw_pitch_roll_deg[2]):
148         raise RuntimeError('The orientation of the part ("{}") must not be symbolic to calculate '
149                            'its physical properties from CAD'.format(yaw_pitch_roll_deg))
150      for key, val in concrete_parameters.items():
151         if key != 'name' and is_symbolic(val):
152            raise RuntimeError('The geometric parameter "{}" of the part must not be symbolic to '
153                               'calculate its physical properties from CAD'.format(key))
154      placement_point = [0.0 if is_symbolic(p) else float(p) for p in placement_point]
155
156      # Create the scripted CAD model
157      doc = FreeCAD.newDocument()
158      model = doc.addObject(CadGeneral.PART_FEATURE_STRING, 'Model')
159      model.Shape = self.creation_callback(concrete_parameters, False)
160      model.Shape.tessellate(TESSELATION_VALUE)
161      rotation_point = CadGeneral.compute_placement_point(model.Shape, placement_point)
162      placement = FreeCAD.Vector(-rotation_point.x, -rotation_point.y, -rotation_point.z)
163      rotation = FreeCAD.Rotation(*yaw_pitch_roll_deg)
164      model.Placement = FreeCAD.Placement(placement, rotation, rotation_point)
165      model.Shape.tessellate(TESSELATION_VALUE)
166
167      # Create a separate displacement model
168      displaced_model = doc.addObject(CadGeneral.PART_FEATURE_STRING, 'DisplacedModel')
169      displaced_model.Shape = self.creation_callback(concrete_parameters, True)
170      displaced_model.Shape.tessellate(TESSELATION_VALUE)
171      displaced_model.Placement = FreeCAD.Placement(placement, rotation, rotation_point)
172      displaced_model.Shape.tessellate(TESSELATION_VALUE)
173
174      # Retrieve all physical model properties
175      doc.recompute()
176      properties = CadGeneral.fetch_model_physical_properties(model.Shape,
177                                                              displaced_model.Shape,
178                                                              material_density_kg_m3,
179                                                              normalize_origin)
180      FreeCAD.closeDocument(doc.Name)
181      return properties
182
183
184   def export_model(self, file_save_path: str,
185                          model_type: Literal['freecad', 'step', 'stl'],
186                          concrete_parameters: Dict[str, float],
187                          placement_point: Tuple[float, float, float],
188                          yaw_pitch_roll_deg: Tuple[float, float, float]) -> None:
189      """Creates a CAD model of the `SymPart` in the specified format.
190
191      Parameters
192      ----------
193      file_save_path : `str`
194         Output file path at which to store the generated CAD model.
195      model_type : {'freecad', 'step', 'stl'}
196         Format of the CAD model to export.
197      concrete_parameters : `Dict[str, float]`
198         Dictionary of free variables along with their desired concrete values.
199      placement_point : `Tuple[float, float, float]`
200         Local coordinate (in percent length) to be used for the center of placement
201         of the CAD model.
202      yaw_pitch_roll_deg : `Tuple[float, float, float]`
203         Global yaw-, pitch-, and roll-orientation in degrees of the CAD object.
204      """
205
206      # Verify that all parameters have concrete representations
207      if is_symbolic(yaw_pitch_roll_deg[0]) or is_symbolic(yaw_pitch_roll_deg[1]) or \
208                                               is_symbolic(yaw_pitch_roll_deg[2]):
209         raise RuntimeError('The orientation of the part ("{}") must not be symbolic to export '
210                            'it as a CAD model'.format(yaw_pitch_roll_deg))
211      for key, val in concrete_parameters.items():
212         if key != 'name' and is_symbolic(val):
213            raise RuntimeError('The geometric parameter "{}" of the part must not be symbolic to '
214                               'export it as a CAD model'.format(key))
215      placement_point = [0.0 if is_symbolic(p) else float(p) for p in placement_point]
216
217      # Create any necessary path directories
218      file_path = Path(file_save_path).absolute().resolve()
219      if not file_path.parent.exists():
220         file_path.parent.mkdir()
221
222      # Create and tessellate the scripted CAD model
223      doc = FreeCAD.newDocument()
224      model = doc.addObject(CadGeneral.PART_FEATURE_STRING, 'Model')
225      model.Shape = self.creation_callback(concrete_parameters, False)
226      model.Shape.tessellate(TESSELATION_VALUE)
227      rotation_point = CadGeneral.compute_placement_point(model.Shape, placement_point)
228      placement = FreeCAD.Vector(-rotation_point.x, -rotation_point.y, -rotation_point.z)
229      rotation = FreeCAD.Rotation(*yaw_pitch_roll_deg)
230      model.Placement = FreeCAD.Placement(placement, rotation, rotation_point)
231      model.Shape.tessellate(TESSELATION_VALUE)
232      doc.recompute()
233
234      # Create the requested CAD format of the model
235      CadGeneral.save_model(file_path, model_type, model)
236      FreeCAD.closeDocument(doc.Name)

Private helper class to generate a CAD representation from a SymPart.

ScriptedCad(creation_callback: Callable[[Dict[str, float], bool], Part.Solid])
38   def __init__(self, creation_callback: Callable[[Dict[str, float], bool], Part.Solid]) -> None:
39      """Initializes a `ScriptedCad` object, where `creation_callback` is a shape-specific
40      method that uses a dictionary of `geometry: value` entries to create an OpenCascade
41      `Part.Solid` for a given `SymPart`.
42      """
43      self.creation_callback = creation_callback

Initializes a ScriptedCad object, where creation_callback is a shape-specific method that uses a dictionary of geometry: value entries to create an OpenCascade Part.Solid for a given SymPart.

creation_callback: Callable[[Dict[str, float], bool], Part.Solid]

Creation callback for a SymPart to generate a concrete OpenCascade model.

def add_to_assembly( self, model_name: str, assembly: App.Document, concrete_parameters: Dict[str, float], placement_point: Tuple[float, float, float], placement_m: Tuple[float, float, float], yaw_pitch_roll_deg: Tuple[float, float, float], fully_displace: Optional[bool] = False) -> None:
 54   def add_to_assembly(self, model_name: str,
 55                             assembly: FreeCAD.Document,
 56                             concrete_parameters: Dict[str, float],
 57                             placement_point: Tuple[float, float, float],
 58                             placement_m: Tuple[float, float, float],
 59                             yaw_pitch_roll_deg: Tuple[float, float, float],
 60                             fully_displace: Optional[bool] = False) -> None:
 61      """Adds a CAD model representation of the `SymPart` to the specified assembly.
 62
 63      Parameters
 64      ----------
 65      model_name : `str`
 66         Desired name for the CAD model.
 67      assembly : `FreeCAD.Document`
 68         FreeCAD assembly document to which the CAD model should be added.
 69      concrete_parameters : `Dict[str, float]`
 70         Dictionary of free variables along with their desired concrete values.
 71      placement_point : `Tuple[float, float, float]`
 72         Local coordinate (in percent length) to be used for the center of rotation and placement
 73         of the CAD model.
 74      placement_m : `Tuple[float, float, float]`
 75         Global xyz-placement (in `m`) of the `placement_point` of the CAD object in the assembly.
 76      yaw_pitch_roll_deg : `Tuple[float, float, float]`
 77         Global yaw-, pitch-, and roll-orientation in degrees of the CAD object.
 78      fully_displace : `bool`
 79         Whether to create a fully solid CAD model for displacement purposes.
 80      """
 81
 82      # Verify that all parameters are concrete
 83      if is_symbolic(yaw_pitch_roll_deg[0]) or is_symbolic(yaw_pitch_roll_deg[1]) or \
 84                                               is_symbolic(yaw_pitch_roll_deg[2]):
 85         raise RuntimeError('The orientation of the part ("{}") must not be symbolic to add it '
 86                            'to a CAD assembly'.format(yaw_pitch_roll_deg))
 87      for key, val in concrete_parameters.items():
 88         if key != 'name' and is_symbolic(val):
 89            raise RuntimeError('The geometric parameter "{}" of the part must not be symbolic to '
 90                               'add it to a CAD assembly'.format(key))
 91
 92      # Create and add a new CAD model to the assembly
 93      model = assembly.addObject(CadGeneral.PART_FEATURE_STRING, model_name)
 94      model.Shape = self.creation_callback(concrete_parameters, fully_displace)
 95      model.Shape.tessellate(TESSELATION_VALUE)
 96      assembly.recompute()
 97
 98      # Properly place and orient the CAD model in the assembly
 99      rotation_point = CadGeneral.compute_placement_point(model.Shape, placement_point)
100      placement = FreeCAD.Vector((1000.0 * placement_m[0]) - rotation_point.x,
101                                 (1000.0 * placement_m[1]) - rotation_point.y,
102                                 (1000.0 * placement_m[2]) - rotation_point.z)
103      rotation = FreeCAD.Rotation(*yaw_pitch_roll_deg)
104      model.Placement = FreeCAD.Placement(placement, rotation, rotation_point)
105      model.Shape.tessellate(TESSELATION_VALUE)
106      assembly.recompute()

Adds a CAD model representation of the SymPart to the specified assembly.

Parameters
  • model_name (str): Desired name for the CAD model.
  • assembly (FreeCAD.Document): FreeCAD assembly document to which the CAD model should be added.
  • concrete_parameters (Dict[str, float]): Dictionary of free variables along with their desired concrete values.
  • placement_point (Tuple[float, float, float]): Local coordinate (in percent length) to be used for the center of rotation and placement of the CAD model.
  • placement_m (Tuple[float, float, float]): Global xyz-placement (in m) of the placement_point of the CAD object in the assembly.
  • yaw_pitch_roll_deg (Tuple[float, float, float]): Global yaw-, pitch-, and roll-orientation in degrees of the CAD object.
  • fully_displace (bool): Whether to create a fully solid CAD model for displacement purposes.
def get_physical_properties( self, concrete_parameters: Dict[str, float], placement_point: Tuple[float, float, float], yaw_pitch_roll_deg: Tuple[float, float, float], material_density_kg_m3: float, normalize_origin: bool) -> Dict[str, float]:
109   def get_physical_properties(self, concrete_parameters: Dict[str, float],
110                                     placement_point: Tuple[float, float, float],
111                                     yaw_pitch_roll_deg: Tuple[float, float, float],
112                                     material_density_kg_m3: float,
113                                     normalize_origin: bool) -> Dict[str, float]:
114      """Returns all physical properties of the CAD model.
115
116      Mass properties will be computed assuming a uniform material density as specified in
117      the `material_density_kg_m3` parameter.
118
119      All free parameters within the CAD model must be concretized by passing in an appropriate
120      `concrete_parameters` dictionary containing the names of the free parameters as its keys,
121      along with their corresponding concrete floating-point values.
122
123      Parameters
124      ----------
125      concrete_parameters : `Dict[str, float]`
126         Dictionary of free variables along with their desired concrete values.
127      placement_point : `Tuple[float, float, float]`
128         Local coordinate (in percent length) to be used for the center of placement
129         of the CAD model.
130      yaw_pitch_roll_deg : `Tuple[float, float, float]`
131         Global yaw-, pitch-, and roll-orientation in degrees of the CAD object.
132      material_density_kg_m3 : `float`
133         Uniform material density to be used in mass property calculations (in `kg/m^3`).
134      normalize_origin : `bool`
135         Return physical properties with respect to the front, left, bottom corner of the
136         underlying CAD model.
137
138      Returns
139      -------
140      `Dict[str, float]`
141         A dictionary containing all physical `SymPart` properties as computed using the
142         underlying CAD model.
143      """
144
145      # Verify that all parameters are concrete
146      if is_symbolic(yaw_pitch_roll_deg[0]) or is_symbolic(yaw_pitch_roll_deg[1]) or \
147                                               is_symbolic(yaw_pitch_roll_deg[2]):
148         raise RuntimeError('The orientation of the part ("{}") must not be symbolic to calculate '
149                            'its physical properties from CAD'.format(yaw_pitch_roll_deg))
150      for key, val in concrete_parameters.items():
151         if key != 'name' and is_symbolic(val):
152            raise RuntimeError('The geometric parameter "{}" of the part must not be symbolic to '
153                               'calculate its physical properties from CAD'.format(key))
154      placement_point = [0.0 if is_symbolic(p) else float(p) for p in placement_point]
155
156      # Create the scripted CAD model
157      doc = FreeCAD.newDocument()
158      model = doc.addObject(CadGeneral.PART_FEATURE_STRING, 'Model')
159      model.Shape = self.creation_callback(concrete_parameters, False)
160      model.Shape.tessellate(TESSELATION_VALUE)
161      rotation_point = CadGeneral.compute_placement_point(model.Shape, placement_point)
162      placement = FreeCAD.Vector(-rotation_point.x, -rotation_point.y, -rotation_point.z)
163      rotation = FreeCAD.Rotation(*yaw_pitch_roll_deg)
164      model.Placement = FreeCAD.Placement(placement, rotation, rotation_point)
165      model.Shape.tessellate(TESSELATION_VALUE)
166
167      # Create a separate displacement model
168      displaced_model = doc.addObject(CadGeneral.PART_FEATURE_STRING, 'DisplacedModel')
169      displaced_model.Shape = self.creation_callback(concrete_parameters, True)
170      displaced_model.Shape.tessellate(TESSELATION_VALUE)
171      displaced_model.Placement = FreeCAD.Placement(placement, rotation, rotation_point)
172      displaced_model.Shape.tessellate(TESSELATION_VALUE)
173
174      # Retrieve all physical model properties
175      doc.recompute()
176      properties = CadGeneral.fetch_model_physical_properties(model.Shape,
177                                                              displaced_model.Shape,
178                                                              material_density_kg_m3,
179                                                              normalize_origin)
180      FreeCAD.closeDocument(doc.Name)
181      return properties

Returns all physical properties of the CAD model.

Mass properties will be computed assuming a uniform material density as specified in the material_density_kg_m3 parameter.

All free parameters within the CAD model must be concretized by passing in an appropriate concrete_parameters dictionary containing the names of the free parameters as its keys, along with their corresponding concrete floating-point values.

Parameters
  • concrete_parameters (Dict[str, float]): Dictionary of free variables along with their desired concrete values.
  • placement_point (Tuple[float, float, float]): Local coordinate (in percent length) to be used for the center of placement of the CAD model.
  • yaw_pitch_roll_deg (Tuple[float, float, float]): Global yaw-, pitch-, and roll-orientation in degrees of the CAD object.
  • material_density_kg_m3 (float): Uniform material density to be used in mass property calculations (in kg/m^3).
  • normalize_origin (bool): Return physical properties with respect to the front, left, bottom corner of the underlying CAD model.
Returns
  • Dict[str, float]: A dictionary containing all physical SymPart properties as computed using the underlying CAD model.
def export_model( self, file_save_path: str, model_type: Literal['freecad', 'step', 'stl'], concrete_parameters: Dict[str, float], placement_point: Tuple[float, float, float], yaw_pitch_roll_deg: Tuple[float, float, float]) -> None:
184   def export_model(self, file_save_path: str,
185                          model_type: Literal['freecad', 'step', 'stl'],
186                          concrete_parameters: Dict[str, float],
187                          placement_point: Tuple[float, float, float],
188                          yaw_pitch_roll_deg: Tuple[float, float, float]) -> None:
189      """Creates a CAD model of the `SymPart` in the specified format.
190
191      Parameters
192      ----------
193      file_save_path : `str`
194         Output file path at which to store the generated CAD model.
195      model_type : {'freecad', 'step', 'stl'}
196         Format of the CAD model to export.
197      concrete_parameters : `Dict[str, float]`
198         Dictionary of free variables along with their desired concrete values.
199      placement_point : `Tuple[float, float, float]`
200         Local coordinate (in percent length) to be used for the center of placement
201         of the CAD model.
202      yaw_pitch_roll_deg : `Tuple[float, float, float]`
203         Global yaw-, pitch-, and roll-orientation in degrees of the CAD object.
204      """
205
206      # Verify that all parameters have concrete representations
207      if is_symbolic(yaw_pitch_roll_deg[0]) or is_symbolic(yaw_pitch_roll_deg[1]) or \
208                                               is_symbolic(yaw_pitch_roll_deg[2]):
209         raise RuntimeError('The orientation of the part ("{}") must not be symbolic to export '
210                            'it as a CAD model'.format(yaw_pitch_roll_deg))
211      for key, val in concrete_parameters.items():
212         if key != 'name' and is_symbolic(val):
213            raise RuntimeError('The geometric parameter "{}" of the part must not be symbolic to '
214                               'export it as a CAD model'.format(key))
215      placement_point = [0.0 if is_symbolic(p) else float(p) for p in placement_point]
216
217      # Create any necessary path directories
218      file_path = Path(file_save_path).absolute().resolve()
219      if not file_path.parent.exists():
220         file_path.parent.mkdir()
221
222      # Create and tessellate the scripted CAD model
223      doc = FreeCAD.newDocument()
224      model = doc.addObject(CadGeneral.PART_FEATURE_STRING, 'Model')
225      model.Shape = self.creation_callback(concrete_parameters, False)
226      model.Shape.tessellate(TESSELATION_VALUE)
227      rotation_point = CadGeneral.compute_placement_point(model.Shape, placement_point)
228      placement = FreeCAD.Vector(-rotation_point.x, -rotation_point.y, -rotation_point.z)
229      rotation = FreeCAD.Rotation(*yaw_pitch_roll_deg)
230      model.Placement = FreeCAD.Placement(placement, rotation, rotation_point)
231      model.Shape.tessellate(TESSELATION_VALUE)
232      doc.recompute()
233
234      # Create the requested CAD format of the model
235      CadGeneral.save_model(file_path, model_type, model)
236      FreeCAD.closeDocument(doc.Name)

Creates a CAD model of the SymPart in the specified format.

Parameters
  • file_save_path (str): Output file path at which to store the generated CAD model.
  • model_type ({'freecad', 'step', 'stl'}): Format of the CAD model to export.
  • concrete_parameters (Dict[str, float]): Dictionary of free variables along with their desired concrete values.
  • placement_point (Tuple[float, float, float]): Local coordinate (in percent length) to be used for the center of placement of the CAD model.
  • yaw_pitch_roll_deg (Tuple[float, float, float]): Global yaw-, pitch-, and roll-orientation in degrees of the CAD object.