Thu, 06 Apr 2017 19:00:07 +0200
ht is not PVC....
""" 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 stl import OPENSCAD_MODULES, precompile_all_stl from config import FONTBASE, SCALE3D 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.width = 0 # will be calculated self.scad = { "tmpl": """// Color support only in compile mode (F5) // cylindertransport.py OpenSCAD script $fn = 10; """ + OPENSCAD_MODULES, "spacer": "", "cylinder": "" } 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] print "// Abort: no suitable pipe found" sys.exit(1) def _circle(self, posx, radius, data, cylinder=""): """ Push the circle definition for later rendering """ if cylinder == "": size = 0.5 txt = data[0] else: size = 1.0 txt = "Tank " + cylinder self.circles.append([ posx, radius, txt, size, data, cylinder ]) 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, data = self.calc_min(r_1, r_2) posx = self.margin + r_2 # start offset x self._circle(posx, r_2, data) self.scad["spacer"] += "spacer(%f, %f, %f, %f);\n" % ( posx * SCALE3D, r_2 * SCALE3D, r_3 * SCALE3D, CYLINDER[self.cylinders[0]][1] * SCALE3D) 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, data = self.calc_min(r_1, r_2) # draw cylinder self._circle(posx, r_1, \ CYLINDER[self.cylinders[i]], self.cylinders[i]) self.scad["cylinder"] += "tank(%f, %f, %f);\n" % ( posx * SCALE3D, r_1 * SCALE3D, CYLINDER[self.cylinders[i]][1] * SCALE3D) sx1 = posx + r_1 posx += offset(r_1, r_2) # draw right spacer self._circle(posx, r_2, data) self.scad["spacer"] += "spacer(%f, %f, %f, %f);\n" % ( posx * SCALE3D, r_2 * SCALE3D, r_1 * SCALE3D, CYLINDER[self.cylinders[i]][1] * SCALE3D) posx += offset(r_2, r_3) sx2 = posx - r_3 if i == (len(self.cylinders) - 2): # draw last bottle self._circle(posx, r_3, \ CYLINDER[self.cylinders[i + 1]], self.cylinders[i + 1]) self.scad["cylinder"] += "tank(%f, %f, %f);\n" % ( posx * SCALE3D, r_3 * SCALE3D, CYLINDER[self.cylinders[i + 1]][1] * SCALE3D) posx += offset(r_2, r_3) self.spacings.append([sx1, sx2]) # last bottle spacer pipe self._circle(posx, r_2, data) self.scad["spacer"] += "spacer(%f, %f, %f, %f);\n" % ( posx * SCALE3D, r_2 * SCALE3D, r_3 * SCALE3D, CYLINDER[self.cylinders[-1]][1] * SCALE3D) 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 """ self.width = self.calculate() image = Image.new('1', (self.width, 250)) # create new image draw = ImageDraw.Draw(image) # draw calculated circles for posx, radius, txt, size, data, cylinder 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') parser.add_argument('--precompile', dest='precompile', \ default=False, action='store_true', \ help='Precompile STL objects for 3D View') options = parser.parse_args() if options.precompile: precompile_all_stl() 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(worker.scad["tmpl"]) # center the object fd.write("translate([%f,0,0]) {\n" % ( ((worker.width - 2 * worker.margin) / -2) * SCALE3D)) fd.write(worker.scad["cylinder"]) fd.write(worker.scad["spacer"]) fd.write("}\n") if __name__ == "__main__": run()