diff --git a/boxes/generators/coinbanksafe.py b/boxes/generators/coinbanksafe.py new file mode 100644 index 00000000..395da379 --- /dev/null +++ b/boxes/generators/coinbanksafe.py @@ -0,0 +1,223 @@ +# 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 . + +from boxes import * + + +class CoinBankSafe(Boxes): + """A piggy-bank designed to look like a safe.""" + + description =''' +Make sure not to discard the circle cutouts from the lid, base, and door. They are all needed. + +![Closed](static/samples/CoinBankSafe-closed.jpg) + +![Open](static/samples/CoinBankSafe-open.jpg) + +Assemble the locking pins like this: wiggle-disc, number-disc, doorhole-disc, spacer-disc, D-disc. +Glue the first three in place, but do not glue the last two. +Leaving them unglued will allow you change the code, and to remove the pin from the door. + +![Pins](static/samples/CoinBankSafe-pins.jpg) + +''' + + ui_group = "Misc" + + def __init__(self) -> None: + Boxes.__init__(self) + self.addSettingsArgs(edges.FingerJointSettings) + self.buildArgParser("x", "y", "h") + self.argparser.add_argument( + "--slotlength", action="store", type=float, default=30, + help="Length of the coin slot in mm") + self.argparser.add_argument( + "--slotwidth", action="store", type=float, default=4, + help="Width of the coin slot in mm") + self.argparser.add_argument( + "--handlelength", action="store", type=float, default=8, + help="Length of handle in multiples of thickness") + self.argparser.add_argument( + "--handleclearance", action="store", type=float, default=1.5, + help="Clearance of handle in multiples of thickness") + + def drawNumbers(self, radius, cover): + fontsize = 0.8 * (radius - cover) + for num in range(8): + angle = num*45 + x = (cover + fontsize *0.4) * math.sin(math.radians(angle)) + y = (cover + fontsize *0.4) * math.cos(math.radians(angle)) + self.text(str(num+1), align="center middle", fontsize=fontsize, angle=-angle, color=[1,0,0], + y=y, x=x) + + def lockPin(self, layers, move=None): + t = self.thickness + cutout_width = t/3 + barb_length = t + base_length = layers * t + base_width = t + total_length = base_length + barb_length + total_width = base_width + cutout_width * 0.5 + cutout_angle = math.degrees(math.atan(cutout_width / base_length)) + cutout_length = math.sqrt(cutout_width**2 + base_length**2) + + #self.rectangularWall(5*t, t) + + if self.move(total_length, total_width, move, True): + return + + self.edge(total_length) + self.corner(90) + self.edge(base_width * 1/3) + self.corner(90) + self.edge(base_length) + self.corner(180+cutout_angle, 0) + self.edge(cutout_length) + self.corner(90-cutout_angle) + self.edge(cutout_width * 1.5) + self.corner(90) + self.edge(barb_length) + self.corner(90) + self.corner(-90, cutout_width * 0.5) + self.edge(base_length - cutout_width * 0.5) + self.corner(90) + self.edge(t) + self.corner(90) + + self.move(total_length, total_width, move) + + + def render(self): + x, y, h = self.x, self.y, self.h + t = self.thickness + + slot_length = self.slotlength + slot_width = self.slotwidth + + handle_length = self.handlelength * t + handle_clearance = self.handleclearance * t + + # lock parameters + big_radius = 2.25 * t + small_radius = 1.4 * t + doorhole_radius = 1.25 * t + spacing = 1 + + # side walls + with self.saved_context(): + self.rectangularWall(x, h, "seFf", move="mirror right") + self.rectangularWall(y, h, "sFFF", move="right") + + # wall with holes for the locking bar + self.rectangularWall( + x, h, "sfFe", ignore_widths=[3,4,7,8], + callback=[lambda: self.fingerHolesAt(2.75*t, 0, h, 90)], + move="mirror right") + + # locking bar + self.moveTo(0, self.edges['s'].spacing() + t) + self.rectangularWall(1.33*t, h, "eeef", move="right") + # door + door_clearance = .1 * t # amount to shave off of the door width so it can open + before_hinge = 1.25 * t - door_clearance + after_hinge = y - 2.25 * t - door_clearance + self.moveTo(self.spacing/2, -t) + self.polyline( + after_hinge, -90, t, 90, t, 90, t, -90, before_hinge, 90, + h, 90, + before_hinge, -90, t, 90, t, 90, t, -90, after_hinge, 90, + h, 90) + num_dials = 3 + space_under_dials = 6*big_radius + space_not_under_dials = h - space_under_dials + dial_spacing = space_not_under_dials / (num_dials + 1) + if dial_spacing < 1 : + min_height = 6*big_radius + 4 + raise ValueError(f"With thickness {t}, h must be at least {min_height} to fit the dials.") + + for pos_y in (h/2, + h/2 - (2*big_radius + dial_spacing), + h/2 + (2*big_radius + dial_spacing)): + self.hole(3*t - door_clearance, pos_y, doorhole_radius) + self.rectangularHole(3*t - door_clearance, pos_y, t, t) + self.rectangularHole(y/2 - door_clearance, h/2, t, handle_length / 2) + + self.rectangularWall(x, h, "seff", move="up only") + + # top + self.rectangularWall( + y, x, "efff", callback=[ + lambda: self.rectangularHole(y/2, x/2, slot_length, slot_width), + lambda: (self.hole(1.75*t, 1.75*t, 1.15*t), + self.rectangularHole(1.75*t, 1.75*t, t, t))], + label="top", move="right") + + # bottom + self.rectangularWall( + y, x, "efff", callback=[ + lambda: (self.hole(1.75*t, 1.75*t, 1.15*t), + self.rectangularHole(1.75*t, 1.75*t, t, t))], + label="bottom", move="right") + + def holeCB(): + self.rectangularHole(0, 0, t, t) + self.moveTo(0, 0, 45) + self.rectangularHole(0, 0, t, t) + + # locks + with self.saved_context(): + self.partsMatrix(3, 1, "right", self.parts.disc, 2*big_radius, + callback=lambda: (self.drawNumbers(big_radius, small_radius), self.rectangularHole(0, 0, t, t))) + self.partsMatrix(3, 1, "right", self.parts.disc, 2*big_radius, + dwidth=0.8,callback=holeCB) + self.partsMatrix( + 3, 1, "right", self.parts.disc, 2*small_radius, + callback=lambda:self.rectangularHole(0, 0, t, t)) + self.partsMatrix( + 3, 1, "right", self.parts.waivyKnob, 2*small_radius, + callback=lambda:self.rectangularHole(0, 0, t, t)) + + self.partsMatrix(3, 1, "up only", self.parts.disc, 2*big_radius) + + # lock pins + with self.saved_context(): + self.lockPin(5, move="up") + self.lockPin(5, move="up") + self.lockPin(5, move="up") + self.lockPin(5, move="right only") + + # handle + self.moveTo(0) + handle_curve_radius = 0.2 * t + self.moveTo(t * 2.5) + self.polyline( + 0, + (90, handle_curve_radius), + handle_length - 2 * handle_curve_radius, + (90, handle_curve_radius), + handle_clearance - handle_curve_radius, + 90, + handle_length / 4, + -90, + t, + 90, + handle_length / 2, + 90, + t, + -90, + handle_length / 4, + 90, + handle_clearance - handle_curve_radius, + ) diff --git a/static/samples/CoinBankSafe-closed-thumb.jpg b/static/samples/CoinBankSafe-closed-thumb.jpg new file mode 100644 index 00000000..ff28c8fa Binary files /dev/null and b/static/samples/CoinBankSafe-closed-thumb.jpg differ diff --git a/static/samples/CoinBankSafe-closed.jpg b/static/samples/CoinBankSafe-closed.jpg new file mode 100644 index 00000000..5239e1b8 Binary files /dev/null and b/static/samples/CoinBankSafe-closed.jpg differ diff --git a/static/samples/CoinBankSafe-open-thumb.jpg b/static/samples/CoinBankSafe-open-thumb.jpg new file mode 100644 index 00000000..83767fbd Binary files /dev/null and b/static/samples/CoinBankSafe-open-thumb.jpg differ diff --git a/static/samples/CoinBankSafe-open.jpg b/static/samples/CoinBankSafe-open.jpg new file mode 100644 index 00000000..e3b33578 Binary files /dev/null and b/static/samples/CoinBankSafe-open.jpg differ diff --git a/static/samples/CoinBankSafe-pins-thumb.jpg b/static/samples/CoinBankSafe-pins-thumb.jpg new file mode 100644 index 00000000..7d35814c Binary files /dev/null and b/static/samples/CoinBankSafe-pins-thumb.jpg differ diff --git a/static/samples/CoinBankSafe-pins.jpg b/static/samples/CoinBankSafe-pins.jpg new file mode 100644 index 00000000..b336e557 Binary files /dev/null and b/static/samples/CoinBankSafe-pins.jpg differ diff --git a/static/samples/CoinBankSafe-thumb.jpg b/static/samples/CoinBankSafe-thumb.jpg new file mode 100644 index 00000000..4330eb79 Binary files /dev/null and b/static/samples/CoinBankSafe-thumb.jpg differ diff --git a/static/samples/CoinBankSafe.jpg b/static/samples/CoinBankSafe.jpg new file mode 100644 index 00000000..1ec97d1e Binary files /dev/null and b/static/samples/CoinBankSafe.jpg differ diff --git a/static/samples/samples.sha256 b/static/samples/samples.sha256 index 49a16790..4e5c4f45 100644 --- a/static/samples/samples.sha256 +++ b/static/samples/samples.sha256 @@ -159,3 +159,9 @@ cdd8658c4bd66de5e00c5a961583986a47c29532e006cb4fedb69faab60c9bac ../static/samp 27699d62eab473d903f2bcf0f334c2c36ff3601772c4c0ddeae89606a7bb10b5 *../static/samples/FlexTest2.jpg 853ace5a13ac571696a1965c72ae456c769ed4bc01ea29bb60bbe40518dcecf9 *../static/samples/RectangularWall.jpg ed533e9827b58591fccbed9e6d31ff8c10bb7f9981501771f6577616a9fbe71b *../static/samples/WallRack.jpg +f625a31c8f1f08341f8e4c0ba5d34524f92e258ca2ae3027774c399a200ddfc9 ../static/samples/CoinBankSafe.jpg +49d330c7159dd77fc2e7a4d82ca46f2834c7e35a8c59b44739c0defbd045bedf ../static/samples/CoinBankSafe-open.jpg +17e9c4f4da3a74b94168913b78695918096269dfd2c59feb70bd4a833e155d04 ../static/samples/CoinBankSafe-pins.jpg +94e37a41d8b873f39bd3e8c74465b012c8f861031c93f64fd6eda89af02015c0 ../static/samples/FlexBook-2.jpg +4d8b4d5467a88431ba24e893157a6e09997d74654cd7e27878d2a54b1ee751c6 ../static/samples/CoinBankSafe-closed.jpg +bace3582c13ee543f09fd45752d4403e237d01541aaa4ea266e61e64fd12156a ../static/samples/FlexBook.jpg