diff --git a/boxes/generators/wallhopper.py b/boxes/generators/wallhopper.py new file mode 100644 index 00000000..7c152240 --- /dev/null +++ b/boxes/generators/wallhopper.py @@ -0,0 +1,153 @@ +# Copyright (C) 2024 Alex Roberts +# +# 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 * +from boxes.walledges import _WallMountedBox + +class WallHopper(_WallMountedBox): + """Storage hopper with dispensing tray""" + + description = ''' +####Assembly Notes: +1. The generator produces three pieces with angled finger joints. +Bottom panel, sloped front panel and label panel (if enabled). +2. Joint lengths vary to accommodate the slope angles. +3. Orient pieces as shown in the generated layout to assemble correctly. +''' + def __init__(self) -> None: + super().__init__() + + self.buildArgParser(x=80, h=150) + + self.argparser.add_argument( + "--hopper_depth", action="store", type=float, default=50, + help="Depth of the hopper") + self.argparser.add_argument( + "--dispenser_depth", action="store", type=float, default=45, + help="Depth of the dispenser") + self.argparser.add_argument( + "--dispenser_height", action="store", type=float, default=50, + help="Height of the dispenser") + self.argparser.add_argument( + "--slope_ratio", action="store", type=float, default=0.4, + help="Fraction of the bottom slope of the dispenser") + self.argparser.add_argument( + "--slope_angle", action="store", type=float, default=30, + help="Angle of the bottom slope of the dispenser") + self.argparser.add_argument( + "--label", action="store", type=boolarg, default=True, + help="include a label area on the front") + self.argparser.add_argument( + "--label_ratio", action="store", type=float, default=0.2, + help="Fraction of the label of the dispenser") + + + def render(self): + self.generateWallEdges() # creates the aAbBcCdD| edges + + hd = self.hopper_depth + dd = self.dispenser_depth + dh = self.dispenser_height + sr = self.slope_ratio if self.slope_ratio < 1 else 0.999 + a = self.slope_angle + lr = self.label_ratio if self.label_ratio < 1 else 0.999 + t = self.thickness + + x = self.x + h = self.h + + # Check that sa generates a valid dispenser + minsa = 0 # 0 degrees is a flat front + maxsa = math.degrees(math.atan(dd/(dh*sr))) # equivalent to no flat section on the dispenser + if a < minsa: + a = minsa + elif a > maxsa: + a = maxsa + + # Get the width of the 'h' edge + wh = self.edges["h"].startwidth() + + # Check that ratios are valid + if not self.label: + lr = 0 + if sr + lr >= 1: # Check you haven't put in invalid values + # Scale proportionally to sum to 0.95 + total = sr + lr + sr = (sr / total) * 0.95 # Scale to 95% + lr = (lr / total) * 0.95 # Scale to 95% + + # Calculate angle between label and return to dispenser + b = math.degrees(math.atan(dd/((1-(lr+sr))*dh))) + + # Dispenser flat is dispenser depth minus the slope + df = dd - dh*sr*math.tan(math.radians(a)) + + # Calculate the length of the slope + sl = dh*sr/math.cos(math.radians(a)) + + # calculate the length of the top slope + tl = (dd**2 + ((1-(lr+sr))*dh)**2)**0.5 + + # Configure angled finger joints for the sloped sections + # First set: For bottom-to-slope connection + angledsettings = copy.deepcopy(self.edges["f"].settings) + angledsettings.setValues(self.thickness, True, angle=90-a) + angledsettings.edgeObjects(self, chars="gG") + + # Second set: For slope-to-label connection + angledsettings = copy.deepcopy(self.edges["f"].settings) + angledsettings.setValues(self.thickness, True, angle=a) + angledsettings.edgeObjects(self, chars="kK") + + + with self.saved_context(): + # Bottom panel with finger joints + self.rectangularWall(x, hd+df, "ffGf", label="bottom", move="up") + + if self.label: + # Sloped front with label area + self.rectangularWall(x, sl, + "gfkf", label="slope", move="up") + # Label panel + self.rectangularWall(x, dh*lr, + "Kfef", label="label", move="up") + else: + # Sloped front without label + self.rectangularWall(x, sl, + "gfef", label="slope", move="up") + # Back panel with wall mount edges + self.rectangularWall(x, h, "hCec", label="back", move="up") + # Front panel of hopper + self.rectangularWall(x, h-dh, "efef", label="front", move="up") + + # Non drawn spacer to move wall pieces to the right + self.rectangularWall(self.x, 3, "DDDD", label="movement", move="right only") + + + sideEdges = [ + t, 0, # nudge along by thickness + hd+df, (90-a, wh), # hopper depth + dispenser flat, then rotate slope angle with a radius of an 'h' edge + sl, (a, wh), # slope length, then rotate back to vertical with a radius of an 'h' edge + dh*lr, b, # label height, then rotate to the angle between label and dispenser + tl, -b, # top slope length, then rotate back to vertical + h-dh, 90, # Additional hopper height, then rotate to horizontal + hd+wh+t, 90, # Hopper depth + 'h' edge width + thickness, then rotate to vertical + h, 0, # Wall edge to the bottom + wh, 90, # Width of an 'h' edge to close the box + ] + + + self.polygonWall(sideEdges, "ehhhehebe",correct_corners=False, label="left", move="up") + self.polygonWall(sideEdges, "ehhhehebe",correct_corners=False, label="right", move="up mirror") diff --git a/examples/WallHopper.svg b/examples/WallHopper.svg new file mode 100644 index 00000000..8a076727 --- /dev/null +++ b/examples/WallHopper.svg @@ -0,0 +1,100 @@ + + + +WallHopper + + +WallMounted - WallHopper +boxes WallHopper +Storage hopper with dispensing tray + + +####Assembly Notes: +1. The generator produces three pieces with angled finger joints. +Bottom panel, sloped front panel and label panel (if enabled). +2. Joint lengths vary to accommodate the slope angles. +3. Orient pieces as shown in the generated layout to assemble correctly. + + +Created with Boxes.py (https://boxes.hackerspace-bamberg.de/) +Command line: boxes WallHopper +Command line short: boxes WallHopper + + + + + 100.0mm, burn:0.10mm + + + bottom + + + slope + + + label + + + + + + + + + back + + + front + + + left + + + + + + + + + + + + + + + + + + right + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/static/samples/WallHopper-thumb.jpg b/static/samples/WallHopper-thumb.jpg new file mode 100644 index 00000000..303537f6 Binary files /dev/null and b/static/samples/WallHopper-thumb.jpg differ diff --git a/static/samples/WallHopper.jpg b/static/samples/WallHopper.jpg new file mode 100644 index 00000000..f51df41d Binary files /dev/null and b/static/samples/WallHopper.jpg differ diff --git a/static/samples/samples.sha256 b/static/samples/samples.sha256 index 9e229057..07d57d89 100644 --- a/static/samples/samples.sha256 +++ b/static/samples/samples.sha256 @@ -189,4 +189,5 @@ d7f7fd6c1b5a51c4fdfc21a03d55597f04d78383fef2138bc6ade4ee95676bc9 ../static/samp fec237bd76c18f1cad18a888f57299e5ff5033ab8032e7a26ea0b1259a42d150 ../static/samples/CompartmentBox-lid.jpg 5c2127a79948504f629ed792de539022411e428eb46dc8ad9c5286fb397cd603 ../static/samples/CompartmentBox.jpg ca53e3c8b9ba8d46ca2d6fdff24865514dde27380b409fef82f0b87ac0bada2d ../static/samples/HobbyCase.jpg +47910e8cf07e0339437d441b0fe270b05973317462495a7bb122bbcb779b135c ../static/samples/WallHopper.jpg 478769d7f422d0e47d50c7c1476b72a269f9acd03042d2d16dc0c00fa80941f6 ../static/samples/WallStackableBin.jpg