cylindertransport.py

Mon, 03 Apr 2017 04:47:41 +0200

author
mdd
date
Mon, 03 Apr 2017 04:47:41 +0200
changeset 5
c2158ae1dc05
parent 4
f62562506053
child 6
57f17c62c137
permissions
-rw-r--r--

finished cylindertransport.py

"""
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" +\
            "include <cylindertransport.scad>\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)')

    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()

    print "\n// ------------ START SCAD SCRIPT ------------"
    print worker.scad + "// ------------ END SCAD SCRIPT ------------"


if __name__ == "__main__":
    run()

mercurial