diff --git a/boxes/generators/can_storage.py b/boxes/generators/can_storage.py new file mode 100644 index 0000000..58238a0 --- /dev/null +++ b/boxes/generators/can_storage.py @@ -0,0 +1,365 @@ +#!/usr/bin/env python3 +# Copyright (C) 2013-2016 Florian Festi +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERself.canHightANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from boxes import * +import math + +class FrontEdge(edges.BaseEdge): + char = "a" + + def __call__(self, length, **kw): + x = math.ceil( ((self.canDiameter * 0.5 + 2 * self.thickness) * math.sin(math.radians(self.chuteAngle))) / self.thickness) + if self.top_edge != "e": + self.corner(90, self.thickness) + self.edge(0.5 * self.canDiameter) + self.corner(-90, 0.25 * self.canDiameter) + else: + self.moveTo(-self.burn, self.canDiameter + self.thickness, -90) + self.corner(90, 0.25 * self.canDiameter) + self.edge(self.thickness) + + self.edge(0.5 * self.canDiameter - self.thickness) + self.corner(-90, 0.25 * self.canDiameter) + self.edge(0.5 * self.canDiameter) + self.corner(90, self.thickness) + self.edge(x * self.thickness ) + self.corner(90, self.thickness) + self.edge(0.5 * self.canDiameter) + self.corner(-90, 0.25 * self.canDiameter) + self.edge(0.5 * self.canDiameter - (1 + x) * self.thickness + self.top_chute_height + self.bottom_chute_height - self.barrier_height) + self.corner(-90, 0.25 * self.canDiameter) + self.edge(0.5 * self.canDiameter) + self.corner(90, self.thickness) + self.edge(self.barrier_height) + self.edge(self.thickness) + +class TopChuteEdge(edges.BaseEdge): + char = "b" + + def __call__(self, length, **kw): + self.edge(0.2 * length - self.thickness) + self.corner(90, self.thickness) + self.edge(1.5*self.canDiameter - 2 * self.thickness) + self.corner(-90, self.thickness) + self.edge(0.6 * length - 2 * self.thickness) + self.corner(-90, self.thickness) + self.edge(1.5*self.canDiameter - 2 * self.thickness) + self.corner(90, self.thickness) + self.edge(0.2 * length - self.thickness) + +class BarrierEdge(edges.BaseEdge): + char = "A" + + def __call__(self, length, **kw): + self.edge(0.2*length) + self.corner(90,self.thickness/2) + self.corner(-90,self.thickness/2) + self.edge(0.6*length-2*self.thickness) + self.corner(-90,self.thickness/2) + self.corner(90,self.thickness/2) + self.edge(0.2*length) + + def startwidth(self): + return self.boxes.thickness + +class CanStorage(Boxes): + """Storage box for round containers""" + + description = """ +for AA batteries: + +![CanStorage for AA batteries](static/samples/CanStorageAA.jpg) + +for canned tomatos: +""" + + + ui_group = "Misc" + + def __init__(self): + Boxes.__init__(self) + + self.addSettingsArgs(edges.FingerJointSettings, finger=2.0, space=2.0, surroundingspaces=0.0) + self.addSettingsArgs(edges.StackableSettings) + self.addSettingsArgs(fillHolesSettings) + + self.argparser.add_argument( + "--top_edge", action="store", + type=ArgparseEdgeType("efhŠ"), choices=list("efhŠ"), + default="Š", help="edge type for top edge") + self.argparser.add_argument( + "--bottom_edge", action="store", + type=ArgparseEdgeType("eEš"), choices=list("eEš"), + default="š", help="edge type for bottom edge") + + # Add non default cli params if needed (see argparse std lib) + self.argparser.add_argument( + "--canDiameter", action="store", type=float, default=75, + help="outer diameter of the cans to be stored (in mm)") + self.argparser.add_argument( + "--canHight", action="store", type=float, default=110, + help="hight of the cans to be stored (in mm)") + self.argparser.add_argument( + "--canNum", action="store", type=int, default=12, + help="number of cans to be stored") + self.argparser.add_argument( + "--chuteAngle", action="store", type=float, default=5.0, + help="slope angle of the chutes") + + def DrawPusher(self, dbg = False): + with self.saved_context(): + if dbg == False: + self.moveTo(0,self.thickness) + self.edge(0.25*self.pusherA) + self.corner(-90) + self.edge(self.thickness) + self.corner(90) + self.edge(0.5*self.pusherA) + self.corner(90) + self.edge(self.thickness) + self.corner(-90) + self.edge(0.25*self.pusherA) + + self.corner(90-self.chuteAngle) + + self.edge(0.25*self.pusherB) + self.corner(-90) + self.edge(self.thickness) + self.corner(90) + self.edge(0.5*self.pusherB) + self.corner(90) + self.edge(self.thickness) + self.corner(-90) + self.edge(0.25*self.pusherB) + + self.corner(90+self.pusherAngle+self.chuteAngle) + self.edge(self.pusherC) + + def cb_top_chute(self, nr): + if nr == 0: + # fill with holes + border = [ + (0, 0), + (self.top_chute_depth, 0), + (self.top_chute_depth, 0.2 * self.width - self.thickness), + (self.top_chute_depth - self.thickness, 0.2 * self.width), + (self.top_chute_depth - 1.5*self.canDiameter, 0.2 * self.width), + (self.top_chute_depth - 1.5*self.canDiameter, 0.8 * self.width), + (self.top_chute_depth - self.thickness, 0.8 * self.width), + (self.top_chute_depth, 0.8 * self.width + self.thickness), + (self.top_chute_depth, self.width), + (0, self.width), + ] + + if self.fillHoles_fill_pattern != "no fill": + self.fillHoles( + pattern="hbar", + border=border, + max_radius = min(2*self.thickness, self.fillHoles_hole_max_radius) if self.fillHoles_fill_pattern in ["hbar", "vbar"] else min(2*self.thickness, self.width/30), + hspace=min(2*self.thickness, self.fillHoles_space_between_holes) if self.fillHoles_fill_pattern in ["hbar", "vbar"] else min(2*self.thickness, self.width/20), + bspace=min(2*self.thickness, self.fillHoles_space_to_border) if self.fillHoles_fill_pattern in ["hbar", "vbar"] else min(2*self.thickness, self.width/20), + bar_length=self.fillHoles_bar_length, + max_random=self.fillHoles_max_random, + ) + + def cb_top(self, nr): + if nr == 0: + # fill with holes + border = [ + (0, 0), + (self.depth, 0), + (self.depth, self.width), + (0, self.width), + ] + + if self.fillHoles_fill_pattern != "no fill": + self.fillHoles( + pattern="hbar", + border=border, + max_radius = min(2*self.thickness, self.fillHoles_hole_max_radius) if self.fillHoles_fill_pattern in ["hbar", "vbar"] else min(2*self.thickness, self.width/30), + hspace=min(2*self.thickness, self.fillHoles_space_between_holes) if self.fillHoles_fill_pattern in ["hbar", "vbar"] else min(2*self.thickness, self.width/20), + bspace=min(2*self.thickness, self.fillHoles_space_to_border) if self.fillHoles_fill_pattern in ["hbar", "vbar"] else min(2*self.thickness, self.width/20), + bar_length=self.fillHoles_bar_length, + max_random=self.fillHoles_max_random, + ) + + def cb_bottom_chute(self, nr): + if nr == 1: + # holes for pusher + self.rectangularHole(self.width*0.85-0.5*self.thickness, 0.25*self.pusherA, self.thickness, 0.5*self.pusherA, center_x=False, center_y=False) + self.rectangularHole(self.width*0.5 -0.5*self.thickness, 0.25*self.pusherA, self.thickness, 0.5*self.pusherA, center_x=False, center_y=False) + self.rectangularHole(self.width*0.15-0.5*self.thickness, 0.25*self.pusherA, self.thickness, 0.5*self.pusherA, center_x=False, center_y=False) + + + def cb_back(self, nr): + if nr == 1: + # holes for pusher + self.rectangularHole(self.width*0.85-0.5*self.thickness, self.thickness + self.depth * math.tan(math.radians(self.chuteAngle)) + 0.25*self.pusherB, self.thickness, 0.5*self.pusherB + self.thickness, center_x=False, center_y=False) + self.rectangularHole(self.width*0.5 -0.5*self.thickness, self.thickness + self.depth * math.tan(math.radians(self.chuteAngle)) + 0.25*self.pusherB, self.thickness, 0.5*self.pusherB + self.thickness, center_x=False, center_y=False) + self.rectangularHole(self.width*0.15-0.5*self.thickness, self.thickness + self.depth * math.tan(math.radians(self.chuteAngle)) + 0.25*self.pusherB, self.thickness, 0.5*self.pusherB + self.thickness, center_x=False, center_y=False) + + + def cb_sides(self, nr): + if nr == 0: + # for debugging only + if self.debug: + # draw orientation points + self.hole(0, 0, 1, color=Color.ANNOTATIONS) + self.hole(0, self.thickness, 1, color=Color.ANNOTATIONS) + self.hole(0, self.thickness + self.canDiameter, 1, color=Color.ANNOTATIONS) + self.hole(0, self.thickness + self.canDiameter + self.bottom_chute_height, 1, color=Color.ANNOTATIONS) + self.hole(0, self.thickness + self.canDiameter + self.bottom_chute_height + self.top_chute_height + self.thickness, 1, color=Color.ANNOTATIONS) + self.hole(0, self.thickness + self.canDiameter + self.bottom_chute_height + self.top_chute_height + self.thickness + self.canDiameter, 1, color=Color.ANNOTATIONS) + self.hole(0, self.thickness + self.canDiameter + self.bottom_chute_height + self.top_chute_height + self.thickness + self.canDiameter + 1.0 * self.thickness, 1, color=Color.ANNOTATIONS) + with self.saved_context(): + # draw cans, bottom row + self.moveTo(0, self.thickness, self.chuteAngle) + self.rectangularHole(2*self.thickness, 0, math.ceil(self.canNum / 2) * self.canDiameter, self.canDiameter, center_x=False, center_y=False, color=Color.ANNOTATIONS) + for i in range(math.ceil(self.canNum / 2)-1): + self.hole(2*self.thickness+(0.5 + i) * self.canDiameter, self.canDiameter / 2, self.canDiameter / 2, color=Color.ANNOTATIONS) + i+=1 + self.hole(2*self.thickness+(0.5 + i) * self.canDiameter, self.canDiameter*0.8 , self.canDiameter / 2, color=Color.ANNOTATIONS) + + with self.saved_context(): + # draw pusher + self.moveTo(self.depth-self.pusherA, self.thickness + (self.depth-self.pusherA) * math.tan(math.radians(self.chuteAngle))) + self.moveTo(0,0,self.chuteAngle) + self.DrawPusher(True) + + with self.saved_context(): + # draw cans, top row + self.moveTo(0, self.thickness + self.canDiameter + self.bottom_chute_height + self.top_chute_height + 0.5 * self.thickness, -self.chuteAngle) + self.rectangularHole(0, 0.5 * self.thickness, math.ceil(self.canNum / 2) * self.canDiameter, self.canDiameter, center_x=False, center_y=False, color=Color.ANNOTATIONS) + for i in range(math.ceil(self.canNum / 2)): + self.hole((0.5 + i) * self.canDiameter, self.canDiameter / 2 + 0.5 * self.thickness, self.canDiameter / 2, color=Color.ANNOTATIONS) + with self.saved_context(): + # draw barrier + self.moveTo(1.5 * self.thickness, 1.1 * self.thickness + self.burn + math.sin(math.radians(self.chuteAngle)) * 2 * self.thickness, 90) + self.rectangularHole(0, 0, self.barrier_height, self.thickness, center_x=False, center_y=True, color=Color.ANNOTATIONS) + + # bottom chute + with self.saved_context(): + self.moveTo(0, 0.5 * self.thickness, self.chuteAngle) + self.fingerHolesAt(0, 0, self.depth / math.cos(math.radians(self.chuteAngle)), 0) + # top chute + with self.saved_context(): + self.moveTo(0, self.thickness + self.canDiameter + self.bottom_chute_height + self.top_chute_height + 0.5 * self.thickness, -self.chuteAngle) + self.fingerHolesAt(0, 0, self.top_chute_depth, 0) + # front barrier + with self.saved_context(): + self.moveTo(1.5 * self.thickness, 1.1 * self.thickness + self.burn + math.sin(math.radians(self.chuteAngle)) * 2 * self.thickness, 90) + self.fingerHolesAt(0, 0, self.barrier_height, 0) + # fill with holes + border = [ + (2*self.thickness, 0.5*self.thickness + 2*self.thickness * math.tan(math.radians(self.chuteAngle)) + 0.5*self.thickness/math.cos(math.radians(self.chuteAngle))), + (self.depth, self.thickness + self.depth * math.tan(math.radians(self.chuteAngle))), + (self.depth, self.height), + (self.thickness + 0.75 * self.canDiameter, self.height), + (self.thickness + 0.75 * self.canDiameter, 0.5*self.thickness + self.canDiameter + self.bottom_chute_height + self.top_chute_height + self.thickness - (self.thickness + 0.75 * self.canDiameter) * math.tan(math.radians(self.chuteAngle)) + 0.5*self.thickness/math.cos(math.radians(self.chuteAngle))), + (self.top_chute_depth * math.cos(math.radians(self.chuteAngle)), self.thickness + self.canDiameter + self.bottom_chute_height + self.top_chute_height + self.thickness - (self.top_chute_depth) * math.sin(math.radians(self.chuteAngle))), + (self.top_chute_depth * math.cos(math.radians(self.chuteAngle)), self.thickness + self.canDiameter + self.bottom_chute_height + self.top_chute_height - (self.top_chute_depth) * math.sin(math.radians(self.chuteAngle))), + (self.thickness + 0.75 * self.canDiameter, 1.5*self.thickness + self.canDiameter + self.bottom_chute_height + self.top_chute_height - (self.thickness + 0.75 * self.canDiameter) * math.tan(math.radians(self.chuteAngle)) - 0.5*self.thickness/math.cos(math.radians(self.chuteAngle))), + (self.thickness + 0.75 * self.canDiameter, 2*self.thickness + self.barrier_height ), + (2*self.thickness, 2*self.thickness + self.barrier_height), + ] + + self.fillHoles( + pattern=self.fillHoles_fill_pattern, + border=border, + max_radius=self.fillHoles_hole_max_radius, + hspace=self.fillHoles_space_between_holes, + bspace=self.fillHoles_space_to_border, + min_radius=self.fillHoles_hole_min_radius, + style=self.fillHoles_hole_style, + bar_length=self.fillHoles_bar_length, + max_random=self.fillHoles_max_random, + ) + + def render(self): + self.chuteAngle = self.chuteAngle + + self.pusherAngle = 30 # angle of pusher + self.pusherA = 0.75 * self.canDiameter # length of pusher + self.pusherB = self.pusherA / math.sin(math.radians(180 - (90+self.chuteAngle) - self.pusherAngle)) * math.sin(math.radians(self.pusherAngle)) + self.pusherC = self.pusherA / math.sin(math.radians(180 - (90+self.chuteAngle) - self.pusherAngle)) * math.sin(math.radians(90+self.chuteAngle)) + + self.addPart(FrontEdge(self, self)) + self.addPart(TopChuteEdge(self, self)) + self.addPart(BarrierEdge(self, self)) + + if self.canDiameter < 8 * self.thickness: + self.edges["f"].settings.setValues(self.thickness, True, finger=1.0) + self.edges["f"].settings.setValues(self.thickness, True, space=1.0) + self.edges["f"].settings.setValues(self.thickness, True, surroundingspaces=0.0) + + if self.canDiameter < 4 * self.thickness: + raise ValueError("Can diameter has to be at least 4 times the material thickness!") + + if self.canNum < 4: + raise ValueError("4 cans is the minimum!") + + self.depth = self.canDiameter * (math.ceil(self.canNum / 2) + 0.1) + self.thickness + self.top_chute_height = max(self.depth * math.sin(math.radians(self.chuteAngle)), 0.1 * self.canDiameter) + self.top_chute_depth = (self.depth - 1.1 * self.canDiameter) / math.cos(math.radians(self.chuteAngle)) + self.bottom_chute_height = max((self.depth - 1.1 * self.canDiameter) * math.sin(math.radians(self.chuteAngle)), 0.1 * self.canDiameter) + self.bottom_chute_depth = self.depth / math.cos(math.radians(self.chuteAngle)) + self.barrier_height = 0.25 * self.canDiameter + + if (self.top_chute_depth + self.bottom_chute_height - self.thickness) < (self.barrier_height + self.canDiameter * 0.1): + self.bottom_chute_height = self.barrier_height + self.canDiameter * 0.1 + self.thickness - self.top_chute_depth + + self.height = self.thickness + self.canDiameter + self.bottom_chute_height + self.top_chute_height + 0.5 * self.thickness + self.canDiameter + 1.5 * self.thickness # measurements from bottom to top + self.width = 0.01 * self.canHight + self.canHight + 0.01 * self.canHight + edgs = self.bottom_edge + "h" + self.top_edge + "a" + + # render your parts here + self.rectangularWall(self.depth, self.height, edges=edgs, callback=self.cb_sides, move="up", label="right") + self.rectangularWall(self.depth, self.height, edges=edgs, callback=self.cb_sides, move="up mirror", label="left") + + self.rectangularWall(self.bottom_chute_depth, self.width, "fefe", callback=self.cb_bottom_chute, move="up", label="bottom chute") + self.rectangularWall(self.top_chute_depth, self.width, "fbfe", callback=self.cb_top_chute, move="up", label="top chute") + + self.rectangularWall(self.barrier_height, self.width, "fAfe", move="right", label="barrier") + self.rectangularWall(self.height, self.width, "fefe", callback=self.cb_back, move="up", label="back") + self.rectangularWall(self.barrier_height, self.width, "fefe", move="left only", label="invisible") + + if self.top_edge != "e": + self.rectangularWall(self.depth, self.width, "fefe", callback=self.cb_top, move="up", label="top") + + pusherH = self.pusherB * math.cos(math.radians(self.chuteAngle)) + self.thickness + pusherV = self.pusherC * math.cos(math.radians(self.chuteAngle)) + self.thickness + + self.move(pusherV, pusherH, where ="right", before=True, label="Pusher") + self.DrawPusher() + self.move(pusherV, pusherH, where ="right", before=False, label="Pusher") + self.move(pusherV, pusherH, where ="right", before=True, label="Pusher") + self.DrawPusher() + self.move(pusherV, pusherH, where ="right", before=False, label="Pusher") + self.move(pusherV, pusherH, where ="up", before=True, label="Pusher") + self.DrawPusher() + self.text("Glue the Pusher pieces into slots on bottom\nand back plates to prevent stuck cans.", pusherV+3,0, fontsize=4, color=Color.ANNOTATIONS) + self.move(pusherV, pusherH, where ="up", before=False, label="Pusher") + + self.move(pusherV, pusherH, where ="left only", before=True, label="Pusher") + self.move(pusherV, pusherH, where ="left only", before=True, label="Pusher") + + if self.bottom_edge == "š": + self.rectangularWall(self.edges["š"].settings.width+3*self.thickness, self.edges["š"].settings.height-4*self.burn, "eeee", move="right", label="Stabilizer 1") + self.rectangularWall(self.edges["š"].settings.width+3*self.thickness, self.edges["š"].settings.height-4*self.burn, "eeee", move="right", label="Stabilizer 2") + self.rectangularWall(self.edges["š"].settings.width+5*self.thickness, self.edges["š"].settings.height-4*self.burn, "eeee", move="right", label="Stabilizer 3") + self.rectangularWall(self.edges["š"].settings.width+5*self.thickness, self.edges["š"].settings.height-4*self.burn, "eeee", move="right", label="Stabilizer 4") + self.text("Glue a stabilizer on the inside of each bottom\nside stacking foot for lateral stabilization.",3 ,0 , fontsize=4, color=Color.ANNOTATIONS) + diff --git a/static/samples/CanStorage-thumb.jpg b/static/samples/CanStorage-thumb.jpg new file mode 100644 index 0000000..866e1fa Binary files /dev/null and b/static/samples/CanStorage-thumb.jpg differ diff --git a/static/samples/CanStorage.jpg b/static/samples/CanStorage.jpg new file mode 100644 index 0000000..7b41f77 Binary files /dev/null and b/static/samples/CanStorage.jpg differ diff --git a/static/samples/CanStorageAA-thumb.jpg b/static/samples/CanStorageAA-thumb.jpg new file mode 100644 index 0000000..2036e49 Binary files /dev/null and b/static/samples/CanStorageAA-thumb.jpg differ diff --git a/static/samples/CanStorageAA.jpg b/static/samples/CanStorageAA.jpg new file mode 100644 index 0000000..3252e1f Binary files /dev/null and b/static/samples/CanStorageAA.jpg differ diff --git a/static/samples/samples.sha256 b/static/samples/samples.sha256 index 1dd3415..a1c7b5f 100644 --- a/static/samples/samples.sha256 +++ b/static/samples/samples.sha256 @@ -98,3 +98,5 @@ b843c04a6ec530afaf40c664479c6f1eeadc9d48f1ff7ca99bea3575da733144 ../static/samp 7ab0da6aa8aa87e72e803bbdc18653622d9f1fb5dd0fe826dbf085bb338e28f6 ../static/samples/RegularStarBox-2.jpg 6cfaf2db37c9d9298e8db9fae0d33a794465d1bdd26ca2fef1eec8ba46cad81c ../static/samples/CardHolder.jpg 002232597490aa8521136592c35b5138f247076adadef6c3fc7a85178e0bfddc ../static/samples/DiceBox.jpg +18324deb519ed78d489da05727fd72ba7077241005c1cfa59da2b5ccb5a5a9a7 ../static/samples/CanStorageAA.jpg +e0906c7ad1f361232cc7c12caeb581bb42f191ac90a89038aca06adb7b4bb845 ../static/samples/CanStorage.jpg