|
| 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