Add PhoneHolder
This commit is contained in:
parent
3a3304de67
commit
97d85ff10e
|
@ -0,0 +1,304 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# 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(BottomEdge, self).__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(SideEdge, self).__init__(boxes, None)
|
||||
self.tab_start = tab_start
|
||||
self.tab_length = tab_length
|
||||
self.reverse = reverse
|
||||
|
||||
def __call__(self, length, **kw):
|
||||
tab_end = length - self.tab_start - self.tab_length
|
||||
|
||||
if self.reverse:
|
||||
self.tab_start, tab_end = tab_end, self.tab_start
|
||||
|
||||
self.edges["F"](self.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(TabbedEdge, self).__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_end = length - self.tab_start - self.tab_length
|
||||
|
||||
if self.reverse:
|
||||
self.tab_start, tab_end = tab_end, self.tab_start
|
||||
|
||||
self.edges["f"](self.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
|
Binary file not shown.
After Width: | Height: | Size: 6.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 211 KiB |
|
@ -60,3 +60,4 @@ b0c8e566c62d5f1102201effa70301a59b144f9fd696ee44acc3582e2f2a2c5f ../static/samp
|
|||
db81bb75c30252359493e48d968268d15d28586a0cf9db125e2ccc44a8e846c6 ../static/samples/StorageRack-3.jpg
|
||||
2f6510b70d91cd668a6f27da2b13bbffbd593f4a8f5202e94a0e629afb994f94 ../static/samples/PaperBox.jpg
|
||||
7f513ac41d6a7dfef10f0dd5419bc36f8189b9896cf6bdd20958c6b691fc765c ../static/samples/TrayLayout2.jpg
|
||||
261531111eb52c5e32bcb989d74933e3177050d11361db3fbfad8193584d6980 ../static/samples/PhoneHolder.jpg
|
||||
|
|
Loading…
Reference in New Issue