Skip to content

Commit

Permalink
New generator: WallHopper
Browse files Browse the repository at this point in the history
A hopper for small pieces that can be hanged on a wall.

Co-authored-by: Florian Festi <[email protected]>
  • Loading branch information
AlexCPU and florianfesti authored Dec 12, 2024
1 parent 72e91d3 commit 6b2e560
Show file tree
Hide file tree
Showing 5 changed files with 254 additions and 0 deletions.
153 changes: 153 additions & 0 deletions boxes/generators/wallhopper.py
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.

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")
Loading

0 comments on commit 6b2e560

Please sign in to comment.