cylindertransport.py

Mon, 03 Apr 2017 09:10:41 +0200

author
mdd
date
Mon, 03 Apr 2017 09:10:41 +0200
changeset 8
63b6f80e09ef
parent 6
57f17c62c137
child 9
a01a3fd32073
permissions
-rw-r--r--

added threejs stl viewer html testing

"""
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.width = 0 # will be calculated
        self.scad = {
            "tmpl": """// Color support only in compile mode (F5)
// cylindertransport.py OpenSCAD script
module tank(x, r, h) {
  color("SteelBlue") render() { //rotate([90,0,0]) {
    translate([x, r, r]) {
        sphere(r = r); // bottom
        cylinder(h = h-2*r, r = r);
    }
    translate([x, r, h-r]) {
        sphere(r = r); // top
        cylinder(h = r*1.4, r = r*0.25);
    }
  }
}

module spacer(x, r, rcyl, h) {
  color("DarkGrey") render() { //rotate([90,0,0]) {
    translate([x, r, rcyl]) {
        difference() {
            cylinder(h = h-2*rcyl, r = r);
            cylinder(h = h-2*rcyl, r = r*0.8);
        }
    }
  }
}
$fn = 10;
""",
            "spacer": "",
            "cylinder": ""
            }
        self.circles = []
        self.spacings = []
        self.margin = 20
        self.scale3d = 0.1

    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"] += "spacer(%f, %f, %f, %f);\n" % (
            posx * self.scale3d, r_2 * self.scale3d, r_3 * self.scale3d,
            CYLINDER[self.cylinders[0]][1] * self.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, label = self.calc_min(r_1, r_2)
            # draw cylinder
            self._circle(posx, r_1, "Tank " + self.cylinders[i])
            self.scad["cylinder"] += "tank(%f, %f, %f);\n" % (
                posx * self.scale3d, r_1 * self.scale3d,
                CYLINDER[self.cylinders[i]][1] * self.scale3d)
            sx1 = posx + r_1
            posx += offset(r_1, r_2)
            # draw right spacer
            self._circle(posx, r_2, label, 0.5)
            self.scad["spacer"] += "spacer(%f, %f, %f, %f);\n" % (
                posx * self.scale3d, r_2 * self.scale3d, r_1 * self.scale3d,
                CYLINDER[self.cylinders[i]][1] * self.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, "Tank " + self.cylinders[i + 1])
                self.scad["cylinder"] += "tank(%f, %f, %f);\n" % (
                    posx * self.scale3d, r_3 * self.scale3d,
                    CYLINDER[self.cylinders[i + 1]][1] * self.scale3d)
                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"] += "spacer(%f, %f, %f, %f);\n" % (
            posx * self.scale3d, r_2 * self.scale3d, r_3 * self.scale3d,
            CYLINDER[self.cylinders[-1]][1] * self.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 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(worker.scad["tmpl"])
            fd.write("translate([%f,0,0]) {\n" % (((worker.width - 2 * worker.margin) / -2)*worker.scale3d)) # center the object
            fd.write(worker.scad["cylinder"])
            fd.write(worker.scad["spacer"])
            fd.write("}\n")

if __name__ == "__main__":
    run()

mercurial