|
| 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