pyroll-core 2.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. pyroll/core/__init__.py +38 -0
  2. pyroll/core/config.py +155 -0
  3. pyroll/core/disk_elements/__init__.py +2 -0
  4. pyroll/core/disk_elements/disk_element_unit.py +73 -0
  5. pyroll/core/disk_elements/hookimpls.py +26 -0
  6. pyroll/core/grooves/__init__.py +10 -0
  7. pyroll/core/grooves/base.py +59 -0
  8. pyroll/core/grooves/boxes/__init__.py +3 -0
  9. pyroll/core/grooves/boxes/box.py +57 -0
  10. pyroll/core/grooves/boxes/constricted_box.py +63 -0
  11. pyroll/core/grooves/boxes/upset_box.py +18 -0
  12. pyroll/core/grooves/diamonds/__init__.py +3 -0
  13. pyroll/core/grooves/diamonds/diamond.py +60 -0
  14. pyroll/core/grooves/diamonds/gothic.py +41 -0
  15. pyroll/core/grooves/diamonds/square.py +48 -0
  16. pyroll/core/grooves/flat.py +31 -0
  17. pyroll/core/grooves/generic_elongation.py +258 -0
  18. pyroll/core/grooves/generic_elongation_solvers.py +422 -0
  19. pyroll/core/grooves/hexagonal.py +57 -0
  20. pyroll/core/grooves/ovals/__init__.py +8 -0
  21. pyroll/core/grooves/ovals/circular_oval.py +43 -0
  22. pyroll/core/grooves/ovals/constricted_circular_oval.py +53 -0
  23. pyroll/core/grooves/ovals/constricted_swedish_oval.py +61 -0
  24. pyroll/core/grooves/ovals/flat_oval.py +56 -0
  25. pyroll/core/grooves/ovals/oval_3radii.py +47 -0
  26. pyroll/core/grooves/ovals/oval_3radii_flanked.py +65 -0
  27. pyroll/core/grooves/ovals/swedish_oval.py +57 -0
  28. pyroll/core/grooves/ovals/upset_oval.py +47 -0
  29. pyroll/core/grooves/rounds/__init__.py +2 -0
  30. pyroll/core/grooves/rounds/false_round.py +65 -0
  31. pyroll/core/grooves/rounds/round.py +44 -0
  32. pyroll/core/grooves/spline.py +92 -0
  33. pyroll/core/hooks.py +420 -0
  34. pyroll/core/log.py +7 -0
  35. pyroll/core/profile/__init__.py +3 -0
  36. pyroll/core/profile/hookimpls.py +58 -0
  37. pyroll/core/profile/profile.py +380 -0
  38. pyroll/core/repr.py +49 -0
  39. pyroll/core/roll/__init__.py +3 -0
  40. pyroll/core/roll/hookimpls.py +65 -0
  41. pyroll/core/roll/roll.py +145 -0
  42. pyroll/core/roll_pass/__init__.py +8 -0
  43. pyroll/core/roll_pass/deformation_unit.py +61 -0
  44. pyroll/core/roll_pass/hookimpls/__init__.py +5 -0
  45. pyroll/core/roll_pass/hookimpls/deformation_unit.py +78 -0
  46. pyroll/core/roll_pass/hookimpls/disk_element.py +16 -0
  47. pyroll/core/roll_pass/hookimpls/profile.py +94 -0
  48. pyroll/core/roll_pass/hookimpls/roll.py +43 -0
  49. pyroll/core/roll_pass/hookimpls/roll_pass.py +107 -0
  50. pyroll/core/roll_pass/roll_pass.py +168 -0
  51. pyroll/core/roll_pass/three_roll_pass.py +84 -0
  52. pyroll/core/rotator/__init__.py +3 -0
  53. pyroll/core/rotator/hookimpls.py +71 -0
  54. pyroll/core/rotator/rotator.py +65 -0
  55. pyroll/core/sequence/__init__.py +3 -0
  56. pyroll/core/sequence/hookimpls.py +18 -0
  57. pyroll/core/sequence/sequence.py +68 -0
  58. pyroll/core/shapes.py +104 -0
  59. pyroll/core/transport/__init__.py +3 -0
  60. pyroll/core/transport/hookimpls.py +17 -0
  61. pyroll/core/transport/transport.py +57 -0
  62. pyroll/core/unit/__init__.py +3 -0
  63. pyroll/core/unit/hookimpls.py +67 -0
  64. pyroll/core/unit/unit.py +271 -0
  65. pyroll_core-2.0.0.dist-info/METADATA +109 -0
  66. pyroll_core-2.0.0.dist-info/RECORD +68 -0
  67. pyroll_core-2.0.0.dist-info/WHEEL +4 -0
  68. pyroll_core-2.0.0.dist-info/licenses/LICENSE +11 -0
@@ -0,0 +1,31 @@
1
+ import numpy as np
2
+ from numpy import deg2rad
3
+
4
+ from .generic_elongation import GenericElongationGroove
5
+
6
+
7
+ class FlatGroove(GenericElongationGroove):
8
+ """Represents a flat groove aka no groove."""
9
+
10
+ def __init__(
11
+ self,
12
+ usable_width: float,
13
+ r1: float = 0,
14
+ pad_angle: float = 0,
15
+ **kwargs
16
+ ):
17
+ """
18
+ :param usable_width: usable width of the roll face
19
+ :param kwargs: more keyword arguments passed to the GenericElongationGroove constructor
20
+ """
21
+ pad_angle = deg2rad(pad_angle)
22
+
23
+ super().__init__(
24
+ usable_width=usable_width, r1=r1, r2=0, depth=0, flank_angle=0,
25
+ pad_angle=pad_angle,
26
+ **kwargs
27
+ )
28
+
29
+ @property
30
+ def classifiers(self):
31
+ return {"flat",} | super().classifiers
@@ -0,0 +1,258 @@
1
+ from typing import Union, Tuple, Sequence, Optional
2
+
3
+ import numpy as np
4
+ from shapely.geometry import LineString, Polygon
5
+
6
+ from pyroll.core.grooves import GrooveBase
7
+ from pyroll.core.repr import ReprMixin
8
+ from ..config import Config
9
+
10
+
11
+ class GenericElongationGroove(GrooveBase, ReprMixin):
12
+ """Represents a groove defined by the generic elongation groove geometry."""
13
+
14
+ def __init__(
15
+ self,
16
+
17
+ r1: float,
18
+ r2: float,
19
+
20
+ flank_angle: Optional[float] = None,
21
+ usable_width: Optional[float] = None,
22
+ ground_width: Optional[float] = None,
23
+ depth: Optional[float] = None,
24
+
25
+ r3: float = 0,
26
+ alpha3: float = 0,
27
+
28
+ r4: float = 0,
29
+ alpha4: float = 0,
30
+
31
+ indent: float = 0,
32
+ even_ground_width: float = 0,
33
+
34
+ pad: float = 0,
35
+ rel_pad: float = Config.GROOVE_PADDING,
36
+ pad_angle: float = 0,
37
+
38
+ classifiers: Sequence[str] = ()
39
+ ):
40
+ """
41
+ Give any three of ``usable_width``, ``ground_width``, ``flank_angle`` and ``depth``.
42
+ All angles are measured in radians.
43
+ All measures must be non-negative.
44
+
45
+ :param r1: radius 1 (face/flank)
46
+ :param r2: radius 2 (flank/radius 3)
47
+
48
+ :param usable_width: width of flank/face intersections
49
+ :param ground_width: width of flank/ground-line intersections
50
+ :param flank_angle: angle of the flanks to the z-axis
51
+ :param depth: maximum depth
52
+
53
+ :param r3: radius 3 (radius 2/radius 4)
54
+ :param r4: radius 4 (radius 3/ground)
55
+ :param alpha3: angle corresponding to ``r3``
56
+ :param alpha4: angle corresponding to ``r4``
57
+
58
+
59
+ :param indent: indent of the ground
60
+
61
+ :param pad: absolute padding at roll face
62
+ :param rel_pad: padding at roll face relative to ``usable_width``
63
+ :param pad_angle: angle of the face padding from horizontal line
64
+ (commonly 0 for two-roll, π/6 for three-roll and π/4 for four-roll)
65
+
66
+ :param classifiers: sequence of additional type classifiers
67
+ """
68
+
69
+ try:
70
+ if usable_width is None:
71
+ usable_width = ground_width + 2 * depth / np.tan(flank_angle)
72
+ elif ground_width is None:
73
+ ground_width = usable_width - 2 * depth / np.tan(flank_angle)
74
+ elif flank_angle is None:
75
+ flank_angle = np.arctan(depth / (usable_width - ground_width) * 2)
76
+ elif depth is None:
77
+ depth = (usable_width - ground_width) / 2 * np.tan(flank_angle)
78
+ else:
79
+ raise TypeError("Too many arguments given.")
80
+
81
+ except TypeError:
82
+ raise TypeError("Exactly three of usable_width, ground_width, flank_angle and depth must be given.")
83
+
84
+ self.r1 = r1
85
+ self.r2 = r2
86
+ self.r3 = r3
87
+ self.r4 = r4
88
+
89
+ self.alpha1 = alpha1 = flank_angle + pad_angle
90
+ self.alpha3 = alpha3
91
+ self.alpha4 = alpha4
92
+ self.alpha2 = alpha2 = flank_angle + self.alpha4 - self.alpha3
93
+
94
+ self.indent = indent
95
+ self.even_ground_width = even_ground_width
96
+ self._usable_width = usable_width
97
+ self.ground_width = ground_width
98
+ self.flank_angle = flank_angle
99
+ self._depth = depth
100
+ self._classifiers = set(classifiers)
101
+ pad = pad if pad else usable_width * rel_pad
102
+ self.pad_angle = pad_angle
103
+
104
+ self.z2 = usable_width / 2
105
+ self.y2 = 0
106
+
107
+ l12 = r1 * np.tan(alpha1 / 2)
108
+ self.z1 = self.z2 + l12 * np.cos(pad_angle)
109
+ self.y1 = l12 * np.sin(pad_angle)
110
+
111
+ self.z0 = self.z1 + pad * np.cos(pad_angle)
112
+ self.y0 = self.y1 + pad * np.sin(pad_angle)
113
+
114
+ self.z12 = self.z1 - r1 * np.sin(pad_angle)
115
+ self.y12 = self.y1 + r1 * np.cos(pad_angle)
116
+
117
+ self.z3 = self.z12 - r1 * np.sin(flank_angle)
118
+ self.y3 = self.y12 - r1 * np.cos(flank_angle)
119
+
120
+ self.z9 = 0
121
+ self.y9 = depth - indent
122
+
123
+ self.z7 = self.even_ground_width / 2
124
+ self.y7 = self.y9
125
+
126
+ self.z8 = self.z7
127
+ self.y8 = self.y9 + r4
128
+
129
+ self.z6 = self.z8 + r4 * np.sin(alpha4)
130
+ self.y6 = self.y8 - r4 * np.cos(alpha4)
131
+
132
+ self.beta = beta = alpha4 - alpha3 / 2
133
+
134
+ self.z10 = self.z6 + r3 * np.sin(alpha3 / 2 + beta)
135
+ self.y10 = self.y6 - r3 * np.cos(alpha3 / 2 + beta)
136
+
137
+ self.z5 = self.z10 + r3 * np.sin(alpha3 / 2 - beta)
138
+ self.y5 = self.y10 + r3 * np.cos(alpha3 / 2 - beta)
139
+
140
+ self.z11 = self.z10 + (r3 - r2) * np.sin(alpha3 / 2 - beta)
141
+ self.y11 = self.y10 + (r3 - r2) * np.cos(alpha3 / 2 - beta)
142
+
143
+ self.gamma = gamma = np.pi / 2 - alpha2 - alpha3 + alpha4
144
+
145
+ self.z4 = self.z11 + r2 * np.cos(gamma)
146
+ self.y4 = self.y11 + r2 * np.sin(gamma)
147
+
148
+ right_side = np.array(list(self._enumerate_contour_points()))
149
+ left_side = right_side[:-1].copy()
150
+ left_side[:, 0] *= -1
151
+
152
+ self._contour_points = np.concatenate([left_side, right_side[::-1]])
153
+ self._contour_line = LineString(self._contour_points)
154
+ self._cross_section = Polygon(self._contour_line)
155
+
156
+ self.test_plausibility()
157
+
158
+ def _r1_contour_line(self, z):
159
+ return self.y12 - np.sqrt(self.r1 ** 2 - (z - self.z12) ** 2)
160
+
161
+ def _r2_contour_line(self, z):
162
+ return self.y11 + np.sqrt(self.r2 ** 2 - (z - self.z11) ** 2)
163
+
164
+ def _r3_contour_line(self, z):
165
+ return self.y10 + np.sqrt(self.r3 ** 2 - (z - self.z10) ** 2)
166
+
167
+ def _r4_contour_line(self, z):
168
+ return self.y8 - np.sqrt(self.r4 ** 2 - (z - self.z8) ** 2)
169
+
170
+ def _flank_contour_line(self, z):
171
+ return self.y3 - np.tan(self.flank_angle) * (z - self.z3)
172
+
173
+ def _ground_contour_line(self, z):
174
+ return np.ones_like(z) * (self.depth - self.indent)
175
+
176
+ @staticmethod
177
+ def _face_contour_line(z):
178
+ return np.zeros_like(z)
179
+
180
+ def _enumerate_contour_points(self):
181
+ yield self.z0, self.y0
182
+
183
+ if not np.isclose(self.z1, self.z3):
184
+ for z in np.linspace(self.z1, self.z3, 20, endpoint=False):
185
+ yield z, self._r1_contour_line(z)
186
+
187
+ if not np.isclose(self.z3, self.z4):
188
+ yield self.z3, self.y3
189
+
190
+ if not np.isclose(self.z4, self.z5):
191
+ for z in np.linspace(self.z4, self.z5, 20, endpoint=False):
192
+ yield z, self._r2_contour_line(z)
193
+
194
+ if not np.isclose(self.z5, self.z6):
195
+ for z in np.linspace(self.z5, self.z6, 20, endpoint=False):
196
+ yield z, self._r3_contour_line(z)
197
+
198
+ if not np.isclose(self.z6, self.z7):
199
+ for z in np.linspace(self.z6, self.z7, 20, endpoint=False):
200
+ yield z, self._r4_contour_line(z)
201
+
202
+ yield self.z9, self.y9
203
+
204
+ @property
205
+ def contour_points(self):
206
+ return self._contour_points
207
+
208
+ @property
209
+ def contour_line(self) -> LineString:
210
+ return self._contour_line
211
+
212
+ @property
213
+ def cross_section(self) -> Polygon:
214
+ return self._cross_section
215
+
216
+ def local_depth(self, z) -> Union[float, np.ndarray]:
217
+ z = np.abs(z)
218
+
219
+ return np.piecewise(
220
+ z,
221
+ [z < self.z7, (self.z7 <= z) & (z < self.z6), (self.z6 <= z) & (z < self.z5),
222
+ (self.z5 <= z) & (z < self.z4), (self.z4 <= z) & (z < self.z3), (self.z3 <= z) & (z < self.z1)],
223
+ [self._ground_contour_line, self._r4_contour_line, self._r3_contour_line, self._r2_contour_line,
224
+ self._flank_contour_line, self._r1_contour_line, self._face_contour_line]
225
+ )
226
+
227
+ @property
228
+ def classifiers(self):
229
+ return {"generic_elongation"} | self._classifiers
230
+
231
+ @property
232
+ def usable_width(self) -> float:
233
+ return self._usable_width
234
+
235
+ @property
236
+ def width(self) -> float:
237
+ return 2 * self.z1
238
+
239
+ @property
240
+ def depth(self) -> float:
241
+ return self._depth
242
+
243
+ def test_plausibility(self):
244
+ if (self.flank_angle + self.alpha4 - self.alpha2 - self.alpha3) > 0.01:
245
+ raise ValueError("given angles should fulfill α1 + α4 = α2 + α3 to be geometrically plausible")
246
+
247
+ if self.y4 - self._flank_contour_line(self.z4) > 0.001 * self.depth:
248
+ raise ValueError("under given conditions a step appears in z4")
249
+
250
+ @property
251
+ def __attrs__(self):
252
+ return {
253
+ n: v for n in ["r1", "r2", "r3", "r4", "alpha1", "alpha2", "alpha3", "alpha4", "depth", "indent",
254
+ "even_ground_width", "usable_width", "classifiers", "flank_angle", "pad_angle",
255
+ "ground_width",
256
+ "contour_line"]
257
+ if (v := getattr(self, n))
258
+ }
@@ -0,0 +1,422 @@
1
+ from typing import Optional
2
+
3
+ import numpy as np
4
+ from scipy.optimize import root_scalar, root, fixed_point
5
+
6
+
7
+ def solve_r124(
8
+ r1: float,
9
+
10
+ r2: Optional[float],
11
+ depth: Optional[float],
12
+ width: Optional[float],
13
+
14
+ pad_angle: float,
15
+
16
+ flank_angle: Optional[float] = None,
17
+ flank_width: Optional[float] = None,
18
+ flank_height: Optional[float] = None,
19
+ flank_length: Optional[float] = None,
20
+
21
+ r4: float = 0,
22
+ indent: float = 0,
23
+ ):
24
+ def l23(_alpha):
25
+ return r1 * np.tan((_alpha + pad_angle) / 2)
26
+
27
+ if flank_angle is None:
28
+ if flank_length is not None:
29
+ def fw(_alpha):
30
+ return flank_length * np.cos(_alpha)
31
+
32
+ def fh(_alpha):
33
+ return flank_length * np.sin(_alpha)
34
+
35
+ elif flank_width is not None:
36
+ def fw(_alpha):
37
+ return flank_width
38
+
39
+ def fh(_alpha):
40
+ return flank_width * np.tan(_alpha)
41
+
42
+ elif flank_height is not None:
43
+ def fw(_alpha):
44
+ return flank_height / np.tan(_alpha)
45
+
46
+ def fh(_alpha):
47
+ return flank_height
48
+
49
+ else:
50
+ def fw(_alpha):
51
+ return 0
52
+
53
+ def fh(_alpha):
54
+ return 0
55
+
56
+ if width is None:
57
+ def f(_alpha):
58
+ return depth - r2 * (1 - np.cos(_alpha)) - l23(_alpha) * np.sin(_alpha) - fh(_alpha)
59
+
60
+ flank_angle = root_scalar(f, bracket=(0, np.pi / 2)).root
61
+
62
+ elif depth is None:
63
+ alpha4 = np.arccos(1 - indent / (r2 + r4))
64
+
65
+ def f(_alpha):
66
+ return (
67
+ width / 2 - r2 * np.sin(_alpha) - l23(_alpha) * np.cos(_alpha)
68
+ - fw(_alpha) - (r2 + r4) * np.sin(alpha4)
69
+ )
70
+
71
+ raster = np.linspace(0, np.pi / 2, 100)
72
+ values = f(raster)
73
+ lower = raster[values > 0][0]
74
+ upper = raster[values < 0][-1]
75
+ flank_angle = root_scalar(f, bracket=(lower, upper)).root
76
+
77
+ elif r2 is None:
78
+ def f(_alpha):
79
+ _r2 = (depth - l23(_alpha) * np.sin(_alpha) - fh(_alpha)) / (1 - np.cos(_alpha))
80
+ _alpha4 = np.arccos(1 - indent / (_r2 + r4))
81
+
82
+ return (
83
+ width / 2 - _r2 * np.sin(_alpha) - l23(_alpha) * np.cos(_alpha)
84
+ - fw(_alpha) - (_r2 + r4) * np.sin(_alpha4)
85
+ )
86
+
87
+ flank_angle = root_scalar(f, bracket=(0, np.pi / 2)).root
88
+
89
+ else:
90
+ raise TypeError("Give either usable_width or depth.")
91
+
92
+ if r2 is not None:
93
+ alpha4 = np.arccos(1 - indent / (r2 + r4))
94
+
95
+ if width is None:
96
+ width = 2 * (
97
+ r2 * np.sin(flank_angle)
98
+ + (depth - r2 * (1 - np.cos(flank_angle))) / np.tan(flank_angle)
99
+ + (r2 + r4) * np.sin(alpha4)
100
+ )
101
+
102
+ elif depth is None:
103
+ depth = (
104
+ r2 * (1 - np.cos(flank_angle))
105
+ + (width / 2 - r2 * np.sin(flank_angle) - (r2 + r4) * np.sin(alpha4)) * np.tan(flank_angle)
106
+ )
107
+
108
+ else:
109
+ def f(_r2):
110
+ _alpha4 = np.arccos(1 - indent / (_r2 + r4))
111
+ return (
112
+ (depth - width / 2 * np.tan(flank_angle) + r4 * np.sin(_alpha4))
113
+ / (1 - np.cos(flank_angle) - np.sin(flank_angle) * np.tan(flank_angle) - np.sin(_alpha4))
114
+ )
115
+ r2 = fixed_point(f, width if width > depth else depth)
116
+ alpha4 = np.arccos(1 - indent / (r2 + r4))
117
+
118
+ return dict(
119
+ width=width,
120
+ depth=depth,
121
+ r2=r2,
122
+ alpha=flank_angle,
123
+ )
124
+
125
+
126
+ def solve_r123(
127
+ r1: float,
128
+ r2: float,
129
+ r3: float,
130
+ depth: float,
131
+ width: float,
132
+
133
+ pad_angle: float,
134
+
135
+ flank_angle: Optional[float] = None,
136
+ flank_width: Optional[float] = None,
137
+ flank_height: Optional[float] = None,
138
+ flank_length: Optional[float] = None,
139
+ ):
140
+ def l23(_alpha):
141
+ return r1 * np.tan((_alpha + pad_angle) / 2)
142
+
143
+ r32 = r3 - r2
144
+
145
+ if flank_angle is None:
146
+ if flank_length is not None:
147
+ def fw(_alpha):
148
+ return flank_length * np.cos(_alpha)
149
+
150
+ def fh(_alpha):
151
+ return flank_length * np.sin(_alpha)
152
+
153
+ elif flank_width is not None:
154
+ def fw(_alpha):
155
+ return flank_width
156
+
157
+ def fh(_alpha):
158
+ return flank_width * np.tan(_alpha)
159
+
160
+ elif flank_height is not None:
161
+ def fw(_alpha):
162
+ return flank_height / np.tan(_alpha)
163
+
164
+ def fh(_alpha):
165
+ return flank_height
166
+
167
+ else:
168
+ def fw(_alpha):
169
+ return 0
170
+
171
+ def fh(_alpha):
172
+ return 0
173
+
174
+ def f(_alpha):
175
+ _alpha2 = _alpha[0]
176
+ _alpha3 = _alpha[1]
177
+ _flank_angle = _alpha2 + _alpha3
178
+ _gamma = np.pi / 2 - _flank_angle
179
+
180
+ return np.array(
181
+ [
182
+ (
183
+ depth - r3 + r32 * np.cos(_alpha3) + r2 * np.sin(_gamma)
184
+ - fh(_flank_angle) - l23(_flank_angle) * np.sin(_flank_angle)
185
+ ),
186
+ (
187
+ width / 2 - r32 * np.sin(_alpha3) - r2 * np.cos(_gamma)
188
+ - fw(_flank_angle) - l23(_flank_angle) * np.cos(_flank_angle)
189
+ ),
190
+ ]
191
+ )
192
+
193
+ sol = root(
194
+ f, np.array([np.pi / 4, np.pi / 4]),
195
+ )
196
+
197
+ if not sol.success:
198
+ raise RuntimeError("Could not determine geometric values with given input.")
199
+
200
+ alpha2 = sol.x[0]
201
+ alpha3 = sol.x[1]
202
+ flank_angle = alpha2 + alpha3
203
+
204
+ else:
205
+ def f(_alpha):
206
+ _alpha2 = _alpha
207
+ _alpha3 = flank_angle - _alpha
208
+ _gamma = np.pi / 2 - flank_angle
209
+
210
+ _fw = width / 2 - r32 * np.sin(_alpha3) - r2 * np.cos(_gamma) - l23(flank_angle) * np.cos(flank_angle)
211
+
212
+ return (
213
+ depth - r3 + r32 * np.cos(_alpha3) + r2 * np.sin(_gamma)
214
+ - _fw * np.tan(flank_angle) - l23(flank_angle) * np.sin(flank_angle)
215
+ )
216
+
217
+ sol = root_scalar(f, bracket=(0, np.pi / 2)).root
218
+ alpha2 = sol
219
+ alpha3 = flank_angle - sol
220
+
221
+ return dict(
222
+ flank_angle=flank_angle,
223
+ alpha2=alpha2,
224
+ alpha3=alpha3,
225
+ )
226
+
227
+
228
+ def solve_r1234(
229
+ r1: float,
230
+ r2: float,
231
+ r3: float,
232
+ r4: float,
233
+ depth: float,
234
+ width: float,
235
+ indent: float,
236
+
237
+ pad_angle: float,
238
+
239
+ flank_angle: Optional[float] = None,
240
+ flank_width: Optional[float] = None,
241
+ flank_height: Optional[float] = None,
242
+ flank_length: Optional[float] = None,
243
+ ):
244
+ def l23(_alpha):
245
+ return r1 * np.tan((_alpha + pad_angle) / 2)
246
+
247
+ if flank_angle is None:
248
+ if flank_length is not None:
249
+ def fw(_alpha):
250
+ return flank_length * np.cos(_alpha)
251
+
252
+ def fh(_alpha):
253
+ return flank_length * np.sin(_alpha)
254
+
255
+ elif flank_width is not None:
256
+ def fw(_alpha):
257
+ return flank_width
258
+
259
+ def fh(_alpha):
260
+ return flank_width * np.tan(_alpha)
261
+
262
+ elif flank_height is not None:
263
+ def fw(_alpha):
264
+ return flank_height / np.tan(_alpha)
265
+
266
+ def fh(_alpha):
267
+ return flank_height
268
+
269
+ else:
270
+ def fw(_alpha):
271
+ return 0
272
+
273
+ def fh(_alpha):
274
+ return 0
275
+
276
+ def f(_alpha):
277
+ _alpha2 = _alpha[0]
278
+ _alpha3 = _alpha[1]
279
+ _alpha4 = _alpha[2]
280
+ _flank_angle = _alpha2 + _alpha3 - _alpha4
281
+
282
+ if _alpha3 > _alpha4:
283
+ _gamma = np.pi / 2 - _flank_angle
284
+ return np.array(
285
+ [
286
+ (
287
+ depth - r3 + (r3 - r2) * np.cos(_alpha3 - _alpha4) + r2 * np.sin(_gamma)
288
+ - fh(_flank_angle) - l23(_flank_angle) * np.sin(_flank_angle)
289
+ ),
290
+ (
291
+ width / 2 - (r3 - r2) * np.sin(_alpha3 - _alpha4) - r2 * np.cos(_gamma)
292
+ - fw(_flank_angle) - l23(_flank_angle) * np.cos(_flank_angle)
293
+ - (r3 + r4) * np.sin(_alpha4)
294
+ ),
295
+ (
296
+ indent - (r3 + r4) * (1 - np.cos(_alpha4))
297
+ )
298
+ ]
299
+ )
300
+ else:
301
+ _gamma = np.pi / 2 - _alpha4
302
+ return np.array(
303
+ [
304
+ (
305
+ depth - r2 * (1 - np.cos(_flank_angle))
306
+ - fh(_flank_angle) - l23(_flank_angle) * np.sin(_flank_angle)
307
+ ),
308
+ (
309
+ width / 2 - r2 * np.sin(_flank_angle)
310
+ - fw(_flank_angle) - l23(_flank_angle) * np.cos(_flank_angle)
311
+ - r4 * np.sin(_alpha4) - r3 * np.cos(_gamma)
312
+ - (r2 - r3) * np.sin(_alpha2 - _flank_angle)
313
+ ),
314
+ (
315
+ indent - r4 * (1 - np.cos(_alpha4)) + r3 * np.sin(_gamma)
316
+ + (r2 - r3) * np.cos(_alpha4 - _alpha3) - r2
317
+ )
318
+ ]
319
+ )
320
+
321
+ sol = root(
322
+ f, np.array([np.pi / 4, np.pi / 4, np.pi / 4]),
323
+ )
324
+
325
+ if not sol.success:
326
+ raise RuntimeError("Could not determine geometric values with given input.")
327
+
328
+ alpha2 = sol.x[0]
329
+ alpha3 = sol.x[1]
330
+ alpha4 = sol.x[2]
331
+ flank_angle = alpha2 + alpha3 - alpha4
332
+
333
+ else:
334
+ def f(_alpha):
335
+ _alpha2 = _alpha[0]
336
+ _alpha3 = _alpha[1]
337
+ _alpha4 = _alpha2 + _alpha3 - flank_angle
338
+ _gamma = np.pi / 2 - flank_angle
339
+
340
+ _fw = width / 2 - (r3 - r2) * np.sin(_alpha3) - r2 * np.cos(_gamma) - l23(flank_angle) * np.cos(flank_angle)
341
+
342
+ return np.array(
343
+ [
344
+ (
345
+ depth - r3 + (r3 - r2) * np.cos(_alpha3 - _alpha4) + r2 * np.sin(_gamma)
346
+ - _fw * np.tan(flank_angle) - l23(flank_angle) * np.sin(flank_angle)
347
+ ),
348
+ (
349
+ indent - (r3 + r4) * (1 - np.cos(_alpha4))
350
+ )
351
+ ]
352
+ )
353
+
354
+ sol = root(
355
+ f, np.array([np.pi / 4, np.pi / 4]),
356
+ )
357
+
358
+ if not sol.success:
359
+ raise RuntimeError("Could not determine geometric values with given input.")
360
+
361
+ alpha2 = sol.x[0]
362
+ alpha3 = sol.x[1]
363
+ alpha4 = alpha2 + alpha3 - flank_angle
364
+
365
+ return dict(
366
+ flank_angle=flank_angle,
367
+ alpha2=alpha2,
368
+ alpha3=alpha3,
369
+ alpha4=alpha4,
370
+ )
371
+
372
+
373
+ def solve_box_like(
374
+ r2: float,
375
+ r4: float,
376
+ depth: float,
377
+ indent: float,
378
+ ground_width: Optional[float],
379
+ even_ground_width: Optional[float],
380
+ usable_width: Optional[float],
381
+ flank_angle: Optional[float],
382
+ ):
383
+ alpha4 = np.arccos(1 - indent / (r2 + r4))
384
+
385
+ if flank_angle is None:
386
+ if usable_width is not None:
387
+ if ground_width is not None:
388
+ flank_angle = np.arctan(depth / (usable_width - ground_width) * 2)
389
+ elif even_ground_width is not None:
390
+ def f(_alpha):
391
+ _ground_width = even_ground_width + 2 * ((r4 + r2) * np.sin(alpha4) + r2 * np.tan(_alpha / 2))
392
+ return depth - (usable_width - _ground_width) / 2 * np.tan(_alpha)
393
+
394
+ flank_angle = root_scalar(f, bracket=(0, np.pi / 2)).root
395
+ ground_width = even_ground_width + 2 * ((r4 + r2) * np.sin(alpha4) + r2 * np.tan(flank_angle / 2))
396
+ else:
397
+ raise TypeError("either ground_width or even_ground_width must not be None")
398
+ else:
399
+ raise TypeError("usable_width must not be None if flank_angle is None")
400
+ elif ground_width is None and even_ground_width is None:
401
+ if usable_width is not None:
402
+ ground_width = usable_width - 2 * depth / np.tan(flank_angle)
403
+ else:
404
+ raise TypeError("usable_width must not be None if ground_width and even_ground_width are None")
405
+ elif usable_width is None:
406
+ if ground_width is None:
407
+ ground_width = even_ground_width + 2 * ((r4 + r2) * np.sin(alpha4) + r2 * np.tan(flank_angle / 2))
408
+
409
+ usable_width = ground_width + 2 * depth / np.tan(flank_angle)
410
+ else:
411
+ raise TypeError("either flank_angle, ground_width or usable_width must be None")
412
+
413
+ if even_ground_width is None:
414
+ even_ground_width = ground_width - 2 * ((r4 + r2) * np.sin(alpha4) + r2 * np.tan(flank_angle / 2))
415
+
416
+ return dict(
417
+ ground_width=ground_width,
418
+ usable_width=usable_width,
419
+ flank_angle=flank_angle,
420
+ even_ground_width=even_ground_width,
421
+ alpha4=alpha4
422
+ )