|
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 """ |
1 from math import sqrt |
7 from math import sqrt |
2 from PIL import Image, ImageDraw, ImageFont |
8 from PIL import Image, ImageDraw, ImageFont |
3 import argparse, sys |
9 import argparse, sys |
4 from data import CYLINDER, PIPES |
10 from data import CYLINDER, PIPES |
5 from config import FONTBASE |
11 from config import FONTBASE |
6 |
12 |
7 def offset(r1, r2): |
13 def offset(r_1, r_2): |
8 return (2*sqrt(r1*r2)) |
14 """ |
|
15 Calculate horizontal center offset of two circles |
|
16 so they tangent each other |
|
17 """ |
|
18 return 2 * sqrt(r_1 * r_2) |
9 |
19 |
10 class CylinderSpacerCalculator: |
20 class CylinderSpacerCalculator(): |
11 def __init__(self, cylinders = ["10", "10"], space_min = 10): |
21 """ |
12 self.cylinders = cylinders |
22 Class to calculate transport spacer pipes between |
13 self.space_min = space_min |
23 Scuba cylinders |
14 self.font = FONTBASE + "arial.ttf" |
24 """ |
15 self.scad = "// Color support only in compile mode (F5)\n" +\ |
25 def __init__(self, cylinders, space_min=10): |
16 "include <cylindertransport.scad>\n" |
26 self.cylinders = cylinders |
17 self.circles = [] |
27 self.space_min = space_min |
18 self.spacings = [] |
28 self.font = FONTBASE + "arial.ttf" |
19 self.margin = 20 |
29 self.scad = "// Color support only in compile mode (F5)\n" +\ |
|
30 "include <cylindertransport.scad>\n" |
|
31 self.circles = [] |
|
32 self.spacings = [] |
|
33 self.margin = 20 |
20 |
34 |
21 def calc_min(self, r1, r2): |
35 def calc_min(self, r_1, r_2): |
22 # stupider annaehreungsversuch, bis sich die beiden Tauchflaschen r1 und r2 nicht mehr beruehren |
36 """ |
23 for p in PIPES: |
37 stupider annaehreungsversuch, bis sich die beiden |
24 i = p[1] / 2 |
38 Tauchflaschen r_1 und r_2 nicht mehr beruehren |
25 if i>r1 or i>r2: |
39 Rueckgabe: 3 Zylinderradien und das label der verwendeten Roehre |
26 return None |
40 """ |
27 x1 = offset(r1, i) |
41 for p in PIPES: |
28 x2 = offset(r2, i) |
42 i = p[1] / 2 |
29 x = (x1 + x2) - (r1+r2) |
43 x_1 = offset(r_1, i) |
30 if x >= self.space_min: |
44 x_2 = offset(r_2, i) |
31 print "%s Pipe (%.1fmm), Cylinder spacing: %imm" % (p[0], p[1], x) |
45 x = (x_1 + x_2) - (r_1 + r_2) |
32 return [r1, i, r2, p[0]] |
46 if x >= self.space_min: |
33 return None |
47 print "%s Pipe (%.1fmm), Cylinder spacing: %imm" % ( |
|
48 p[0], p[1], x) |
|
49 return [r_1, i, r_2, p[0]] |
|
50 print "Abort: no suitable pipe found" |
|
51 sys.exit(1) |
34 |
52 |
35 def _circle(self, x, r, txt = "", size = 1.0): |
53 def _circle(self, x, r, txt="", size=1.0): |
36 self.circles.append([ |
54 """ |
37 x, r, txt, size |
55 Push the circle definition for later rendering |
38 ]) |
56 """ |
|
57 self.circles.append([ |
|
58 x, r, txt, size |
|
59 ]) |
39 |
60 |
40 def _calculate(self): |
61 def _calculate(self): |
41 # first bottle spacer |
62 """ |
42 r1 = CYLINDER[self.cylinders[0]][0] / 2 |
63 Calculate all cylinder and spacer circles |
43 r2 = CYLINDER[self.cylinders[1]][0] / 2 |
64 """ |
44 r1, r2, r3, dn = self.calc_min(r1, r2) |
65 # first bottle spacer |
45 x = self.margin + r2 # start offset x |
66 r_1 = CYLINDER[self.cylinders[0]][0] / 2 |
46 self._circle(x, r2, dn, 0.5) |
67 r_2 = CYLINDER[self.cylinders[1]][0] / 2 |
47 self.scad += "spacer(%i, %i, %i, %i);\n" % ( |
68 r_1, r_2, r_3, dn = self.calc_min(r_1, r_2) |
48 x, r2, r3, CYLINDER[self.cylinders[0]][1]) |
69 x = self.margin + r_2 # start offset x |
49 x = x + offset(r2, r3) |
70 self._circle(x, r_2, dn, 0.5) |
|
71 self.scad += "spacer(%i, %i, %i, %i);\n" % ( |
|
72 x, r_2, r_3, CYLINDER[self.cylinders[0]][1]) |
|
73 x = x + offset(r_2, r_3) |
50 |
74 |
51 for i in range(0, len(self.cylinders)-1): |
75 for i in range(0, len(self.cylinders)-1): |
52 r1 = CYLINDER[self.cylinders[i]][0] / 2 |
76 r_1 = CYLINDER[self.cylinders[i]][0] / 2 |
53 r2 = CYLINDER[self.cylinders[i+1]][0] / 2 |
77 r_2 = CYLINDER[self.cylinders[i+1]][0] / 2 |
54 r1, r2, r3, dn = self.calc_min(r1, r2) |
78 r_1, r_2, r_3, dn = self.calc_min(r_1, r_2) |
55 # draw cylinder |
79 # draw cylinder |
56 self._circle(x, r1, "Tank " + self.cylinders[i]) |
80 self._circle(x, r_1, "Tank " + self.cylinders[i]) |
57 self.scad += "tank(%i, %i, %i);\n" % ( |
81 self.scad += "tank(%i, %i, %i);\n" % ( |
58 x, r1, CYLINDER[self.cylinders[i]][1]) |
82 x, r_1, CYLINDER[self.cylinders[i]][1]) |
59 sx1 = x+r1 |
83 sx1 = x + r_1 |
60 x = x + offset(r1, r2) |
84 x = x + offset(r_1, r_2) |
61 # draw right spacer |
85 # draw right spacer |
62 self._circle(x, r2, dn, 0.5) |
86 self._circle(x, r_2, dn, 0.5) |
63 self.scad += "spacer(%i, %i, %i, %i);\n" % ( |
87 self.scad += "spacer(%i, %i, %i, %i);\n" % ( |
64 x, r2, r1, CYLINDER[self.cylinders[i]][1]) |
88 x, r_2, r_1, CYLINDER[self.cylinders[i]][1]) |
65 x = x + offset(r2, r3) |
89 x = x + offset(r_2, r_3) |
66 sx2 = x-r3 |
90 sx2 = x - r_3 |
67 if i == (len(self.cylinders) - 2): |
91 if i == (len(self.cylinders) - 2): |
68 # draw last bottle |
92 # draw last bottle |
69 self._circle(x, r3, "Tank " + self.cylinders[i+1]) |
93 self._circle(x, r_3, "Tank " + self.cylinders[i + 1]) |
70 self.scad += "tank(%i, %i, %i);\n" % ( |
94 self.scad += "tank(%i, %i, %i);\n" % ( |
71 x, r3, CYLINDER[self.cylinders[i+1]][1]) |
95 x, r_3, CYLINDER[self.cylinders[i + 1]][1]) |
72 x = x + offset(r2, r3) |
96 x = x + offset(r_2, r_3) |
73 |
97 |
74 self.spacings.append([sx1, sx2]) |
98 self.spacings.append([sx1, sx2]) |
75 |
99 |
76 # last bottle spacer pipe |
100 # last bottle spacer pipe |
77 self._circle(x, r2, dn, 0.5) |
101 self._circle(x, r_2, dn, 0.5) |
78 self.scad += "spacer(%i, %i, %i, %i);\n" % ( |
102 self.scad += "spacer(%i, %i, %i, %i);\n" % ( |
79 x, r2, r3, CYLINDER[self.cylinders[i+1]][1]) |
103 x, r_2, r_3, CYLINDER[self.cylinders[len(self.cylinders)]][1]) |
80 return int(x + r2 + self.margin) |
104 return int(x + r_2 + self.margin) |
81 |
105 |
82 def render_image(self): |
106 def centertext(self, draw, x, y, txt, size): |
83 width = self._calculate() |
107 font = ImageFont.truetype(self.font, int(24 * size)) |
84 image = Image.new('1', (width, 250)) # create new image |
108 tox, toy = draw.textsize(txt, font=font) |
85 draw = ImageDraw.Draw(image) |
109 draw.text((x - tox / 2, y - toy / 2), \ |
86 # draw calculated circles |
110 txt, font=font, fill='#ffffff') |
87 for circle in self.circles: |
|
88 x, r, txt, size = circle |
|
89 draw.arc([x - r, 0, x + r, 2 * r], 0, 360, 'white') |
|
90 if txt != "": |
|
91 font = ImageFont.truetype(self.font, int(24 * size)) |
|
92 tox, toy = draw.textsize(txt, font=font) |
|
93 draw.text((x - tox / 2, r - toy / 2), txt, font=font, fill='#ffffff') |
|
94 |
111 |
95 # draw the spacing between cylinders |
112 def render_image(self): |
96 spacerY1 = 200 |
113 """ |
97 spacerY2 = 220 |
114 Start the calculation and return rendered PIL image object |
98 for spacing in self.spacings: |
115 """ |
99 sx1, sx2 = spacing |
116 width = self._calculate() |
100 draw.line((sx1, spacerY1, sx1, spacerY2), fill='#ffffff') |
117 image = Image.new('1', (width, 250)) # create new image |
101 draw.line((sx2, spacerY1, sx2, spacerY2), fill='#ffffff') |
118 draw = ImageDraw.Draw(image) |
102 txt = "%imm" % (sx2 - sx1) |
119 # draw calculated circles |
103 font = ImageFont.truetype(FONTBASE+"arial.ttf", 12) |
120 for circle in self.circles: |
104 tox, toy = draw.textsize(txt, font=font) |
121 x, r, txt, size = circle |
105 draw.text((sx1 + (sx2 - sx1) / 2 - tox / 2, |
122 draw.arc([x - r, 0, x + r, 2 * r], 0, 360, 'white') |
106 spacerY2 + toy / 2), txt, font=font, fill='#ffffff') |
123 if txt != "": |
|
124 self.centertext(draw, x, r, txt, size) |
107 |
125 |
108 return image |
126 # draw the spacing between cylinders |
|
127 spacer_y1 = 200 |
|
128 spacer_y2 = 220 |
|
129 for sx1, sx2 in self.spacings: |
|
130 draw.line((sx1, spacer_y1, sx1, spacer_y2), fill='#ffffff') |
|
131 draw.line((sx2, spacer_y1, sx2, spacer_y2), fill='#ffffff') |
|
132 self.centertext(draw, sx1 + (sx2 - sx1) / 2, \ |
|
133 spacer_y2 + 10, "%imm" % (sx2 - sx1), 0.5) |
|
134 |
|
135 return image |
109 |
136 |
110 if __name__ == "__main__": |
137 if __name__ == "__main__": |
111 parser = argparse.ArgumentParser(description = \ |
138 parser = argparse.ArgumentParser(description=\ |
112 "Calculate spacer pipes for pressure cylinder transport\n" +\ |
139 "Calculate spacer pipes for pressure cylinder transport\n" +\ |
113 "Known cylinder types:\n" + ", ".join(sorted(CYLINDER.keys())) ) |
140 "Known cylinder types:\n" + ", ".join(sorted(CYLINDER.keys()))) |
114 parser.add_argument('cylinders', metavar = 'cylinder', |
141 parser.add_argument('cylinders', metavar='cylinder', \ |
115 type = str, nargs = '+', help = 'cylinder types') |
142 type=str, nargs='+', help='cylinder types') |
116 parser.add_argument('--space', dest='space_min', |
143 parser.add_argument('--space', dest='space_min', \ |
117 type = int, default=10, |
144 type=int, default=10, \ |
118 help='minimum space between cylinders (mm)') |
145 help='minimum space between cylinders (mm)') |
119 |
146 |
120 args = parser.parse_args() |
147 options = parser.parse_args() |
121 |
148 |
122 for test in args.cylinders: |
149 for test in options.cylinders: |
123 if not test in CYLINDER.keys(): |
150 if not test in CYLINDER.keys(): |
124 print "Cylinder type '%s' is unknown" % test |
151 print "Cylinder type '%s' is unknown" % test |
125 sys.exit(1) |
152 sys.exit(1) |
126 |
153 |
127 obj = CylinderSpacerCalculator( |
154 worker = CylinderSpacerCalculator( |
128 args.cylinders, args.space_min) |
155 options.cylinders, options.space_min) |
129 |
156 |
130 image = obj.render_image() |
157 image = worker.render_image() |
131 image.show() |
158 image.show() |
132 |
159 |
133 print "\n------------ START SCAD SCRIPT ------------" |
160 print "\n------------ START SCAD SCRIPT ------------" |
134 print obj.scad + "------------ END SCAD SCRIPT ------------" |
161 print worker.scad + "------------ END SCAD SCRIPT ------------" |