Skip to content

Commit 4d0034d

Browse files
committed
Add new ApothecaryDrawerBox generator
New generator for Apothecary Drawer Box and optional drawers using existing generators. New custom edge added for alternating finger joints to save on material costs.
1 parent 05d0c80 commit 4d0034d

File tree

5 files changed

+1019
-0
lines changed

5 files changed

+1019
-0
lines changed
Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
from boxes import *
2+
from boxes.edges import FingerJointEdge
3+
4+
# Dependent generators for drawers
5+
from boxes.generators.abox import ABox
6+
from boxes.generators.dividertray import DividerTray
7+
8+
class AlternatingFingerJointEdgeEven(FingerJointEdge):
9+
"""Alternating finger joint edge """
10+
char = 'a'
11+
description = "Alternating Finger Joint"
12+
positive = True
13+
14+
# Pasted from FingerJointEdge but with added alternate args and rendering
15+
def __call__(self, length, bedBolts=None, bedBoltSettings=None, alternate=True, altMod=0, **kw):
16+
positive = self.positive
17+
t = self.settings.thickness
18+
19+
s, f = self.settings.space, self.settings.finger
20+
thickness = self.settings.thickness
21+
style = self.settings.style
22+
play = self.settings.play
23+
24+
fingers, leftover = self.calcFingers(length, bedBolts)
25+
26+
if (fingers == 0 and f and
27+
leftover > 0.75 * thickness and leftover > 4 * play):
28+
fingers = 1
29+
f = leftover = leftover / 2.0
30+
bedBolts = None
31+
style = "rectangular"
32+
33+
if not positive:
34+
f += play
35+
s -= play
36+
leftover -= play
37+
38+
self.edge(leftover / 2.0, tabs=1)
39+
40+
l1, l2 = self.fingerLength(self.settings.angle)
41+
h = l1 - l2
42+
43+
d = (bedBoltSettings or self.bedBoltSettings)[0]
44+
45+
for i in range(fingers):
46+
if i != 0:
47+
if not positive and bedBolts and bedBolts.drawBolt(i):
48+
self.hole(0.5 * s,
49+
0.5 * self.settings.thickness, 0.5 * d)
50+
51+
if positive and bedBolts and bedBolts.drawBolt(i):
52+
self.bedBoltHole(s, bedBoltSettings)
53+
else:
54+
self.edge(s)
55+
56+
# Skip finger if alternating
57+
if alternate and i % 2 == altMod:
58+
self.edge(f)
59+
else:
60+
self.draw_finger(f, h, style,
61+
positive, i < fingers // 2)
62+
63+
self.edge(leftover / 2.0, tabs=1)
64+
65+
class AlternatingFingerJointEdgeOdd(AlternatingFingerJointEdgeEven):
66+
"""Alternating finger joint edge """
67+
char = 'b'
68+
description = "Alternating Finger Joint"
69+
positive = True
70+
71+
def __call__(self, length, bedBolts=None, bedBoltSettings=None, alternate=True, altMod=1, **kw):
72+
fingers, leftover = self.calcFingers(length, bedBolts)
73+
# Handles odd number of fingers
74+
if fingers % 2 == 0:
75+
altMod = 0
76+
77+
super().__call__(length, bedBolts, bedBoltSettings, alternate=alternate, altMod=altMod, **kw)
78+
79+
class DrawerSettings(edges.Settings):
80+
"""Settings for the Drawers
81+
Values:
82+
* absolute
83+
84+
* style : "ABox" : generator to use for drawers
85+
* notched : False : notches in drawer (DividerTray only)
86+
* bottom_edge : "F" : bottom edge for drawer (ABox only)
87+
88+
* relative (in multiples of thickness)
89+
90+
* depth_reduction : 0.0 : drawer depth reduction for adding stop block inside
91+
* tolerance : 0.5 : tolerance for drawer fit
92+
* num_dividers : 5 : number of dividers in each drawer (DividerTray only)
93+
"""
94+
absolute_params = {
95+
"style": ("none", "ABox", "DividerTray"),
96+
"notched": False,
97+
"bottom_edge": ("F", "e"),
98+
}
99+
100+
relative_params = {
101+
"depth_reduction": 0.0,
102+
"tolerance": 0.5,
103+
"num_dividers": 5,
104+
}
105+
106+
class ApothecaryDrawerBox(Boxes):
107+
"""Apothecary style sliding drawer box"""
108+
109+
ui_group = "Box"
110+
description = """## Apothecary style sliding drawer box
111+
Apothecary style sliding drawer box that uses alternating finger joints
112+
for inner shelves to save on materials.
113+
114+
It leverages existing generators to create drawers, or you can
115+
generate your own to suit your specific use case.
116+
117+
Default settings fit in a Kallax cube giving you 12 drawers. These drawers
118+
can each fit most popular TCG cards.
119+
"""
120+
121+
def __init__(self) -> None:
122+
Boxes.__init__(self)
123+
124+
self.addSettingsArgs(edges.FingerJointSettings, finger=2.0, space=2.0)
125+
126+
self.addSettingsArgs(DrawerSettings, style="none", notched=False,
127+
depth_reduction=0.0, tolerance=0.5)
128+
129+
self.buildArgParser(x=334, y=374, h=334, outside=True)
130+
131+
self.argparser.add_argument(
132+
"--rows", action="store", type=int, default=3,
133+
help="number of rows")
134+
135+
self.argparser.add_argument(
136+
"--cols", action="store", type=int, default=4,
137+
help="number of columns")
138+
139+
self.argparser.add_argument(
140+
"--generate_drawers", action="store", type=BoolArg(), default=True,
141+
help="generate drawers using DividerTray")
142+
143+
def render(self):
144+
x, y, h = self.x, self.y, self.h
145+
rows = self.rows
146+
cols = self.cols
147+
t = self.thickness
148+
149+
if self.outside:
150+
self.x = x = self.adjustSize(x, "f", "f")
151+
self.y = y = self.adjustSize(y, "e")
152+
self.h = h = self.adjustSize(h, "f", "f")
153+
154+
self.unit_w, self.unit_h = unit_w, unit_h = self.unit_dimensions()
155+
156+
drawer_settings = self.edgesettings['Drawer']
157+
drawer_style = drawer_settings['style']
158+
159+
# Add alternating finger joint edges
160+
altEven = AlternatingFingerJointEdgeEven(self, self.edges["f"].settings)
161+
self.addPart(altEven)
162+
altOdd = AlternatingFingerJointEdgeOdd(self, self.edges["f"].settings)
163+
self.addPart(altOdd)
164+
165+
# Back panel
166+
self.rectangularWall(x, h, "ffff", label="back", callback=[self.back_panel_slot_holes_callback], move="up")
167+
168+
# Top/Bottom
169+
with self.saved_context():
170+
for i in range(2):
171+
self.rectangularWall(x, y, "eFFF", label="top/bottom", callback=[self.vertical_slot_holes_callback], move="right")
172+
173+
self.rectangularWall(x, y, "ffff", move="up only")
174+
175+
# Left/Right walls
176+
with self.saved_context():
177+
for i in range(2):
178+
self.rectangularWall(y, h, "fFfe", label="left/right", callback=[self.horizontal_slot_holes_callback], move="right")
179+
180+
self.rectangularWall(x, h, "ffff", move="up only")
181+
182+
# Inner walls
183+
num_inner_walls = cols - 1
184+
with self.saved_context():
185+
for i in range(num_inner_walls):
186+
self.rectangularWall(y, h, "fffe", label="divider", callback=[self.horizontal_slot_holes_callback], move="right")
187+
188+
self.rectangularWall(x, h, "ffff", move="up only")
189+
190+
# Shelves
191+
num_edge_shelves = 0 if cols < 2 else rows -1
192+
num_inner_shelves = (rows - 1) * (cols - 2)
193+
num_single_col_shelves = 0 if cols > 1 else rows - 1
194+
shelf_width = unit_w + (self.thickness * 2)
195+
196+
with self.saved_context():
197+
for i in range(num_edge_shelves):
198+
self.rectangularWall(unit_w, y, "ebff", label="left shelf", move="right")
199+
self.rectangularWall(unit_w, y, "effa", label="right shelf", move="right")
200+
201+
for i in range(num_inner_shelves):
202+
self.rectangularWall(unit_w, y, "ebfa", label="inner shelf", move="right")
203+
204+
for i in range(num_single_col_shelves):
205+
self.rectangularWall(unit_w, y, "efff", label="shelf", move="right")
206+
207+
self.rectangularWall(unit_w, y, "efff", move="up only")
208+
209+
# Drawers
210+
if drawer_style != "none":
211+
num_drawers = rows * cols
212+
213+
if self.labels:
214+
self.text(f"{num_drawers} sets of generated drawers using {drawer_style} generator --^", fontsize=6, color=Color.ANNOTATIONS)
215+
self.moveTo(0, 10)
216+
217+
# Use existing generators to create drawers
218+
for i in range(num_drawers):
219+
with self.saved_context():
220+
drawer_gen, render_width = self.drawerGenerator(drawer_style, drawer_settings, labels=self.labels, outside=self.outside)
221+
drawer_gen._buildObjects()
222+
drawer_gen.render()
223+
224+
# Reset positioning for rendering next iteration
225+
self.moveTo(render_width + 5, 0)
226+
227+
def back_panel_slot_holes_callback(self):
228+
self.vertical_slot_holes_callback(self.h)
229+
230+
for col in range(self.cols):
231+
posx = col * (self.unit_w + self.thickness + (self.burn * 2))
232+
for row in range(self.rows - 1):
233+
posy = ((row + 1) * self.unit_h) + (self.thickness / 2) + (self.thickness * row)
234+
self.fingerHolesAt(posx, posy, self.unit_w, angle=0)
235+
236+
def vertical_slot_holes_callback(self, length=None):
237+
length = length or self.y
238+
for col in range(1, self.cols):
239+
posx = 0.5 * self.thickness + col * self.unit_w + (col - 1) * self.thickness
240+
self.fingerHolesAt(posx, 0, length, angle=90)
241+
242+
def horizontal_slot_holes_callback(self):
243+
for row in range(1, self.rows):
244+
posy = 0.5 * self.thickness + row * self.unit_h + (row - 1) * self.thickness
245+
self.fingerHolesAt(0, posy, self.y, angle=0)
246+
247+
def unit_dimensions(self):
248+
total_inner_width = self.x - (self.thickness * (self.cols - 1))
249+
total_inner_height = self.h - (self.thickness * (self.rows - 1))
250+
unit_w = total_inner_width / self.cols
251+
unit_h = total_inner_height / self.rows
252+
return unit_w, unit_h
253+
254+
def drawerGenerator(self, drawer_style, drawer_settings, labels, outside):
255+
"""Return a generator object based on the style name"""
256+
257+
drawer_width = self.unit_w
258+
drawer_height = self.unit_h
259+
drawer_depth = self.y
260+
261+
if drawer_settings['tolerance'] > 0:
262+
drawer_width -= drawer_settings['tolerance']
263+
drawer_height -= drawer_settings['tolerance']
264+
drawer_depth -= drawer_settings['tolerance']
265+
266+
if drawer_settings['depth_reduction'] > 0:
267+
drawer_depth -= drawer_settings['depth_reduction']
268+
269+
if drawer_style == "ABox":
270+
bottom_edge = drawer_settings['bottom_edge'] or "F"
271+
args = [
272+
f"--x={drawer_width}",
273+
f"--y={drawer_depth}",
274+
f"--h={drawer_height}",
275+
f"--bottom_edge={bottom_edge}",
276+
]
277+
278+
gen = ABox()
279+
gen.parseArgs(args)
280+
render_width = drawer_width + drawer_depth
281+
282+
elif drawer_style == "DividerTray":
283+
num_dividers = drawer_settings['num_dividers'] + 1
284+
div_depth = drawer_depth / num_dividers
285+
286+
args = [
287+
f"--sx={drawer_width}*1",
288+
f"--sy={div_depth}*{num_dividers}",
289+
f"--h={drawer_height}",
290+
f"--bottom=True"
291+
]
292+
293+
if drawer_settings['notched'] == False:
294+
args.append(f"--Notch_depth=0")
295+
args.append(f"--Notch_lower_radius=0")
296+
args.append(f"--Notch_upper_radius=0")
297+
298+
gen = DividerTray()
299+
gen.parseArgs(args)
300+
render_width = max(drawer_width, drawer_depth) + drawer_width
301+
302+
else:
303+
raise ValueError(f"Invalid generator: {drawer_style}")
304+
305+
gen.thickness = self.thickness
306+
gen.ctx = self.ctx
307+
gen.outside = outside
308+
gen.labels = labels
309+
gen.label_format = self.label_format
310+
gen.spacing = self.spacing
311+
gen.edgesettings = self.edgesettings
312+
gen.bedBoltSettings = self.bedBoltSettings
313+
gen.hexHolesSettings = self.hexHolesSettings
314+
315+
return gen, render_width

0 commit comments

Comments
 (0)