# Copyright (C) 2024 Oliver Jensen
#
# 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 *
[docs]
class FlexBook(Boxes):
"""Box with living hinge styled after a book."""
ui_group = "FlexBox"
description = """
If you have an enclosure, arrange the living hinge to be as close to your extractor fan as possible.
"""
def __init__(self) -> None:
Boxes.__init__(self)
self.addSettingsArgs(edges.FingerJointSettings)
self.addSettingsArgs(edges.FlexSettings)
self.buildArgParser(x=75.0, y=35.0, h=125.0)
self.argparser.add_argument(
"--latchsize", action="store", type=float, default=8,
help="size of latch in multiples of thickness")
self.argparser.add_argument(
"--recess_wall", action="store", type=boolarg, default=False,
help="Whether to recess the inner wall for easier object removal")
def flexBookSide(self, h, x, r, callback=None, move=None):
t = self.thickness
tw, th = h+t, x+2*t+r
if self.move(tw, th, move, True):
return
self.fingerHolesAt(0, x+1.5*t, h, 0)
self.edges["F"](h)
self.corner(90, 0)
self.edges["e"](t)
self.edges["f"](x + t)
self.corner(180, r)
self.edges["e"](x + 2*t)
self.corner(90)
self.move(tw, th, move)
def flexBookRecessedWall(self, h, y, include_recess, callback=None, move=None):
t = self.thickness
tw, th = h, y+2*t
if self.move(tw, th, move, True):
return
# TODO: figure out math for gentler angles
cutout_radius = min(h/4, y/8)
cutout_angle = 90
cutout_predist = y * 0.2
cutout_angle_dist = h/2 - 2 * cutout_radius
cutout_base_dist = y - (y * .4) - 4 * cutout_radius
self.moveTo(0, t)
self.edges["f"](h)
self.corner(90,)
self.edges["e"](y)
self.corner(90)
self.edges["f"](h)
self.corner(90)
if include_recess:
self.polyline(
cutout_predist,
(cutout_angle, cutout_radius),
cutout_angle_dist,
(-cutout_angle, cutout_radius),
cutout_base_dist,
(-cutout_angle, cutout_radius),
cutout_angle_dist,
(cutout_angle, cutout_radius),
cutout_predist)
else :
self.edges["e"](y)
self.corner(90)
self.move(tw, th, move)
def flexBookLatchWall(self, h, y, latchSize, callback=None, move=None):
t = self.thickness
if self.recess_wall:
x_adjust = 0
else:
x_adjust = 3 * t
tw, th = h+t+x_adjust, y+2*t
if self.move(tw, th, move, True):
return
self.moveTo(x_adjust, t)
self.edges["f"](h)
self.corner(90)
self.edges["f"](y)
self.corner(90)
self.edges["f"](h)
self.corner(90)
self.rectangularHole(y/2, -1.5*t, latchSize - 2*t, t)
self.polyline(
(y-latchSize) / 2,
-90,
2.5*t,
(90, t/2),
latchSize - t,
(90, t/2),
2.5*t,
-90,
(y-latchSize) / 2,
90
)
self.move(tw, th, move)
def flexBookCover(self, move=None):
x, y = self.x, self.y
latchSize = self.latchsize
c4 = self.c4
t = self.thickness
tw = 2*x + 6*t + 2*c4 + t
th = y + 4*t
if self.move(tw, th, move, True):
return
self.moveTo(2*t, 0)
self.edges["h"](x+t)
self.edges["X"](2*c4 + t, y + 4*t) # extra thickness here to make it fit
self.edges["e"](x+t)
self.corner(90, 2*t)
self.edges["e"](y/2)
self.rectangularHole(0, 1.5*t, latchSize, t)
self.rectangularHole((latchSize+7*t)/2, 3.5*t, t, t)
self.rectangularHole(-(latchSize+7*t)/2, 3.5*t, t, t)
self.edges["e"](y/2)
self.corner(90, 2*t)
self.edges["e"](x+t + 2*c4 + t) # corresponding extra thickness
self.edges["h"](x+t)
self.corner(90, 2*t)
self.edges["h"](y)
self.corner(90, 2*t)
if False:
# debug lines
self.moveTo(0, 2*t)
self.edges["e"](x+t + 2*c4 + x+t + t)
self.corner(90)
self.edges["e"](y)
self.corner(90)
self.edges["e"](x+t + 2*c4 + x+t + t)
self.corner(90)
self.edges["e"](y)
self.corner(90)
self.edges["e"](x)
self.corner(90)
self.edges["e"](y)
self.corner(90)
self.edges["e"](x)
self.corner(90)
self.edges["e"](y)
self.corner(90)
self.move(tw, th, move)
def flexBookLatchBracket(self, isCover, move=None):
t = self.thickness
round = t/3
tw, th = 5*t, 5.5*t
if self.move(tw, th, move, True):
return
if isCover:
self.edge(5*t)
else:
self.edge(t)
self.corner(90)
self.edge(2*t - round)
self.corner(-90, round)
self.edge(1.5*t - round)
self.rectangularHole(0, 1.5 * t, t, t)
self.edge(1.5*t - round)
self.corner(-90, round)
self.edge(2*t - round)
self.corner(90)
self.edge(t)
self.corner(90)
self.edge(3*t)
self.corner(180, 2.5 * t)
self.edge(3*t)
if not isCover:
# anchor pin
self.moveTo(-1.5*t, 1.25*t)
self.ctx.stroke()
self.rectangularWall(t, 2*t)
self.move(tw, th, move)
def flexBookLatchPin(self, move=None):
t = self.thickness
l = self.latchsize
tw, th = l + 4*t, 5*t
if self.move(tw, th, move, True):
return
round = t/3
self.moveTo(2*t, 0)
self.polyline(
l,
90,
2*t,
-90,
2*t - round,
(90, round),
2*t - 2*round,
(90, round),
3*t - round,
-90,
t - round,
(90, round),
l - 2*t - 2*round,
(90, round),
t - round,
-90,
3*t - round,
(90, round),
2*t - 2*round,
(90, round),
2*t - round,
-90,
2*t,
90)
self.move(tw, th, move)
def render(self):
# I found it easier to conceptualize swapping y and h
y = self.h
self.h = self.y
self.y = y
t = self.thickness
self.radius = self.h / 2
self.c4 = c4 = math.pi * self.radius * 0.5
self.latchsize *= self.thickness
self.flexBookCover(move="up")
self.flexBookRecessedWall(self.h, self.y, self.recess_wall, move="mirror right")
self.flexBookLatchWall(self.h, self.y, self.latchsize, move="right")
with self.saved_context():
self.flexBookSide(self.h, self.x, self.radius, move="right")
self.flexBookSide(self.h, self.x, self.radius, move="mirror right")
self.flexBookSide(self.h, self.x, self.radius, move="up only")
with self.saved_context():
self.flexBookLatchBracket(False, move="up")
self.flexBookLatchBracket(False, move="up")
self.flexBookLatchBracket(False, move="right only")
with self.saved_context():
self.flexBookLatchBracket(True, move="up")
self.flexBookLatchBracket(True, move="up")
self.flexBookLatchBracket(False, move="right only")
l = self.latchsize
self.rectangularWall(
4*t, l,
callback=[lambda: self.rectangularHole(2*t, l/2, 2.5*t, .8*l, r=2*t)],
move="right")
self.flexBookLatchPin(move="right")