diff --git a/boxes/generators/spicesrack.py b/boxes/generators/spicesrack.py new file mode 100644 index 0000000..5f9ecad --- /dev/null +++ b/boxes/generators/spicesrack.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python3 +# Copyright (C) 2013-2019 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 +# MERCHANTABILITY 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 * + +class FrontEdge(edges.Edge): + + def __call__(self, length, **kw): + with self.saved_context(): + a = 90 + r = (self.diameter +self.space) / 2 + self.ctx.scale(1, self.edge_width/r) + for i in range(self.numx): + self.corner(-a) + self.corner(180, r) + self.corner(-a) + self.moveTo(length) + + def margin(self): + return self.edge_width + +class SpicesRack(Boxes): + """Rack for cans of spices""" + + ui_group = "Shelf" + + def __init__(self): + Boxes.__init__(self) + + self.addSettingsArgs(edges.FingerJointSettings, surroundingspaces=1.0) + + self.argparser.add_argument( + "--diameter", action="store", type=float, default=55., + help="diameter of spice cans") + + self.argparser.add_argument( + "--height", action="store", type=float, default=60., + help="height of the cans that needs to be supported") + self.argparser.add_argument( + "--space", action="store", type=float, default=10., + help="space between cans") + self.argparser.add_argument( + "--numx", action="store", type=int, default=5, + help="number of cans in a row") + self.argparser.add_argument( + "--numy", action="store", type=int, default=6, + help="number of cans in a column") + self.argparser.add_argument( + "--in_place_supports", action="store", type=boolarg, default=False, + help="place supports pieces in holes (check for fit yourself)") + self.argparser.add_argument( + "--feet", action="store", type=boolarg, default=False, + help="add feet so the rack can stand on the ground") + + + def support(self, width, height, move=None): + t = self.thickness + tw = width + t + th = height + + r = min(width - 2*t, height - 2*t) + + if self.move(tw, th, move, True): + return + + self.polyline(width-r, 90, 0, (-90, r), 0, 90, height-r, 90, width, 90) + self.edges["f"](height) + + self.move(tw, th, move) + + def holes(self): + w = 2* self.base_r + r = self.diameter / 2 + a = self.base_angle + l = self.hole_length + self.moveTo(0, self.hole_distance) + + with self.saved_context(): + self.ctx.scale(1, l/self.base_h) + self.moveTo(self.space/2, 0, 90) + for i in range(self.numx): + self.polyline(0, -a, 0, (-180+2*a, r), 0, -90-a, w, -90) + self.moveTo(0, -(self.diameter+self.space)) + self.ctx.stroke() + if self.feet and not self.feet_done: + self.feet_done = True + return + + if not self.in_place_supports: + return + inner_width = self.hole_distance + self.hole_length/3 + t = self.thickness + for i in range(self.numx-1): + with self.saved_context(): + self.moveTo((self.diameter+self.space)*(i+0.5)- (inner_width+t)/2, self.spacing) + self.support(inner_width, (self.h-t)/2) + + def backCB(self): + t = self.thickness + dy = self.h/2 - t/2 + for i in range(self.numy): + self.fingerHolesAt(0, (i+1)*self.h-0.5*self.thickness-dy, self.x, 0) + for j in range(1, self.numx): + self.fingerHolesAt( + j*(self.diameter+self.space), + (i+1)*self.h-t-dy, (self.h-t)/2, -90) + + def render(self): + self.feet_done = False + t = self.thickness + self.x = x = self.numx * (self.diameter+self.space) + d = self.diameter + + self.base_angle = 10 + self.base_r = self.diameter/2 * math.cos(math.radians(self.base_angle)) + self.base_h = self.diameter/2 * (1-math.sin(math.radians(self.base_angle))) + self.angle = math.degrees(math.atan(self.base_r/self.height)) + self.hole_length = (self.base_h**2+self.height**2)**0.5 + self.hole_distance = (self.diameter-self.base_r) * math.sin(math.radians(self.angle)) + print(self.angle) + + self.h = (self.space + d) / math.cos(math.radians(self.angle)) + h = self.numy * self.h - self.h / 2 + 6*t + + width = self.hole_distance + self.hole_length + self.space/2 + inner_width = self.hole_distance + self.hole_length/3 + + self.edge_width = width - inner_width + + for i in range(self.numy): + self.rectangularWall(x, inner_width,[ + "f", "e", FrontEdge(self, self), "e"], + callback=[self.holes], move="up") + + self.rectangularWall(x, h, + callback=[self.backCB, + None, + lambda:self.hole(3*t, 3*t, 1.5), + lambda:self.hole(3*t, 3*t, 1.5), + ], move="up") + + + if not self.in_place_supports: + self.partsMatrix((self.numx-1)*self.numy, self.numx-1, "up", + self.support, inner_width, (self.h-t)/2) + if self.feet: + self.partsMatrix(self.numx-1, self.numx-1, "up", + self.rectangularTriangle, (self.h-t)/2, width, "fee", r=(self.h-t)/4) +