Mon, 03 Apr 2017 05:21:17 +0200
finishing
4 | 1 | """ |
2 | Calculation of spacer pipes for Scuba cylinder transportation | |
3 | 2017 by NeoSoft, mdd | |
4 | Input: see commandline help | |
5 | Output: 2D schematic & 3D OpenSCAD script | |
6 | """ | |
0 | 7 | from math import sqrt |
8 | from PIL import Image, ImageDraw, ImageFont | |
9 | import argparse, sys | |
2 | 10 | from data import CYLINDER, PIPES |
11 | from config import FONTBASE | |
1 | 12 | |
4 | 13 | def offset(r_1, r_2): |
14 | """ | |
15 | Calculate horizontal center offset of two circles | |
16 | so they tangent each other | |
17 | """ | |
18 | return 2 * sqrt(r_1 * r_2) | |
0 | 19 | |
5 | 20 | class CylinderSpacerCalculator(object): |
4 | 21 | """ |
22 | Class to calculate transport spacer pipes between | |
23 | Scuba cylinders | |
24 | """ | |
25 | def __init__(self, cylinders, space_min=10): | |
26 | self.cylinders = cylinders | |
27 | self.space_min = space_min | |
28 | self.font = FONTBASE + "arial.ttf" | |
6 | 29 | self.scad = "// Color support only in compile mode (F5)\n" |
4 | 30 | self.circles = [] |
31 | self.spacings = [] | |
32 | self.margin = 20 | |
0 | 33 | |
4 | 34 | def calc_min(self, r_1, r_2): |
35 | """ | |
36 | stupider annaehreungsversuch, bis sich die beiden | |
37 | Tauchflaschen r_1 und r_2 nicht mehr beruehren | |
38 | Rueckgabe: 3 Zylinderradien und das label der verwendeten Roehre | |
39 | """ | |
5 | 40 | for pipe in PIPES: |
41 | i = pipe[1] / 2 | |
4 | 42 | x_1 = offset(r_1, i) |
43 | x_2 = offset(r_2, i) | |
5 | 44 | posx = (x_1 + x_2) - (r_1 + r_2) |
45 | if posx >= self.space_min: | |
46 | print "// %s Pipe (%.1fmm), Cylinder spacing: %imm" % ( | |
47 | pipe[0], pipe[1], posx) | |
48 | return [r_1, i, r_2, pipe[0]] | |
49 | print "// Abort: no suitable pipe found" | |
4 | 50 | sys.exit(1) |
0 | 51 | |
5 | 52 | def _circle(self, posx, radius, txt="", size=1.0): |
4 | 53 | """ |
54 | Push the circle definition for later rendering | |
55 | """ | |
56 | self.circles.append([ | |
5 | 57 | posx, radius, txt, size |
4 | 58 | ]) |
3 | 59 | |
4 | 60 | def _calculate(self): |
61 | """ | |
62 | Calculate all cylinder and spacer circles | |
63 | """ | |
64 | # first bottle spacer | |
65 | r_1 = CYLINDER[self.cylinders[0]][0] / 2 | |
66 | r_2 = CYLINDER[self.cylinders[1]][0] / 2 | |
5 | 67 | r_1, r_2, r_3, label = self.calc_min(r_1, r_2) |
68 | posx = self.margin + r_2 # start offset x | |
69 | self._circle(posx, r_2, label, 0.5) | |
4 | 70 | self.scad += "spacer(%i, %i, %i, %i);\n" % ( |
5 | 71 | posx, r_2, r_3, CYLINDER[self.cylinders[0]][1]) |
72 | posx += offset(r_2, r_3) | |
0 | 73 | |
5 | 74 | for i in range(0, len(self.cylinders) - 1): |
4 | 75 | r_1 = CYLINDER[self.cylinders[i]][0] / 2 |
76 | r_2 = CYLINDER[self.cylinders[i+1]][0] / 2 | |
5 | 77 | r_1, r_2, r_3, label = self.calc_min(r_1, r_2) |
4 | 78 | # draw cylinder |
5 | 79 | self._circle(posx, r_1, "Tank " + self.cylinders[i]) |
4 | 80 | self.scad += "tank(%i, %i, %i);\n" % ( |
5 | 81 | posx, r_1, CYLINDER[self.cylinders[i]][1]) |
82 | sx1 = posx + r_1 | |
83 | posx += offset(r_1, r_2) | |
4 | 84 | # draw right spacer |
5 | 85 | self._circle(posx, r_2, label, 0.5) |
4 | 86 | self.scad += "spacer(%i, %i, %i, %i);\n" % ( |
5 | 87 | posx, r_2, r_1, CYLINDER[self.cylinders[i]][1]) |
88 | posx += offset(r_2, r_3) | |
89 | sx2 = posx - r_3 | |
4 | 90 | if i == (len(self.cylinders) - 2): |
91 | # draw last bottle | |
5 | 92 | self._circle(posx, r_3, "Tank " + self.cylinders[i + 1]) |
4 | 93 | self.scad += "tank(%i, %i, %i);\n" % ( |
5 | 94 | posx, r_3, CYLINDER[self.cylinders[i + 1]][1]) |
95 | posx += offset(r_2, r_3) | |
3 | 96 | |
4 | 97 | self.spacings.append([sx1, sx2]) |
98 | ||
99 | # last bottle spacer pipe | |
5 | 100 | self._circle(posx, r_2, label, 0.5) |
4 | 101 | self.scad += "spacer(%i, %i, %i, %i);\n" % ( |
5 | 102 | posx, r_2, r_3, CYLINDER[self.cylinders[-1]][1]) |
103 | return int(posx + r_2 + self.margin) | |
3 | 104 | |
5 | 105 | def centertext(self, draw, posx, posy, txt, size): |
106 | """ | |
107 | Centers text at position horizontally and vertically | |
108 | """ | |
4 | 109 | font = ImageFont.truetype(self.font, int(24 * size)) |
110 | tox, toy = draw.textsize(txt, font=font) | |
5 | 111 | draw.text((posx - tox / 2, posy - toy / 2), \ |
4 | 112 | txt, font=font, fill='#ffffff') |
0 | 113 | |
4 | 114 | def render_image(self): |
115 | """ | |
116 | Start the calculation and return rendered PIL image object | |
117 | """ | |
118 | width = self._calculate() | |
119 | image = Image.new('1', (width, 250)) # create new image | |
120 | draw = ImageDraw.Draw(image) | |
121 | # draw calculated circles | |
5 | 122 | for posx, radius, txt, size in self.circles: |
123 | draw.arc([posx - radius, self.margin, \ | |
124 | posx + radius, 2 * radius + self.margin], \ | |
125 | 0, 360, 'white') | |
4 | 126 | if txt != "": |
5 | 127 | self.centertext(draw, posx, radius + self.margin, txt, size) |
0 | 128 | |
4 | 129 | # draw the spacing between cylinders |
130 | spacer_y1 = 200 | |
131 | spacer_y2 = 220 | |
132 | for sx1, sx2 in self.spacings: | |
133 | draw.line((sx1, spacer_y1, sx1, spacer_y2), fill='#ffffff') | |
134 | draw.line((sx2, spacer_y1, sx2, spacer_y2), fill='#ffffff') | |
135 | self.centertext(draw, sx1 + (sx2 - sx1) / 2, \ | |
136 | spacer_y2 + 10, "%imm" % (sx2 - sx1), 0.5) | |
3 | 137 | |
4 | 138 | return image |
0 | 139 | |
5 | 140 | def run(): |
141 | """ | |
142 | Command line program invocation | |
143 | """ | |
4 | 144 | parser = argparse.ArgumentParser(description=\ |
145 | "Calculate spacer pipes for pressure cylinder transport\n" +\ | |
146 | "Known cylinder types:\n" + ", ".join(sorted(CYLINDER.keys()))) | |
147 | parser.add_argument('cylinders', metavar='cylinder', \ | |
148 | type=str, nargs='+', help='cylinder types') | |
149 | parser.add_argument('--space', dest='space_min', \ | |
150 | type=int, default=10, \ | |
3 | 151 | help='minimum space between cylinders (mm)') |
6 | 152 | parser.add_argument('--scad', dest='scad', \ |
153 | type=str, default="", metavar='filename', \ | |
154 | help='Write OpenSCAD script file') | |
0 | 155 | |
4 | 156 | options = parser.parse_args() |
0 | 157 | |
4 | 158 | for test in options.cylinders: |
159 | if not test in CYLINDER.keys(): | |
160 | print "Cylinder type '%s' is unknown" % test | |
161 | sys.exit(1) | |
0 | 162 | |
4 | 163 | worker = CylinderSpacerCalculator( |
164 | options.cylinders, options.space_min) | |
3 | 165 | |
5 | 166 | img = worker.render_image() |
167 | img.show() | |
1 | 168 | |
6 | 169 | if (options.scad != ""): |
170 | with open(options.scad, "w") as fd: | |
171 | fd.write("""// cylindertransport.py OpenSCAD script | |
172 | module tank(x, r, h) { | |
173 | cap = r; | |
174 | color("SteelBlue") render() rotate([90,0,0]) { | |
175 | translate([x, r, cap]) { | |
176 | sphere(r = r); // bottom | |
177 | cylinder(h = h-2*cap, r = r); | |
178 | } | |
179 | translate([x, r, h-cap]) { | |
180 | sphere(r = r); // top | |
181 | cylinder(h = 30+cap, r = 25); | |
182 | } | |
183 | } | |
184 | } | |
5 | 185 | |
6 | 186 | module spacer(x, r, rcylinder, h) { |
187 | cap = rcylinder; | |
188 | ||
189 | color("DarkGrey") render() rotate([90,0,0]) | |
190 | translate([x, r, cap]) { | |
191 | difference() { | |
192 | cylinder(h = h-2*cap, r = r); | |
193 | cylinder(h = h-2*cap, r = r-5); | |
194 | } | |
195 | } | |
196 | } | |
197 | $fn = 20;\n""") | |
198 | fd.write(worker.scad) | |
5 | 199 | |
200 | if __name__ == "__main__": | |
201 | run() |