Skip to content

Commit 6e6439d

Browse files
committed
Add a gear example
1 parent 71cc985 commit 6e6439d

File tree

4 files changed

+1233
-0
lines changed

4 files changed

+1233
-0
lines changed

examples/complex/gear/gear.py

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
#!/usr/bin/env python
2+
import os
3+
import time
4+
5+
import numpy as np
6+
import scipy.optimize
7+
from involute_gear import InvoluteGear
8+
from tooth import ToothSketch
9+
10+
import classy_blocks as cb
11+
from classy_blocks.util import functions as f
12+
13+
time_start = time.time()
14+
15+
mesh = cb.Mesh()
16+
17+
# parameters
18+
RADIUS_EXPANSION = 1.1
19+
20+
# create an interpolated curve that represents a gear tooth
21+
fillet = 0.1 # Must be more than zero!
22+
gear = InvoluteGear(fillet=fillet, arc_step_size=0.1, max_steps=1000, teeth=15)
23+
tng_points = gear.generate_tooth_and_gap()
24+
25+
z_coords = np.zeros(len(tng_points[0]))
26+
tng_points = np.stack((tng_points[0], tng_points[1], z_coords)).T
27+
tng_points = np.flip(tng_points, axis=0)
28+
29+
# add start and end points exactly on the 2pi/teeth
30+
start_point = f.to_polar(tng_points[0], axis="z")
31+
start_point[1] = np.pi / gear.teeth
32+
start_point = f.to_cartesian(start_point)
33+
34+
end_point = f.to_polar(tng_points[-1], axis="z")
35+
end_point[1] = -np.pi / gear.teeth
36+
end_point = f.to_cartesian(end_point)
37+
38+
tng_points = np.concatenate(([start_point], tng_points, [end_point]))
39+
40+
tooth_curve = cb.LinearInterpolatedCurve(tng_points)
41+
gear_params = np.linspace(0, 1, num=8)
42+
43+
44+
# fix points 1 and 3:
45+
# 1 is on radius gear.root_radius + fillet/2
46+
def frad(t, radius):
47+
return f.norm(tooth_curve.get_point(t)) - radius
48+
49+
50+
gear_params[1] = scipy.optimize.brentq(lambda t: frad(t, gear.root_radius + fillet / 2), 0, 0.25)
51+
52+
# 3 is on radius gear.outer_radius - fillet/2
53+
gear_params[3] = scipy.optimize.brentq(lambda t: frad(t, gear.outer_radius - fillet / 2), 0.25, 0.5)
54+
55+
gear_params[6] = 1 - gear_params[1]
56+
gear_params[4] = 1 - gear_params[3]
57+
58+
gear_params[2] = (gear_params[1] + gear_params[3]) / 2
59+
gear_params[5] = (gear_params[4] + gear_params[6]) / 2
60+
61+
gear_points = np.array([tooth_curve.get_point(t) for t in gear_params])
62+
63+
outer_radius = f.norm(gear_points[3] * RADIUS_EXPANSION)
64+
p11_polar = f.to_polar(gear_points[-1], axis="z")
65+
p14_polar = f.to_polar(gear_points[0], axis="z")
66+
angles = np.linspace(p11_polar[1], p14_polar[1], num=4)
67+
tangential_points = np.array([f.to_cartesian([outer_radius, angle, 0]) for angle in angles])
68+
69+
radial_points_1 = np.linspace(gear_points[-1], tangential_points[0], axis=0, num=5)[1:-1]
70+
radial_points_2 = np.linspace(tangential_points[-1], gear_points[0], axis=0, num=5)[1:-1]
71+
72+
outer_points = np.concatenate((gear_points, radial_points_1, tangential_points, radial_points_2))
73+
inner_points = np.zeros((6, 3))
74+
75+
positions = np.concatenate((outer_points, inner_points))
76+
77+
# At this point, a smoother would reliably
78+
# produce almost the best blocking if this was a convex sketch.
79+
# Alas, this is a severely concave case so smoothing will produce
80+
# degenerate quads which even optimizers won't be able to fix.
81+
# It's best to manually position points, then optimize the sketch.
82+
83+
84+
def mirror(target, source):
85+
# once a position is obtained, the mirrored counterpart is also determined
86+
positions[target] = [positions[source][0], -positions[source][1], 0]
87+
88+
89+
# fix points 18 and 23 because optimization doesn't 'see' curved edges
90+
# and produces high non-orthogonality
91+
dir_0 = f.unit_vector(gear_points[0] - gear_points[1])
92+
dir_2 = f.unit_vector(gear_points[2] - gear_points[1])
93+
dir_18 = f.unit_vector(dir_0 + dir_2)
94+
95+
positions[18] = gear_points[1] + dir_18 * f.norm(gear_points[0] - gear_points[1]) / 2
96+
mirror(23, 18)
97+
98+
positions[17] = positions[0] + f.unit_vector(positions[17] - positions[0]) * f.norm(positions[18] - positions[1])
99+
mirror(8, 17)
100+
101+
102+
# other points are somewhere between...
103+
def midpoint(target, left, right):
104+
positions[target] = (positions[left] + positions[right]) / 2
105+
106+
107+
midpoint(19, 2, 16)
108+
midpoint(20, 3, 13)
109+
110+
# and their mirrored counterparts
111+
mirror(22, 19)
112+
mirror(21, 20)
113+
114+
115+
sketch = ToothSketch(positions, tooth_curve)
116+
117+
# Optimize the sketch:
118+
optimizer = cb.SketchOptimizer(sketch)
119+
120+
# point 2 is on gear curve
121+
optimizer.add_clamp(cb.CurveClamp(positions[2], tooth_curve))
122+
123+
# point 13 is movable radially
124+
optimizer.add_clamp(cb.RadialClamp(positions[13], [0, 0, 0], [0, 0, 1]))
125+
126+
# 15-17 move along a line
127+
for i in (15, 16, 17):
128+
optimizer.add_clamp(cb.LineClamp(positions[i], gear_points[0], tangential_points[-1]))
129+
130+
# freely movable points (on sketch plane)
131+
# TODO: easier clamp definition for sketch optimization
132+
for i in (19, 20):
133+
optimizer.add_clamp(cb.PlaneClamp(sketch.positions[i], sketch.positions[i], sketch.normal))
134+
135+
# Links!
136+
symmetry_pairs = [
137+
(2, 5),
138+
(19, 22),
139+
(20, 21),
140+
(17, 8),
141+
(16, 9),
142+
(15, 10),
143+
]
144+
145+
for pair in symmetry_pairs:
146+
optimizer.add_link(cb.SymmetryLink(positions[pair[0]], positions[pair[1]], f.vector(0, 1, 0), f.vector(0, 0, 0)))
147+
148+
optimizer.optimize()
149+
150+
stack = cb.TransformedStack(
151+
sketch,
152+
[cb.Translation([0, 0, 4]), cb.Rotation(sketch.normal, 10 * np.pi / 180, [0, 0, 0])],
153+
2,
154+
[cb.Translation([0, 0, 2]), cb.Rotation(sketch.normal, 5 * np.pi / 180, [0, 0, 0])],
155+
)
156+
157+
# TODO: this be mighty clumsy; unclumsify
158+
bulk_size = 0.1
159+
wall_size = 0.01
160+
161+
stack.shapes[0].chop(0, start_size=bulk_size)
162+
stack.shapes[0].chop(1, start_size=wall_size, end_size=bulk_size / 2)
163+
stack.shapes[0].operations[10].chop(1, start_size=bulk_size)
164+
stack.chop(count=8)
165+
166+
# patches
167+
for shape in stack.shapes:
168+
for i in range(7):
169+
shape.operations[i].set_patch("front", "gear")
170+
171+
for i in (9, 10, 11):
172+
shape.operations[i].set_patch("front", "outer")
173+
174+
for operation in stack.shapes[0].operations:
175+
operation.set_patch("bottom", "bottom")
176+
177+
for operation in stack.shapes[-1].operations:
178+
operation.set_patch("top", "top")
179+
180+
mesh.add(stack)
181+
182+
for i, angle in enumerate(np.linspace(0, 2 * np.pi, num=gear.teeth, endpoint=False)[1:]):
183+
print(f"Adding tooth {i+2}")
184+
mesh.add(stack.copy().rotate(angle, [0, 0, 1], [0, 0, 0]))
185+
186+
print("Writing mesh...")
187+
mesh.write(os.path.join("..", "..", "case", "system", "blockMeshDict"), debug_path="debug.vtk")
188+
189+
time_end = time.time()
190+
191+
print(f"Elapsed time: {time_end - time_start}s")

0 commit comments

Comments
 (0)