From f3b554dc149d7b790d68d7eaea7db8e3db7adc4c Mon Sep 17 00:00:00 2001 From: jaseg Date: Mon, 5 Aug 2024 17:48:37 +0200 Subject: [PATCH] We have animations now --- gear_mesh_planning.py | 72 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 65 insertions(+), 7 deletions(-) diff --git a/gear_mesh_planning.py b/gear_mesh_planning.py index edd929d..287c95b 100644 --- a/gear_mesh_planning.py +++ b/gear_mesh_planning.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 +import re import colorsys import textwrap import math @@ -194,23 +195,25 @@ blue = lambda s, l: hls_svg(240, l, s) @click.option('-r', '--radius', default='50/50') @click.option('-n', '--num-meshes', default='5/3') @click.option('-w', '--mesh-width', default='10/10') +@click.option('-t', '--mesh-thickness', default='1.6/1.6') @click.option('-o', '--offset', type=float, default=20) @click.option('-p', '--phase', type=float, default=0) @click.option('--counterrotation/--corotation') @click.argument('out_svg_1', type=click.Path(dir_okay=False, path_type=Path)) @click.argument('out_svg_2', type=click.Path(dir_okay=False, path_type=Path)) -def cli(out_svg_1, out_svg_2, radius, num_meshes, mesh_width, offset, phase, counterrotation): +@click.argument('out_svg_3', type=click.Path(dir_okay=False, path_type=Path)) +def cli(out_svg_1, out_svg_2, out_svg_3, radius, num_meshes, mesh_width, mesh_thickness, offset, phase, counterrotation): tags = [] dim_tags = [] plots = [] - def parse_split(s, parse=float): + def parse_split(s, parse=float, extra_args=((), ())): if not isinstance(s, str): return s, s l, _, r = s.partition('/') - l = parse(l) - r = parse(r) if r else l + l = parse(l, *extra_args[0]) + r = parse(r, *extra_args[1]) if r else l return l, r r1, r2 = parse_split(radius) @@ -218,10 +221,25 @@ def cli(out_svg_1, out_svg_2, radius, num_meshes, mesh_width, offset, phase, cou assert offset > 0 assert offset-r2 < r1 < offset+r2 + th1, th2 = parse_split(mesh_thickness) + assert th1 > 0 and th2 > 0 + n1, n2 = parse_split(num_meshes, int) assert n1 > 0 and n2 > 0 - mesh_w1, mesh_w2 = parse_split(mesh_width) + def parse_mesh_width(s, r, th): + num, unit = re.fullmatch(r'([0-9.]+)\s*(|deg|degree|mm|cm)', s).groups() + num = float(num) + + if unit in ('', 'deg', 'degree'): + return num + + r -= th/2 + out = math.degrees(math.atan(num/2 / r)) + print(f'Calculated mesh angle for mesh width {num:.1f}{unit} and thickness {th:.1f}mm at radius {r:.1f}mm: {out:.1f}°') + return out + + mesh_w1, mesh_w2 = parse_split(mesh_width, parse=parse_mesh_width, extra_args=((r1, th1), (r2, th2))) assert mesh_w1 > 0 and mesh_w2 > 0 mesh_w1, mesh_w2 = math.radians(mesh_w1), math.radians(mesh_w2) @@ -325,7 +343,7 @@ def cli(out_svg_1, out_svg_2, radius, num_meshes, mesh_width, offset, phase, cou for i in range(num): yield (i*(space+w) + phase)%(2*math.pi), (i*(space+w) + w + phase)%(2*math.pi) - shift_angles = lambda angles, shift: [((a1+shift)%(2*math.pi), (a2+shift)%(2*math.pi)) for a1, a2 in angles] + shift_angles = lambda angles, shift: [((a1+shift)%tau, (a2+shift)%tau) for a1, a2 in angles] angles_1, angles_2 = list(make_angles(n1, mesh_w1, 0)), list(make_angles(n2, mesh_w2, math.radians(phase))) plots.append(list(plot_angles(angles_1, c1, 'url(#hatch_1)', title='Mesh 1'))) @@ -391,13 +409,19 @@ def cli(out_svg_1, out_svg_2, radius, num_meshes, mesh_width, offset, phase, cou collided = list(collide_schedules(angles_1, angles_2)) + best_solution = None def plot_centers(collided): + nonlocal best_solution + + widths = sorted(((end-start)%tau, start, end) for start, end in collided) + if collided: print('Best phases:') + w, start, end = widths[0] + best_solution = (start + (end-start)/2) % tau else: print('No solution.') - widths = sorted(((end-start)%tau, start, end) for start, end in collided) for w, start, end in widths: if math.isclose(start, end, abs_tol=1e-6): print(f'{math.degrees(start):>3.0f}° (exact)') @@ -430,6 +454,40 @@ def cli(out_svg_1, out_svg_2, radius, num_meshes, mesh_width, offset, phase, cou out_svg_2.write_text(str(Tag.setup_svg(tags + dim_tags, bounds=((0, 0), (100, (i+.8)*pitch)), margin=3))) + tags = [] + tags.append(Tag('defs', defs)) + dim_tags = [] + + for cx, cy, r, th, sched, stroke, fill in [ + ( 0, 0, r1, th1, shift_angles(angles_1, a1), c1, 'url(#hatch_1)'), + (offset, 0, r2, th2, shift_angles(angles_2, math.pi - a2 - best_solution), c2, 'url(#hatch_2)')]: + + max_a = max((a2 - a1) % tau for a1, a2 in sched) + max_w = math.tan(max_a) * (r-th/2) + max_rd = math.hypot(r + th/2, max_w) + tags.append(Tag('circle', cx=cx, cy=cy, fill='none', stroke=fill, stroke_opacity=0.4, + r=((r-th/2) + max_rd)/2, stroke_width=max_rd - (r-th/2))) + tags.append(Tag('circle', cx=cx, cy=cy, fill='none', stroke=stroke, + r=(r-th/2), stroke_width=0.15)) + tags.append(Tag('circle', cx=cx, cy=cy, fill='none', stroke=stroke, + r=max_rd, stroke_width=0.15)) + + group = [] + for a1, a2 in sched: + da = (a2-a1)%tau + aw = math.tan(da)*(r-th/2) + rot = math.degrees(a2 - a1 + a1) + group.append(Tag('rect', x=cx + r-th/2, y=cy-aw, width=th, height=2*aw, + fill=stroke, transform=f'rotate({rot} {cx} {cy})')) + group.append(Tag('animateTransform', attributeName='transform', attributeType='XML', type='rotate', + _from=f'360 {cx} {cy}', to=f'0 {cx} {cy}', dur='10s', repeatCount='indefinite')) + tags.append(Tag('g', group)) + print(math.degrees(best_solution)) + + out_svg_3.write_text(str(Tag.setup_svg(tags + dim_tags, bounds=( + (min(-r1, -r2+offset), min(-r1, -r2)), + (max(r1, r2+offset), max(r1, r2))), margin=3))) + if __name__ == '__main__': cli()