boxespy/boxes/generators/phoneholder.py

306 lines
9.4 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# Copyright (C) 2021 Guillaume Collic
#
# 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/>.
import math
from functools import partial
from boxes import Boxes, edges
class PhoneHolder(Boxes):
"""
Smartphone desk holder
"""
ui_group = "Misc"
description = """
This phone stand holds your phone between two tabs, with access to its
bottom, in order to connect a charger, headphones, and also not to obstruct
the mic.
Default values are currently based on Galaxy S7.
"""
def __init__(self):
Boxes.__init__(self)
self.argparser.add_argument(
"--phone_height",
type=float,
default=142,
help="Height of the phone.",
)
self.argparser.add_argument(
"--phone_width",
type=float,
default=73,
help="Width of the phone.",
)
self.argparser.add_argument(
"--phone_depth",
type=float,
default=11,
help=(
"Depth of the phone. Used by the bottom support holding the "
"phone, and the side tabs depth as well. Should be at least "
"your material thickness for assembly reasons."
),
)
self.argparser.add_argument(
"--angle",
type=float,
default=25,
help="angle at which the phone stands, in degrees. 0° is vertical.",
)
self.argparser.add_argument(
"--bottom_margin",
type=float,
default=30,
help="Height of the support below the phone",
)
self.argparser.add_argument(
"--tab_size",
type=float,
default=76,
help="Length of the tabs holding the phone",
)
self.argparser.add_argument(
"--bottom_support_spacing",
type=float,
default=16,
help=(
"Spacing between the two bottom support. Choose a value big "
"enough for the charging cable, without getting in the way of "
"other ports."
),
)
self.addSettingsArgs(edges.FingerJointSettings)
def render(self):
self.h = self.phone_height + self.bottom_margin
tab_start = self.bottom_margin
tab_length = self.tab_size
tab_depth = self.phone_depth
support_depth = self.phone_depth
support_spacing = self.bottom_support_spacing
rad = math.radians(self.angle)
self.stand_depth = self.h * math.sin(rad)
self.stand_height = self.h * math.cos(rad)
self.render_front_plate(tab_start, tab_length, support_spacing, move="right")
self.render_back_plate(move="right")
self.render_side_plate(tab_start, tab_length, tab_depth, move="right")
for move in ["right mirror", "right"]:
self.render_bottom_support(tab_start, support_depth, tab_length, move=move)
def render_front_plate(
self,
tab_start,
tab_length,
support_spacing,
support_fingers_length=None,
move="right",
):
if not support_fingers_length:
support_fingers_length = tab_length
be = BottomEdge(self, tab_start, support_spacing)
se1 = SideEdge(self, tab_start, tab_length)
se2 = SideEdge(self, tab_start, tab_length, reverse=True)
self.rectangularWall(
self.phone_width,
self.h,
[be, se1, "e", se2],
move=move,
callback=[
partial(
lambda: self.front_plate_holes(
tab_start, support_fingers_length, support_spacing
)
)
],
)
def render_back_plate(
self,
move="right",
):
be = BottomEdge(self, 0, 0)
self.rectangularWall(
self.phone_width,
self.stand_height,
[be, "F", "e", "F"],
move=move,
)
def front_plate_holes(
self, support_start_height, support_fingers_length, support_spacing
):
margin = (self.phone_width - support_spacing - self.thickness) / 2
self.fingerHolesAt(
margin,
support_start_height,
support_fingers_length,
)
self.fingerHolesAt(
self.phone_width - margin,
support_start_height,
support_fingers_length,
)
def render_side_plate(self, tab_start, tab_length, tab_depth, move):
te = TabbedEdge(self, tab_start, tab_length, tab_depth, reverse=True)
self.rectangularTriangle(
self.stand_depth,
self.stand_height,
["e", "f", te],
move=move,
num=2,
)
def render_bottom_support(
self, support_start_height, support_depth, support_fingers_length, move="right"
):
full_height = support_start_height + support_fingers_length
rad = math.radians(self.angle)
floor_length = full_height * math.sin(rad)
angled_height = full_height * math.cos(rad)
bottom_radius = min(support_start_height, 3 * self.thickness + support_depth)
smaller_radius = 0.5
support_hook_height = 5
full_width = floor_length + (support_depth + 3 * self.thickness) * math.cos(rad)
if self.move(full_width, angled_height, move, True):
return
self.polyline(
floor_length,
self.angle,
3 * self.thickness + support_depth - bottom_radius,
(90, bottom_radius),
support_hook_height + support_start_height - bottom_radius,
(180, self.thickness),
support_hook_height - smaller_radius,
(-90, smaller_radius),
self.thickness + support_depth - smaller_radius,
-90,
)
self.edges["f"](support_fingers_length)
self.polyline(
0,
180 - self.angle,
angled_height,
90,
)
# Move for next piece
self.move(full_width, angled_height, move)
class BottomEdge(edges.BaseEdge):
def __init__(self, boxes, support_start_height, support_spacing):
super().__init__(boxes, None)
self.support_start_height = support_start_height
self.support_spacing = support_spacing
def __call__(self, length, **kw):
cable_hole_radius = 2.5
self.support_spacing = max(self.support_spacing, 2 * cable_hole_radius)
side = (length - self.support_spacing - 2 * self.thickness) / 2
half = [
side,
90,
self.support_start_height,
-90,
self.thickness,
-90,
self.support_start_height,
90,
self.support_spacing / 2 - cable_hole_radius,
90,
2 * cable_hole_radius,
]
path = half + [(-180, cable_hole_radius)] + list(reversed(half))
self.polyline(*path)
class SideEdge(edges.BaseEdge):
def __init__(self, boxes, tab_start, tab_length, reverse=False):
super().__init__(boxes, None)
self.tab_start = tab_start
self.tab_length = tab_length
self.reverse = reverse
def __call__(self, length, **kw):
tab_start = self.tab_start
tab_end = length - self.tab_start - self.tab_length
if self.reverse:
tab_start, tab_end = tab_end, tab_start
self.edges["F"](tab_start)
self.polyline(
0,
90,
self.thickness,
-90,
)
self.edges["f"](self.tab_length)
self.polyline(0, -90, self.thickness, 90)
self.edges["F"](tab_end)
def startwidth(self):
return self.boxes.thickness
class TabbedEdge(edges.BaseEdge):
def __init__(self, boxes, tab_start, tab_length, tab_depth, reverse=False):
super().__init__(boxes, None)
self.tab_start = tab_start
self.tab_length = tab_length
self.tab_depth = tab_depth
self.reverse = reverse
def __call__(self, length, **kw):
tab_start = self.tab_start
tab_end = length - self.tab_start - self.tab_length
if self.reverse:
tab_start, tab_end = tab_end, tab_start
self.edges["f"](tab_start)
self.ctx.save()
self.fingerHolesAt(0, -self.thickness / 2, self.tab_length, 0)
self.ctx.restore()
self.polyline(
0,
-90,
self.thickness,
(90, self.tab_depth),
self.tab_length - 2 * self.tab_depth,
(90, self.tab_depth),
self.thickness,
-90,
)
self.edges["f"](tab_end)
def margin(self):
return self.tab_depth + self.thickness