Source code for boxes.generators.phoneholder

#!/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


[docs]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