Mon, 03 Apr 2017 05:21:17 +0200
finishing
""" Calculation of spacer pipes for Scuba cylinder transportation 2017 by NeoSoft, mdd Input: see commandline help Output: 2D schematic & 3D OpenSCAD script """ from math import sqrt from PIL import Image, ImageDraw, ImageFont import argparse, sys from data import CYLINDER, PIPES from config import FONTBASE def offset(r_1, r_2): """ Calculate horizontal center offset of two circles so they tangent each other """ return 2 * sqrt(r_1 * r_2) class CylinderSpacerCalculator(object): """ Class to calculate transport spacer pipes between Scuba cylinders """ def __init__(self, cylinders, space_min=10): self.cylinders = cylinders self.space_min = space_min self.font = FONTBASE + "arial.ttf" self.scad = "// Color support only in compile mode (F5)\n" self.circles = [] self.spacings = [] self.margin = 20 def calc_min(self, r_1, r_2): """ stupider annaehreungsversuch, bis sich die beiden Tauchflaschen r_1 und r_2 nicht mehr beruehren Rueckgabe: 3 Zylinderradien und das label der verwendeten Roehre """ for pipe in PIPES: i = pipe[1] / 2 x_1 = offset(r_1, i) x_2 = offset(r_2, i) posx = (x_1 + x_2) - (r_1 + r_2) if posx >= self.space_min: print "// %s Pipe (%.1fmm), Cylinder spacing: %imm" % ( pipe[0], pipe[1], posx) return [r_1, i, r_2, pipe[0]] print "// Abort: no suitable pipe found" sys.exit(1) def _circle(self, posx, radius, txt="", size=1.0): """ Push the circle definition for later rendering """ self.circles.append([ posx, radius, txt, size ]) def _calculate(self): """ Calculate all cylinder and spacer circles """ # first bottle spacer r_1 = CYLINDER[self.cylinders[0]][0] / 2 r_2 = CYLINDER[self.cylinders[1]][0] / 2 r_1, r_2, r_3, label = self.calc_min(r_1, r_2) posx = self.margin + r_2 # start offset x self._circle(posx, r_2, label, 0.5) self.scad += "spacer(%i, %i, %i, %i);\n" % ( posx, r_2, r_3, CYLINDER[self.cylinders[0]][1]) posx += offset(r_2, r_3) for i in range(0, len(self.cylinders) - 1): r_1 = CYLINDER[self.cylinders[i]][0] / 2 r_2 = CYLINDER[self.cylinders[i+1]][0] / 2 r_1, r_2, r_3, label = self.calc_min(r_1, r_2) # draw cylinder self._circle(posx, r_1, "Tank " + self.cylinders[i]) self.scad += "tank(%i, %i, %i);\n" % ( posx, r_1, CYLINDER[self.cylinders[i]][1]) sx1 = posx + r_1 posx += offset(r_1, r_2) # draw right spacer self._circle(posx, r_2, label, 0.5) self.scad += "spacer(%i, %i, %i, %i);\n" % ( posx, r_2, r_1, CYLINDER[self.cylinders[i]][1]) posx += offset(r_2, r_3) sx2 = posx - r_3 if i == (len(self.cylinders) - 2): # draw last bottle self._circle(posx, r_3, "Tank " + self.cylinders[i + 1]) self.scad += "tank(%i, %i, %i);\n" % ( posx, r_3, CYLINDER[self.cylinders[i + 1]][1]) posx += offset(r_2, r_3) self.spacings.append([sx1, sx2]) # last bottle spacer pipe self._circle(posx, r_2, label, 0.5) self.scad += "spacer(%i, %i, %i, %i);\n" % ( posx, r_2, r_3, CYLINDER[self.cylinders[-1]][1]) return int(posx + r_2 + self.margin) def centertext(self, draw, posx, posy, txt, size): """ Centers text at position horizontally and vertically """ font = ImageFont.truetype(self.font, int(24 * size)) tox, toy = draw.textsize(txt, font=font) draw.text((posx - tox / 2, posy - toy / 2), \ txt, font=font, fill='#ffffff') def render_image(self): """ Start the calculation and return rendered PIL image object """ width = self._calculate() image = Image.new('1', (width, 250)) # create new image draw = ImageDraw.Draw(image) # draw calculated circles for posx, radius, txt, size in self.circles: draw.arc([posx - radius, self.margin, \ posx + radius, 2 * radius + self.margin], \ 0, 360, 'white') if txt != "": self.centertext(draw, posx, radius + self.margin, txt, size) # draw the spacing between cylinders spacer_y1 = 200 spacer_y2 = 220 for sx1, sx2 in self.spacings: draw.line((sx1, spacer_y1, sx1, spacer_y2), fill='#ffffff') draw.line((sx2, spacer_y1, sx2, spacer_y2), fill='#ffffff') self.centertext(draw, sx1 + (sx2 - sx1) / 2, \ spacer_y2 + 10, "%imm" % (sx2 - sx1), 0.5) return image def run(): """ Command line program invocation """ parser = argparse.ArgumentParser(description=\ "Calculate spacer pipes for pressure cylinder transport\n" +\ "Known cylinder types:\n" + ", ".join(sorted(CYLINDER.keys()))) parser.add_argument('cylinders', metavar='cylinder', \ type=str, nargs='+', help='cylinder types') parser.add_argument('--space', dest='space_min', \ type=int, default=10, \ help='minimum space between cylinders (mm)') parser.add_argument('--scad', dest='scad', \ type=str, default="", metavar='filename', \ help='Write OpenSCAD script file') options = parser.parse_args() for test in options.cylinders: if not test in CYLINDER.keys(): print "Cylinder type '%s' is unknown" % test sys.exit(1) worker = CylinderSpacerCalculator( options.cylinders, options.space_min) img = worker.render_image() img.show() if (options.scad != ""): with open(options.scad, "w") as fd: fd.write("""// cylindertransport.py OpenSCAD script module tank(x, r, h) { cap = r; color("SteelBlue") render() rotate([90,0,0]) { translate([x, r, cap]) { sphere(r = r); // bottom cylinder(h = h-2*cap, r = r); } translate([x, r, h-cap]) { sphere(r = r); // top cylinder(h = 30+cap, r = 25); } } } module spacer(x, r, rcylinder, h) { cap = rcylinder; color("DarkGrey") render() rotate([90,0,0]) translate([x, r, cap]) { difference() { cylinder(h = h-2*cap, r = r); cylinder(h = h-2*cap, r = r-5); } } } $fn = 20;\n""") fd.write(worker.scad) if __name__ == "__main__": run()