2016-07-11 23:01:57 +02:00
#!/usr/bin/env python3
2014-03-16 18:26:12 +01:00
# Copyright (C) 2013-2014 Florian Festi
#
# 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/>.
2023-01-08 19:41:02 +01:00
from __future__ import annotations
2013-04-01 09:42:29 +02:00
2022-12-31 15:52:55 +01:00
import argparse
import copy
2013-04-01 09:42:29 +02:00
import math
2022-12-31 15:52:55 +01:00
import random
import re
2017-02-23 17:34:57 +01:00
import sys
2016-03-04 12:09:59 +01:00
from argparse import ArgumentParser
2019-02-02 21:06:00 +01:00
from contextlib import contextmanager
2022-12-31 15:52:55 +01:00
from functools import wraps
2019-12-21 17:45:21 +01:00
from shlex import quote
2023-01-08 19:41:02 +01:00
from typing import Any
2022-12-31 15:52:55 +01:00
from xml . sax . saxutils import quoteattr
2022-06-05 09:24:54 +02:00
from shapely . geometry import *
from shapely . ops import split
2022-06-24 11:54:40 +02:00
import gettext
2019-07-06 11:47:41 +02:00
2016-03-25 14:02:52 +01:00
from boxes import edges
2016-06-27 09:10:00 +02:00
from boxes import formats
2016-07-11 14:59:11 +02:00
from boxes import gears
2016-07-15 18:29:14 +02:00
from boxes import parts
2022-12-31 15:52:55 +01:00
from boxes import pulley
from boxes import svgutil
from boxes . Color import *
2023-02-20 22:24:25 +01:00
import qrcode
from boxes . qrcode_factory import BoxesQrCodeFactory
2016-08-17 15:07:41 +02:00
2016-04-10 22:51:25 +02:00
### Helpers
2013-04-17 13:25:30 +02:00
def dist ( dx , dy ) :
2016-03-28 16:55:41 +02:00
"""
Return distance
: param dx : delta x
2023-01-02 00:32:42 +01:00
: param dy : delay y
2016-03-28 16:55:41 +02:00
"""
2016-08-17 15:07:41 +02:00
return ( dx * dx + dy * dy ) * * 0.5
2013-05-14 17:52:33 +02:00
def restore ( func ) :
2016-03-28 16:55:41 +02:00
"""
2023-01-02 00:32:42 +01:00
Wrapper : Restore coordinates after function
2016-03-28 16:55:41 +02:00
: param func : function to wrap
"""
2016-08-17 15:07:41 +02:00
2013-05-14 17:52:33 +02:00
@wraps ( func )
def f ( self , * args , * * kw ) :
2019-02-02 21:06:00 +01:00
with self . saved_context ( ) :
pt = self . ctx . get_current_point ( )
func ( self , * args , * * kw )
2013-06-07 12:50:13 +02:00
self . ctx . move_to ( * pt )
2016-08-17 15:07:41 +02:00
2013-05-14 17:52:33 +02:00
return f
2016-08-17 15:07:41 +02:00
2016-06-21 19:06:33 +02:00
def holeCol ( func ) :
"""
Wrapper : color holes differently
: param func : function to wrap
"""
2016-08-17 15:07:41 +02:00
2016-06-21 19:06:33 +02:00
@wraps ( func )
def f ( self , * args , * * kw ) :
2021-06-01 21:59:21 +02:00
if " color " in kw :
color = kw . pop ( " color " )
else :
color = Color . INNER_CUT
2016-06-21 19:06:33 +02:00
self . ctx . stroke ( )
2019-02-02 21:07:32 +01:00
with self . saved_context ( ) :
2021-06-01 21:59:21 +02:00
self . set_source_color ( color )
2019-02-02 21:07:32 +01:00
func ( self , * args , * * kw )
self . ctx . stroke ( )
2016-06-21 19:06:33 +02:00
2016-08-17 15:07:41 +02:00
return f
2016-06-21 19:06:33 +02:00
2013-07-20 10:49:45 +02:00
#############################################################################
### Building blocks
#############################################################################
2013-07-25 14:22:55 +02:00
class NutHole :
2016-03-28 16:55:41 +02:00
""" Draw a hex nut """
2013-07-25 14:22:55 +02:00
sizes = {
2016-08-17 15:07:41 +02:00
" M1.6 " : ( 3.2 , 1.3 ) ,
" M2 " : ( 4 , 1.6 ) ,
" M2.5 " : ( 5 , 2.0 ) ,
" M3 " : ( 5.5 , 2.4 ) ,
" M4 " : ( 7 , 3.2 ) ,
" M5 " : ( 8 , 4.7 ) ,
" M6 " : ( 10 , 5.2 ) ,
2016-09-26 18:33:22 +02:00
" M8 " : ( 13.7 , 6.8 ) ,
2016-08-17 15:07:41 +02:00
" M10 " : ( 16 , 8.4 ) ,
" M12 " : ( 18 , 10.8 ) ,
" M14 " : ( 21 , 12.8 ) ,
" M16 " : ( 24 , 14.8 ) ,
" M20 " : ( 30 , 18.0 ) ,
" M24 " : ( 36 , 21.5 ) ,
" M30 " : ( 46 , 25.6 ) ,
" M36 " : ( 55 , 31 ) ,
" M42 " : ( 65 , 34 ) ,
" M48 " : ( 75 , 38 ) ,
" M56 " : ( 85 , 45 ) ,
" M64 " : ( 95 , 51 ) ,
}
2013-07-25 14:22:55 +02:00
2023-01-08 19:41:02 +01:00
def __init__ ( self , boxes , settings ) - > None :
2013-07-25 14:22:55 +02:00
self . boxes = boxes
self . ctx = boxes . ctx
self . settings = settings
2019-02-14 17:31:56 +01:00
def __getattr__ ( self , name ) :
return getattr ( self . boxes , name )
2013-07-25 14:22:55 +02:00
@restore
2016-06-21 19:06:33 +02:00
@holeCol
2013-07-25 14:22:55 +02:00
def __call__ ( self , size , x = 0 , y = 0 , angle = 0 ) :
2016-03-23 09:12:14 +01:00
size = self . sizes . get ( size , ( size , ) ) [ 0 ]
2016-08-17 15:07:41 +02:00
side = size / 3 * * 0.5
2013-07-25 14:22:55 +02:00
self . boxes . moveTo ( x , y , angle )
2016-08-17 15:07:41 +02:00
self . boxes . moveTo ( - 0.5 * side , 0.5 * size , angle )
2013-07-25 14:22:55 +02:00
for i in range ( 6 ) :
self . boxes . edge ( side )
self . boxes . corner ( - 60 )
2016-08-17 15:07:41 +02:00
2016-04-10 22:51:25 +02:00
##############################################################################
### Argument types
##############################################################################
2016-03-03 13:27:22 +01:00
def argparseSections ( s ) :
2016-03-28 16:55:41 +02:00
"""
Parse sections parameter
: param s : string to parse
"""
2019-06-15 20:04:09 +02:00
result = [ ]
s = re . split ( r " \ s|: " , s )
2016-03-03 13:27:22 +01:00
try :
2019-06-15 20:04:09 +02:00
for part in s :
m = re . match ( r " ^( \ d+( \ . \ d+)?)/( \ d+)$ " , part )
if m :
n = int ( m . group ( 3 ) )
result . extend ( [ float ( m . group ( 1 ) ) / n ] * n )
continue
m = re . match ( r " ^( \ d+( \ . \ d+)?) \ *( \ d+)$ " , part )
if m :
n = int ( m . group ( 3 ) )
result . extend ( [ float ( m . group ( 1 ) ) ] * n )
continue
result . append ( float ( part ) )
2016-03-03 13:27:22 +01:00
except ValueError :
raise argparse . ArgumentTypeError ( " Don ' t understand sections string " )
2019-11-22 16:47:41 +01:00
if not result :
result . append ( 0.0 )
2019-06-15 20:04:09 +02:00
return result
2016-08-17 15:07:41 +02:00
2016-04-10 22:51:57 +02:00
class ArgparseEdgeType :
2018-09-07 14:46:27 +02:00
""" argparse type to select from a set of edge types """
2016-04-17 14:26:27 +02:00
names = edges . getDescriptions ( )
2023-01-08 19:41:02 +01:00
edges : list [ str ] = [ ]
2016-04-10 22:51:57 +02:00
2023-01-08 19:41:02 +01:00
def __init__ ( self , edges : str | None = None ) - > None :
2016-04-10 22:51:57 +02:00
if edges :
self . edges = list ( edges )
def __call__ ( self , pattern ) :
if len ( pattern ) != 1 :
raise ValueError ( " Edge type can only have one letter. " )
if pattern not in self . edges :
raise ValueError ( " Use one of the following values: " +
" , " . join ( edges ) )
return pattern
2020-04-13 18:45:17 +02:00
def html ( self , name , default , translate ) :
2016-04-10 22:51:57 +02:00
options = " \n " . join (
2023-01-13 15:32:32 +01:00
""" <option value= " %s " %s > %s </option> """ %
2016-04-10 22:51:57 +02:00
( e , ' selected= " selected " ' if e == default else " " ,
2023-01-13 15:32:32 +01:00
translate ( " %s %s " % ( e , self . names . get ( e , " " ) ) ) ) for e in self . edges )
2022-08-30 00:59:57 +02:00
return """ <select name= " %s " id= " %s " aria-labeledby= " %s %s " size= " 1 " > \n %s </select> \n """ % ( name , name , name + " _id " , name + " _description " , options )
2016-04-10 22:51:57 +02:00
2017-02-23 17:27:20 +01:00
def inx ( self , name , viewname , arg ) :
2022-02-18 10:42:40 +01:00
return ( ' <param name= " %s " type= " optiongroup " appearance= " combo " gui-text= " %s " gui-description= %s > \n ' %
2017-02-23 17:27:20 +01:00
( name , viewname , quoteattr ( arg . help or " " ) ) +
2023-01-13 15:32:32 +01:00
' ' . join ( ' <option value= " %s " > %s %s </option> \n ' % (
2017-02-23 17:27:20 +01:00
e , e , self . names . get ( e , " " ) )
2023-01-13 15:32:32 +01:00
for e in self . edges ) +
2017-02-23 17:27:20 +01:00
' </param> \n ' )
2016-12-18 23:28:15 +01:00
class BoolArg :
def __call__ ( self , arg ) :
2017-02-23 17:19:23 +01:00
if not arg or arg . lower ( ) in ( " none " , " 0 " , " off " , " false " ) :
2016-12-18 23:28:15 +01:00
return False
return True
2020-04-13 18:45:17 +02:00
def html ( self , name , default , _ ) :
2018-08-16 08:54:52 +02:00
if isinstance ( default , ( str ) ) :
default = self ( default )
2016-12-18 23:28:15 +01:00
return """ <input name= " %s " type= " hidden " value= " 0 " >
2022-08-30 00:59:57 +02:00
< input name = " %s " id = " %s " aria - labeledby = " %s %s " type = " checkbox " value = " 1 " % s > """ % \
2022-12-04 16:54:51 +01:00
( name , name , name , name + " _id " , name + " _description " , ' checked= " checked " ' if default else " " )
2016-12-18 23:28:15 +01:00
boolarg = BoolArg ( )
2016-08-17 15:07:41 +02:00
2020-08-01 19:09:23 +02:00
class HexHolesSettings ( edges . Settings ) :
""" Settings for hexagonal hole patterns
Values :
* absolute
* diameter : 5.0 : diameter of the holes
* distance : 3.0 : distance between the holes
2020-10-10 09:03:35 +02:00
* style : " circle " : currently only supported style
2020-08-01 19:09:23 +02:00
"""
absolute_params = {
' diameter ' : 10.0 ,
' distance ' : 3.0 ,
' style ' : ( ' circle ' , ) ,
}
relative_params = { }
2022-06-05 09:24:54 +02:00
class fillHolesSettings ( edges . Settings ) :
""" Settings for Hole filling
Values :
* absolute
* fill_pattern : " no fill " : style of hole pattern
* hole_style : " round " : style of holes ( does not apply to fill patterns ' vbar ' and ' hbar ' )
* max_random : 1000 : maximum number of random holes
* bar_length : 50 : maximum length of bars
* hole_max_radius : 12.0 : maximum radius of generated holes ( in mm )
* hole_min_radius : 4.0 : minimum radius of generated holes ( in mm )
* space_between_holes : 4.0 : hole to hole spacing ( in mm )
* space_to_border : 4.0 : hole to border spacing ( in mm )
"""
absolute_params = {
" fill_pattern " : ( " no fill " , " hex " , " square " , " random " , " hbar " , " vbar " ) ,
" hole_style " : ( " round " , " triangle " , " square " , " hexagon " , " octagon " ) ,
" max_random " : 1000 ,
" bar_length " : 50 ,
" hole_max_radius " : 3.0 ,
" hole_min_radius " : 0.5 ,
" space_between_holes " : 4.0 ,
" space_to_border " : 4.0 ,
}
2020-08-01 19:09:23 +02:00
2016-04-10 22:51:25 +02:00
##############################################################################
### Main class
##############################################################################
2013-04-01 09:42:29 +02:00
class Boxes :
2023-01-02 00:32:42 +01:00
""" Main class -- Generator should subclass this """
2013-04-01 09:42:29 +02:00
2016-07-31 16:36:33 +02:00
webinterface = True
2017-02-13 17:31:58 +01:00
ui_group = " Misc "
2023-03-02 15:14:50 +01:00
UI = " "
2016-07-31 16:36:33 +02:00
2023-01-08 19:41:02 +01:00
description : str = " " # Markdown syntax is supported
2017-11-18 22:16:53 +01:00
2023-01-08 19:41:02 +01:00
def __init__ ( self ) - > None :
2016-06-27 09:10:00 +02:00
self . formats = formats . Formats ( )
2019-02-08 17:43:15 +01:00
self . ctx = None
2023-01-08 19:41:02 +01:00
description : str = self . __doc__ or " "
2017-11-30 22:40:45 +01:00
if self . description :
description + = " \n \n " + self . description
self . argparser = ArgumentParser ( description = description )
2023-01-08 19:41:02 +01:00
self . edgesettings : dict [ Any , Any ] = { }
2017-02-23 17:34:57 +01:00
self . inkscapefile = None
2023-02-20 22:22:26 +01:00
self . non_default_args : dict [ Any , Any ] = { }
2022-06-24 11:54:40 +02:00
self . translations = gettext . NullTranslations ( )
2016-10-31 18:12:39 +01:00
2019-07-06 11:47:41 +02:00
self . metadata = {
" name " : self . __class__ . __name__ ,
2019-07-04 21:43:56 +02:00
" short_description " : self . __doc__ ,
2019-07-06 11:47:41 +02:00
" description " : self . description ,
" group " : self . ui_group ,
" url " : " " ,
2023-02-20 22:22:26 +01:00
" url_short " : " " ,
" cli " : " " ,
" cli_short " : " " ,
2019-07-06 11:47:41 +02:00
}
self . argparser . _action_groups [ 1 ] . title = self . __class__ . __name__ + " Settings "
2016-10-30 15:27:16 +01:00
defaultgroup = self . argparser . add_argument_group (
" Default Settings " )
defaultgroup . add_argument (
2017-01-30 08:10:29 +01:00
" --thickness " , action = " store " , type = float , default = 3.0 ,
2022-01-09 16:20:58 +01:00
help = " thickness of the material (in mm) [ \U0001F6C8 ](https://florianfesti.github.io/boxes/html/usermanual.html#thickness) " )
2016-10-30 15:27:16 +01:00
defaultgroup . add_argument (
2016-08-17 15:07:41 +02:00
" --output " , action = " store " , type = str , default = " box.svg " ,
2016-03-03 13:27:22 +01:00
help = " name of resulting file " )
2016-10-30 15:27:16 +01:00
defaultgroup . add_argument (
2016-08-17 15:07:41 +02:00
" --format " , action = " store " , type = str , default = " svg " ,
2016-06-27 09:10:00 +02:00
choices = self . formats . getFormats ( ) ,
2022-01-03 13:30:43 +01:00
help = " format of resulting file [ \U0001F6C8 ](https://florianfesti.github.io/boxes/html/usermanual.html#format) " )
2017-12-11 18:48:33 +01:00
defaultgroup . add_argument (
" --tabs " , action = " store " , type = float , default = 0.0 ,
2022-01-09 16:20:58 +01:00
help = " width of tabs holding the parts in place (in mm)(not supported everywhere) [ \U0001F6C8 ](https://florianfesti.github.io/boxes/html/usermanual.html#tabs) " )
2023-02-20 22:28:16 +01:00
defaultgroup . add_argument (
" --qr_code " , action = " store " , type = boolarg , default = False ,
help = " Add a QR Code with link or command line to the generated output " )
2016-10-30 15:27:16 +01:00
defaultgroup . add_argument (
2016-12-18 23:28:15 +01:00
" --debug " , action = " store " , type = boolarg , default = False ,
2022-01-03 13:30:43 +01:00
help = " print surrounding boxes for some structures [ \U0001F6C8 ](https://florianfesti.github.io/boxes/html/usermanual.html#debug) " )
2021-06-04 20:59:17 +02:00
defaultgroup . add_argument (
" --labels " , action = " store " , type = boolarg , default = True ,
help = " label the parts (where available) " )
2016-10-30 15:27:16 +01:00
defaultgroup . add_argument (
2016-08-17 15:07:41 +02:00
" --reference " , action = " store " , type = float , default = 100 ,
2022-01-09 16:20:58 +01:00
help = " print reference rectangle with given length (in mm)(zero to disable) [ \U0001F6C8 ](https://florianfesti.github.io/boxes/html/usermanual.html#reference) " )
2022-03-20 00:21:57 +01:00
defaultgroup . add_argument (
" --inner_corners " , action = " store " , type = str , default = " loop " ,
choices = [ " loop " , " corner " , " backarc " ] ,
help = " style for inner corners [ \U0001F6C8 ](https://florianfesti.github.io/boxes/html/usermanual.html#inner-corners) " )
2016-10-30 15:27:16 +01:00
defaultgroup . add_argument (
2017-01-30 08:10:29 +01:00
" --burn " , action = " store " , type = float , default = 0.1 ,
2022-01-09 16:20:58 +01:00
help = ' burn correction (in mm)(bigger values for tighter fit) [ \U0001F6C8 ](https://florianfesti.github.io/boxes/html/usermanual.html#burn) ' )
2016-03-03 13:27:22 +01:00
2019-02-02 21:06:00 +01:00
@contextmanager
def saved_context ( self ) :
"""
2020-05-17 22:28:22 +02:00
Generator : for saving and restoring contexts .
2019-02-02 21:06:00 +01:00
"""
cr = self . ctx
cr . save ( )
try :
yield cr
finally :
cr . restore ( )
2019-02-02 21:07:32 +01:00
def set_source_color ( self , color ) :
"""
Sets the color of the pen .
"""
self . ctx . set_source_rgb ( * color )
2020-04-20 23:38:00 +02:00
def set_font ( self , style , bold = False , italic = False ) :
"""
Set font style used
: param style : " serif " , " sans-serif " or " monospaced "
: param bold : Use bold font
: param italic : Use italic font
"""
self . ctx . set_font ( style , bold , italic )
2016-06-07 20:20:35 +02:00
def open ( self ) :
2016-03-28 16:55:41 +02:00
"""
Prepare for rendering
2019-02-13 09:04:57 +01:00
Create canvas and edge and other objects
Call this before . render ( )
2016-03-28 16:55:41 +02:00
"""
2019-02-08 17:43:15 +01:00
if self . ctx is not None :
return
2013-07-20 10:49:45 +02:00
2016-08-17 15:07:41 +02:00
self . bedBoltSettings = ( 3 , 5.5 , 2 , 20 , 15 ) # d, d_nut, h_nut, l, l1
2016-06-27 09:10:00 +02:00
self . surface , self . ctx = self . formats . getSurface ( self . format , self . output )
2019-01-22 05:40:16 +01:00
if self . format == ' svg_Ponoko ' :
self . ctx . set_line_width ( 0.01 )
self . set_source_color ( Color . BLUE )
else :
self . ctx . set_line_width ( max ( 2 * self . burn , 0.05 ) )
self . set_source_color ( Color . BLACK )
2019-02-02 21:07:32 +01:00
self . spacing = 2 * self . burn + 0.5 * self . thickness
2020-04-20 23:38:00 +02:00
self . set_font ( " sans-serif " )
2013-07-20 10:49:45 +02:00
self . _buildObjects ( )
2019-02-14 21:36:54 +01:00
if self . reference and self . format != ' svg_Ponoko ' :
2022-06-05 21:46:20 +02:00
self . move ( self . reference , 10 , " up " , before = True )
2016-07-31 18:28:57 +02:00
self . ctx . rectangle ( 0 , 0 , self . reference , 10 )
2022-04-23 08:45:10 +02:00
if self . reference < 80 :
self . text ( " % .fmm, burn: %.2f mm " % ( self . reference , self . burn ) , self . reference + 5 , 5 ,
fontsize = 8 , align = " middle left " , color = Color . ANNOTATIONS )
2016-07-31 18:28:57 +02:00
else :
2022-04-23 08:45:10 +02:00
self . text ( " % .fmm, burn: %.2f mm " % ( self . reference , self . burn ) , self . reference / 2.0 , 5 ,
fontsize = 8 , align = " middle center " , color = Color . ANNOTATIONS )
2022-06-05 21:46:20 +02:00
self . move ( self . reference , 10 , " up " )
2023-02-20 22:28:16 +01:00
if self . qr_code :
self . renderQrCode ( )
2017-02-23 17:29:18 +01:00
self . ctx . stroke ( )
2023-02-20 22:28:16 +01:00
def renderQrCode ( self ) :
content = self . metadata [ ' url_short ' ] or self . metadata [ " cli_short " ]
size = 1.5
if content :
with self . saved_context ( ) :
self . qrcode ( content , box_size = size , move = " right " )
self . text ( text = content , y = 6 , color = Color . ANNOTATIONS , fontsize = 6 )
self . qrcode ( content , box_size = size , move = " up only " )
2013-07-20 10:49:45 +02:00
2017-03-29 07:36:50 +02:00
def buildArgParser ( self , * l , * * kw ) :
2016-03-28 16:55:41 +02:00
"""
2018-09-07 14:47:01 +02:00
Add commonly used arguments
2016-03-28 16:55:41 +02:00
2023-01-02 00:32:42 +01:00
: param l : parameter names
: param kw : parameters with new default values
2016-03-28 16:55:41 +02:00
2018-09-07 14:47:01 +02:00
Supported parameters are
* floats : x , y , h , hi
2019-03-30 23:48:59 +01:00
* argparseSections : sx , sy , sh
2018-09-07 14:47:01 +02:00
* ArgparseEdgeType : bottom_edge , top_edge
* boolarg : outside
* str ( selection ) : nema_mount
2016-03-28 16:55:41 +02:00
"""
2016-03-03 13:27:22 +01:00
for arg in l :
2017-03-29 07:36:50 +02:00
kw [ arg ] = None
for arg , default in kw . items ( ) :
2016-03-03 13:27:22 +01:00
if arg == " x " :
2017-03-29 07:36:50 +02:00
if default is None : default = 100.0
2020-08-02 11:45:54 +02:00
help = " inner width in mm "
if " outside " in kw :
help + = " (unless outside selected) "
2016-03-03 13:27:22 +01:00
self . argparser . add_argument (
2017-03-29 07:36:50 +02:00
" --x " , action = " store " , type = float , default = default ,
2020-08-02 11:45:54 +02:00
help = help )
2016-03-03 13:27:22 +01:00
elif arg == " y " :
2017-03-29 07:36:50 +02:00
if default is None : default = 100.0
2020-08-02 11:45:54 +02:00
help = " inner depth in mm "
if " outside " in kw :
help + = " (unless outside selected) "
2016-03-03 13:27:22 +01:00
self . argparser . add_argument (
2017-03-29 07:36:50 +02:00
" --y " , action = " store " , type = float , default = default ,
2020-08-02 11:45:54 +02:00
help = help )
2016-03-03 13:27:22 +01:00
elif arg == " sx " :
2017-03-29 07:36:50 +02:00
if default is None : default = " 50*3 "
2016-03-03 13:27:22 +01:00
self . argparser . add_argument (
2016-08-17 15:07:41 +02:00
" --sx " , action = " store " , type = argparseSections ,
2017-03-29 07:36:50 +02:00
default = default ,
2022-01-03 13:30:43 +01:00
help = """ sections left to right in mm [ \U0001F6C8 ](https://florianfesti.github.io/boxes/html/usermanual.html#section-parameters) """ )
2016-03-03 13:27:22 +01:00
elif arg == " sy " :
2017-03-29 07:36:50 +02:00
if default is None : default = " 50*3 "
2016-03-03 13:27:22 +01:00
self . argparser . add_argument (
2016-08-17 15:07:41 +02:00
" --sy " , action = " store " , type = argparseSections ,
2017-03-29 07:36:50 +02:00
default = default ,
2022-01-03 13:30:43 +01:00
help = """ sections back to front in mm [ \U0001F6C8 ](https://florianfesti.github.io/boxes/html/usermanual.html#section-parameters) """ )
2019-03-30 23:48:59 +01:00
elif arg == " sh " :
if default is None : default = " 50*3 "
self . argparser . add_argument (
" --sh " , action = " store " , type = argparseSections ,
default = default ,
2022-01-03 13:30:43 +01:00
help = """ sections bottom to top in mm [ \U0001F6C8 ](https://florianfesti.github.io/boxes/html/usermanual.html#section-parameters) """ )
2016-03-03 13:27:22 +01:00
elif arg == " h " :
2017-03-29 07:36:50 +02:00
if default is None : default = 100.0
2020-08-02 11:45:54 +02:00
help = " inner height in mm "
if " outside " in kw :
help + = " (unless outside selected) "
2016-03-03 13:27:22 +01:00
self . argparser . add_argument (
2017-03-29 07:36:50 +02:00
" --h " , action = " store " , type = float , default = default ,
2020-08-02 11:45:54 +02:00
help = help )
2016-03-03 13:27:22 +01:00
elif arg == " hi " :
2017-03-29 07:36:50 +02:00
if default is None : default = 0.0
2016-03-03 13:27:22 +01:00
self . argparser . add_argument (
2017-03-29 07:36:50 +02:00
" --hi " , action = " store " , type = float , default = default ,
2018-04-05 22:55:23 +02:00
help = " inner height of inner walls in mm (unless outside selected)(leave to zero for same as outer walls) " )
2022-05-15 10:23:14 +02:00
elif arg == " hole_dD " :
if default is None : default = " 3.5:6.5 "
self . argparser . add_argument (
" --hole_dD " , action = " store " , type = argparseSections , default = default ,
help = " mounting hole diameter (shaft:head) in mm [ \U0001F6C8 ](https://florianfesti.github.io/boxes/html/usermanual.html#mounting-holes) " )
2016-04-09 11:47:12 +02:00
elif arg == " bottom_edge " :
2017-03-29 07:36:50 +02:00
if default is None : default = " h "
2016-04-09 11:47:12 +02:00
self . argparser . add_argument (
2016-08-17 15:07:41 +02:00
" --bottom_edge " , action = " store " ,
2018-12-08 18:21:36 +01:00
type = ArgparseEdgeType ( " Fhse " ) , choices = list ( " Fhse " ) ,
2017-03-29 07:36:50 +02:00
default = default ,
2016-04-09 11:47:12 +02:00
help = " edge type for bottom edge " )
elif arg == " top_edge " :
2017-03-29 07:36:50 +02:00
if default is None : default = " e "
2016-04-09 11:47:12 +02:00
self . argparser . add_argument (
2016-08-17 15:07:41 +02:00
" --top_edge " , action = " store " ,
2022-06-16 14:59:36 +02:00
type = ArgparseEdgeType ( " efFhcESŠikvLtGyY " ) , choices = list ( " efFhcESŠikvfLtGyY " ) ,
2017-03-29 07:36:50 +02:00
default = default , help = " edge type for top edge " )
2016-08-17 15:07:41 +02:00
elif arg == " outside " :
2017-03-29 07:36:50 +02:00
if default is None : default = True
2016-07-09 22:52:03 +02:00
self . argparser . add_argument (
2017-03-29 07:36:50 +02:00
" --outside " , action = " store " , type = boolarg , default = default ,
2022-01-07 11:11:08 +01:00
help = " treat sizes as outside measurements [ \U0001F6C8 ](https://florianfesti.github.io/boxes/html/usermanual.html#outside) " )
2017-03-29 07:38:10 +02:00
elif arg == " nema_mount " :
if default is None : default = 23
self . argparser . add_argument (
" --nema_mount " , action = " store " ,
type = int , choices = list ( sorted ( self . nema_sizes . keys ( ) ) ) ,
default = default , help = " NEMA size of motor " )
2016-03-03 13:27:22 +01:00
else :
raise ValueError ( " No default for argument " , arg )
2016-11-01 11:45:59 +01:00
def addSettingsArgs ( self , settings , prefix = None , * * defaults ) :
2016-10-31 18:12:39 +01:00
prefix = prefix or settings . __name__ [ : - len ( " Settings " ) ]
2016-11-01 11:45:59 +01:00
settings . parserArguments ( self . argparser , prefix , * * defaults )
2016-10-31 18:12:39 +01:00
self . edgesettings [ prefix ] = { }
2020-08-14 11:54:36 +02:00
2016-10-31 18:12:39 +01:00
2016-03-04 12:09:59 +01:00
def parseArgs ( self , args = None ) :
2016-03-28 16:55:41 +02:00
"""
Parse command line parameters
: param args : ( Default value = None ) parameters , None for using sys . argv
"""
2017-03-17 16:23:25 +01:00
if args is None :
args = sys . argv [ 1 : ]
if len ( args ) > 1 and args [ - 1 ] [ 0 ] != " - " :
2017-02-23 17:34:57 +01:00
self . inkscapefile = args [ - 1 ]
del args [ - 1 ]
args = [ a for a in args if not a . startswith ( ' --tab= ' ) ]
2021-06-25 22:12:42 +02:00
def cliquote ( s ) :
s = s . replace ( ' \r ' , ' ' )
s = s . replace ( ' \n ' , " \\ n " )
return quote ( s )
2023-01-13 15:32:32 +01:00
self . metadata [ " cli " ] = " boxes " + self . __class__ . __name__ + " " + " " . join ( cliquote ( arg ) for arg in args )
2023-02-20 22:22:26 +01:00
2016-08-17 15:07:41 +02:00
for key , value in vars ( self . argparser . parse_args ( args = args ) ) . items ( ) :
2023-02-20 22:22:26 +01:00
default = self . argparser . get_default ( key )
2020-08-14 11:54:36 +02:00
# treat edge settings separately
2016-10-31 18:12:39 +01:00
for setting in self . edgesettings :
if key . startswith ( setting + ' _ ' ) :
self . edgesettings [ setting ] [ key [ len ( setting ) + 1 : ] ] = value
continue
2016-06-21 22:08:31 +02:00
setattr ( self , key , value )
2023-02-20 22:22:26 +01:00
if ( value != default ) :
self . non_default_args [ key ] = value
2016-03-03 13:27:22 +01:00
2016-06-27 09:10:00 +02:00
# Change file ending to format if not given explicitly
2019-01-22 05:40:16 +01:00
format = getattr ( self , " format " , " svg " )
2016-06-27 09:10:00 +02:00
if getattr ( self , ' output ' , None ) == ' box.svg ' :
2019-01-22 05:40:16 +01:00
self . output = ' box. ' + format . split ( " _ " ) [ 0 ]
2016-06-27 09:10:00 +02:00
2023-02-20 22:22:26 +01:00
self . metadata [ " cli_short " ] = " boxes " + self . __class__ . __name__ + " " + " " . join ( cliquote ( arg ) for arg in args if ( arg . split ( " = " ) [ 0 ] [ 2 : ] in self . non_default_args ) )
2013-07-20 10:49:45 +02:00
def addPart ( self , part , name = None ) :
2016-03-28 16:55:41 +02:00
"""
Add Edge or other part instance to this one and add it as attribute
: param part : Callable
: param name : ( Default value = None ) attribute name ( __name__ as default )
"""
2013-07-20 10:49:45 +02:00
if name is None :
name = part . __class__ . __name__
name = name [ 0 ] . lower ( ) + name [ 1 : ]
2016-08-17 15:07:41 +02:00
# if not hasattr(self, name):
2016-04-17 18:04:23 +02:00
if isinstance ( part , edges . BaseEdge ) :
2013-07-20 10:49:45 +02:00
self . edges [ part . char ] = part
2016-04-02 20:52:27 +02:00
else :
setattr ( self , name , part )
2013-07-20 10:49:45 +02:00
2016-11-23 21:11:15 +01:00
def addParts ( self , parts ) :
for part in parts :
self . addPart ( part )
2013-07-20 10:49:45 +02:00
def _buildObjects ( self ) :
2023-01-02 00:32:42 +01:00
""" Add default edges and parts """
2013-07-20 10:49:45 +02:00
self . edges = { }
2016-03-25 14:02:52 +01:00
self . addPart ( edges . Edge ( self , None ) )
self . addPart ( edges . OutSetEdge ( self , None ) )
2016-11-23 21:11:15 +01:00
edges . GripSettings ( self . thickness ) . edgeObjects ( self )
2013-07-20 10:49:45 +02:00
2016-05-03 22:56:35 +02:00
# Finger joints
2013-07-20 10:49:45 +02:00
# Share settings object
2016-10-31 18:12:39 +01:00
s = edges . FingerJointSettings ( self . thickness , True ,
* * self . edgesettings . get ( " FingerJoint " , { } ) )
2016-11-23 21:11:15 +01:00
s . edgeObjects ( self )
2016-04-09 16:18:05 +02:00
self . addPart ( edges . FingerHoles ( self , s ) , name = " fingerHolesAt " )
2016-05-03 22:56:35 +02:00
# Stackable
2016-11-23 21:11:15 +01:00
edges . StackableSettings ( self . thickness , True ,
* * self . edgesettings . get ( " Stackable " , { } ) ) . edgeObjects ( self )
2016-05-03 22:56:35 +02:00
# Dove tail joints
2016-11-23 21:11:15 +01:00
edges . DoveTailSettings ( self . thickness , True ,
* * self . edgesettings . get ( " DoveTail " , { } ) ) . edgeObjects ( self )
2016-05-03 22:56:35 +02:00
# Flex
2016-11-01 13:30:32 +01:00
s = edges . FlexSettings ( self . thickness , True ,
* * self . edgesettings . get ( " Flex " , { } ) )
2016-03-25 14:02:52 +01:00
self . addPart ( edges . FlexEdge ( self , s ) )
2016-05-03 22:56:35 +02:00
# Clickable
2016-11-23 21:11:15 +01:00
edges . ClickSettings ( self . thickness , True ,
* * self . edgesettings . get ( " Click " , { } ) ) . edgeObjects ( self )
2016-05-25 19:59:49 +02:00
# Hinges
2016-11-23 21:11:15 +01:00
edges . HingeSettings ( self . thickness , True ,
* * self . edgesettings . get ( " Hinge " , { } ) ) . edgeObjects ( self )
2017-02-06 22:35:26 +01:00
edges . ChestHingeSettings ( self . thickness , True ,
* * self . edgesettings . get ( " ChestHinge " , { } ) ) . edgeObjects ( self )
2017-02-10 23:32:23 +01:00
edges . CabinetHingeSettings ( self . thickness , True ,
* * self . edgesettings . get ( " CabinetHinge " , { } ) ) . edgeObjects ( self )
2016-10-27 21:33:29 +02:00
# Sliding Lid
2016-11-23 21:11:15 +01:00
edges . LidSettings ( self . thickness , True ,
* * self . edgesettings . get ( " Lid " , { } ) ) . edgeObjects ( self )
2019-01-06 10:09:31 +01:00
# Rounded Triangle Edge
edges . RoundedTriangleEdgeSettings ( self . thickness , True ,
* * self . edgesettings . get ( " RoundedTriangleEdge " , { } ) ) . edgeObjects ( self )
2020-09-28 23:45:54 +02:00
# Grooved Edge
edges . GroovedSettings ( self . thickness , True ,
* * self . edgesettings . get ( " Grooved " , { } ) ) . edgeObjects ( self )
2022-05-25 23:31:55 +02:00
# Mounting Edge
edges . MountingSettings ( self . thickness , True ,
* * self . edgesettings . get ( " Mounting " , { } ) ) . edgeObjects ( self )
2022-06-16 14:59:36 +02:00
# Handle Edge
edges . HandleEdgeSettings ( self . thickness , True ,
* * self . edgesettings . get ( " HandleEdge " , { } ) ) . edgeObjects ( self )
2020-08-01 19:09:23 +02:00
# HexHoles
self . hexHolesSettings = HexHolesSettings ( self . thickness , True ,
* * self . edgesettings . get ( " HexHoles " , { } ) )
2016-05-03 22:56:35 +02:00
# Nuts
2013-07-25 14:22:55 +02:00
self . addPart ( NutHole ( self , None ) )
2016-07-11 14:59:11 +02:00
# Gears
self . addPart ( gears . Gears ( self ) )
2016-11-01 13:30:32 +01:00
s = edges . GearSettings ( self . thickness , True ,
* * self . edgesettings . get ( " Gear " , { } ) )
2016-10-12 22:47:08 +02:00
self . addPart ( edges . RackEdge ( self , s ) )
2016-07-13 09:36:49 +02:00
self . addPart ( pulley . Pulley ( self ) )
2016-07-15 18:29:14 +02:00
self . addPart ( parts . Parts ( self ) )
2013-07-25 14:22:55 +02:00
2016-07-09 22:52:03 +02:00
def adjustSize ( self , l , e1 = True , e2 = True ) :
2019-09-01 15:26:31 +02:00
# Char to edge object
e1 = self . edges . get ( e1 , e1 )
e2 = self . edges . get ( e2 , e2 )
2016-07-09 22:52:03 +02:00
try :
total = sum ( l )
2016-08-17 15:07:41 +02:00
walls = ( len ( l ) - 1 ) * self . thickness
2016-07-09 22:52:03 +02:00
except TypeError :
total = l
walls = 0
if isinstance ( e1 , edges . BaseEdge ) :
walls + = e1 . startwidth ( ) + e1 . margin ( )
elif e1 :
walls + = self . thickness
2016-08-17 15:07:41 +02:00
2016-07-09 22:52:03 +02:00
if isinstance ( e2 , edges . BaseEdge ) :
2019-09-01 15:26:31 +02:00
walls + = e2 . startwidth ( ) + e2 . margin ( )
2016-07-09 22:52:03 +02:00
elif e2 :
walls + = self . thickness
try :
2018-02-10 20:59:25 +01:00
if total > 0.0 :
factor = ( total - walls ) / total
else :
factor = 1.0
2016-08-17 15:07:41 +02:00
return [ s * factor for s in l ]
2016-07-09 22:52:03 +02:00
except TypeError :
return l - walls
2016-03-29 19:15:32 +02:00
def render ( self ) :
2023-01-02 00:32:42 +01:00
""" Implement this method in your subclass.
2016-03-29 19:15:32 +02:00
2023-01-02 00:32:42 +01:00
You will typically need to call . parseArgs ( ) before calling this one
"""
2021-11-03 13:13:38 +01:00
# Change settings and create new Edges and part classes here
2019-04-05 19:08:26 +02:00
raise NotImplementedError
2013-04-01 09:42:29 +02:00
2023-02-15 21:47:53 +01:00
def cc ( self , callback , number , x = 0.0 , y = None , a = 0.0 ) :
2016-03-28 16:55:41 +02:00
""" Call callback from edge of a part
: param callback : callback ( callable or list of callables )
: param number : number of the callback
: param x : ( Default value = 0.0 ) x position to be call on
: param y : ( Default value = None ) y position to be called on ( default does burn correction )
"""
2013-06-29 13:55:57 +02:00
if y is None :
y = self . burn
2016-08-17 15:07:41 +02:00
2016-10-25 19:31:15 +02:00
if hasattr ( callback , ' __getitem__ ' ) :
2013-04-16 04:27:48 +02:00
try :
callback = callback [ number ]
2016-10-25 19:31:15 +02:00
number = None
2013-04-16 05:21:38 +02:00
except ( KeyError , IndexError ) :
2013-04-16 04:27:48 +02:00
pass
2016-10-25 19:31:15 +02:00
if callback and callable ( callback ) :
2019-02-02 21:06:00 +01:00
with self . saved_context ( ) :
2023-02-15 21:47:53 +01:00
self . moveTo ( x , y , a )
2019-02-02 21:06:00 +01:00
if number is None :
callback ( )
else :
callback ( number )
2016-10-25 19:31:15 +02:00
self . ctx . move_to ( 0 , 0 )
2013-04-16 04:27:48 +02:00
2013-06-15 23:26:22 +02:00
def getEntry ( self , param , idx ) :
2016-03-28 16:55:41 +02:00
"""
Get entry from list or items itself
: param param : list or item
: param idx : index in list
"""
2013-06-15 23:26:22 +02:00
if isinstance ( param , list ) :
2016-08-17 15:07:41 +02:00
if len ( param ) > idx :
2013-06-15 23:26:22 +02:00
return param [ idx ]
else :
return None
else :
return param
2013-05-14 17:52:33 +02:00
2014-03-21 21:08:54 +01:00
def close ( self ) :
2016-03-28 16:55:41 +02:00
""" Finish rendering
2019-02-13 09:04:57 +01:00
Flush canvas to disk and convert output to requested format if needed .
Call after . render ( ) """
2022-12-28 18:14:58 +01:00
if self . ctx is None :
2019-02-08 17:32:04 +01:00
return
2014-03-21 21:08:54 +01:00
self . ctx . stroke ( )
2019-02-08 17:32:04 +01:00
self . ctx = None
2019-11-23 18:38:23 +01:00
2020-03-27 11:00:12 +01:00
self . surface . set_metadata ( self . metadata )
2014-03-21 21:08:54 +01:00
self . surface . flush ( )
2022-03-20 00:21:57 +01:00
self . surface . finish ( self . inner_corners )
2013-07-20 10:49:45 +02:00
2019-07-06 11:47:41 +02:00
self . formats . convert ( self . output , self . format , self . metadata )
2017-02-23 17:34:57 +01:00
if self . inkscapefile :
2017-05-14 21:47:49 +02:00
try :
out = sys . stdout . buffer
except AttributeError :
out = sys . stdout
svgutil . svgMerge ( self . output , self . inkscapefile , out )
2014-03-29 08:23:18 +01:00
2013-04-01 09:42:29 +02:00
############################################################
### Turtle graphics commands
############################################################
2017-12-19 21:58:13 +01:00
def corner ( self , degrees , radius = 0 , tabs = 0 ) :
2016-03-28 16:55:41 +02:00
"""
Draw a corner
This is what does the burn corrections
: param degrees : angle
: param radius : ( Default value = 0 )
"""
2016-11-02 21:50:44 +01:00
2019-03-24 21:45:45 +01:00
try :
degrees , radius = degrees
except :
pass
2016-08-17 15:07:41 +02:00
rad = degrees * math . pi / 180
2017-12-19 21:58:13 +01:00
if tabs and self . tabs :
if degrees > 0 :
r_ = radius + self . burn
2020-02-01 15:03:49 +01:00
tabrad = self . tabs / max ( r_ , 0.01 )
2017-12-19 21:58:13 +01:00
else :
r_ = radius - self . burn
2020-02-01 15:03:49 +01:00
tabrad = - self . tabs / max ( r_ , 0.01 )
2017-12-19 21:58:13 +01:00
length = abs ( r_ * rad )
2019-03-24 21:47:10 +01:00
tabs = min ( tabs , int ( length / / ( tabs * 3 * self . tabs ) ) )
if tabs and self . tabs :
2017-12-19 21:58:13 +01:00
l = ( length - tabs * self . tabs ) / tabs
lang = math . degrees ( l / r_ )
if degrees < 0 :
lang = - lang
2018-07-24 20:36:13 +02:00
#print(degrees, radius, l, lang, tabs, math.degrees(tabrad))
2017-12-19 21:58:13 +01:00
self . corner ( lang / 2. , radius )
for i in range ( tabs - 1 ) :
self . moveArc ( math . degrees ( tabrad ) , r_ )
self . corner ( lang , radius )
if tabs :
self . moveArc ( math . degrees ( tabrad ) , r_ )
self . corner ( lang / 2. , radius )
return
2020-04-26 17:56:20 +02:00
if ( ( radius > 0.5 * self . burn and abs ( degrees ) > 36 ) or
( abs ( degrees ) > 100 ) ) :
2017-12-19 21:58:13 +01:00
steps = int ( abs ( degrees ) / 36. ) + 1
for i in range ( steps ) :
self . corner ( float ( degrees ) / steps , radius )
return
2013-04-01 09:42:29 +02:00
if degrees > 0 :
2016-08-17 15:07:41 +02:00
self . ctx . arc ( 0 , radius + self . burn , radius + self . burn ,
- 0.5 * math . pi , rad - 0.5 * math . pi )
2013-06-15 23:26:22 +02:00
elif radius > self . burn :
2016-08-17 15:07:41 +02:00
self . ctx . arc_negative ( 0 , - ( radius - self . burn ) , radius - self . burn ,
0.5 * math . pi , rad + 0.5 * math . pi )
else : # not rounded inner corner
self . ctx . arc_negative ( 0 , self . burn - radius , self . burn - radius ,
- 0.5 * math . pi , - 0.5 * math . pi + rad )
2013-06-15 23:26:22 +02:00
2018-12-08 12:46:54 +01:00
self . _continueDirection ( rad )
2013-04-01 09:42:29 +02:00
2017-12-11 18:48:33 +01:00
def edge ( self , length , tabs = 0 ) :
2016-03-28 16:55:41 +02:00
"""
Simple line
: param length : length in mm
"""
2016-08-17 15:07:41 +02:00
self . ctx . move_to ( 0 , 0 )
2017-12-11 18:48:33 +01:00
if tabs and self . tabs :
if self . tabs > length :
self . ctx . move_to ( length , 0 )
else :
tabs = min ( tabs , max ( 1 , int ( length / / ( tabs * 3 * self . tabs ) ) ) )
l = ( length - tabs * self . tabs ) / tabs
self . ctx . line_to ( 0.5 * l , 0 )
for i in range ( tabs - 1 ) :
self . ctx . move_to ( ( i + 0.5 ) * l + self . tabs , 0 )
self . ctx . line_to ( ( i + 0.5 ) * l + self . tabs + l , 0 )
if tabs == 1 :
self . ctx . move_to ( ( tabs - 0.5 ) * l + self . tabs , 0 )
else :
self . ctx . move_to ( ( tabs - 0.5 ) * l + 2 * self . tabs , 0 )
self . ctx . line_to ( length , 0 )
else :
self . ctx . line_to ( length , 0 )
2013-04-01 09:42:29 +02:00
self . ctx . translate ( * self . ctx . get_current_point ( ) )
2020-02-03 16:59:19 +01:00
def step ( self , out ) :
"""
2023-01-02 00:32:42 +01:00
Create a parallel step perpendicular to the current direction
2020-02-03 16:59:19 +01:00
Positive values move to the outside of the part
"""
if out > 1E-5 :
self . corner ( - 90 )
self . edge ( out )
self . corner ( 90 )
elif out < - 1E-5 :
self . corner ( 90 )
self . edge ( - out )
self . corner ( - 90 )
2013-04-05 20:43:34 +02:00
def curveTo ( self , x1 , y1 , x2 , y2 , x3 , y3 ) :
2016-03-28 16:55:41 +02:00
""" control point 1, control point 2, end point
2020-08-14 11:54:36 +02:00
: param x1 :
: param y1 :
: param x2 :
: param y2 :
: param x3 :
: param y3 :
2016-03-28 16:55:41 +02:00
"""
2013-04-05 20:43:34 +02:00
self . ctx . curve_to ( x1 , y1 , x2 , y2 , x3 , y3 )
2016-08-17 15:07:41 +02:00
dx = x3 - x2
dy = y3 - y2
2013-04-05 20:43:34 +02:00
rad = math . atan2 ( dy , dx )
2018-12-08 12:46:54 +01:00
self . _continueDirection ( rad )
2013-04-05 20:43:34 +02:00
2014-04-20 21:54:33 +02:00
def polyline ( self , * args ) :
2016-03-28 16:55:41 +02:00
"""
Draw multiple connected lines
2023-01-02 00:32:42 +01:00
: param args : Alternating length in mm and angle in degrees .
2016-03-28 16:55:41 +02:00
2019-02-14 17:32:59 +01:00
lengths may be a tuple ( length , #tabs)
angles may be tuple ( angle , radius )
2016-03-28 16:55:41 +02:00
"""
2014-04-20 21:54:33 +02:00
for i , arg in enumerate ( args ) :
if i % 2 :
2016-05-25 19:29:35 +02:00
if isinstance ( arg , tuple ) :
self . corner ( * arg )
else :
self . corner ( arg )
2014-04-20 21:54:33 +02:00
else :
2017-12-11 18:48:33 +01:00
if isinstance ( arg , tuple ) :
self . edge ( * arg )
else :
self . edge ( arg )
2014-04-20 21:54:33 +02:00
2018-01-19 23:48:27 +01:00
def bedBoltHole ( self , length , bedBoltSettings = None , tabs = 0 ) :
2016-03-28 16:55:41 +02:00
"""
Draw an edge with slot for a bed bolt
: param length : length of the edge in mm
2023-01-02 00:32:42 +01:00
: param bedBoltSettings : ( Default value = None ) Dimensions of the slot
2016-03-28 16:55:41 +02:00
"""
2013-06-15 23:26:22 +02:00
d , d_nut , h_nut , l , l1 = bedBoltSettings or self . bedBoltSettings
2018-01-19 23:48:27 +01:00
self . edge ( ( length - d ) / 2.0 , tabs = tabs / / 2 )
2013-06-15 23:26:22 +02:00
self . corner ( 90 )
self . edge ( l1 )
self . corner ( 90 )
2016-08-17 15:07:41 +02:00
self . edge ( ( d_nut - d ) / 2.0 )
2013-06-15 23:26:22 +02:00
self . corner ( - 90 )
self . edge ( h_nut )
self . corner ( - 90 )
2016-08-17 15:07:41 +02:00
self . edge ( ( d_nut - d ) / 2.0 )
2013-06-15 23:26:22 +02:00
self . corner ( 90 )
2016-08-17 15:07:41 +02:00
self . edge ( l - l1 - h_nut )
2013-06-15 23:26:22 +02:00
self . corner ( - 90 )
self . edge ( d )
self . corner ( - 90 )
2016-08-17 15:07:41 +02:00
self . edge ( l - l1 - h_nut )
2013-06-15 23:26:22 +02:00
self . corner ( 90 )
2016-08-17 15:07:41 +02:00
self . edge ( ( d_nut - d ) / 2.0 )
2013-06-15 23:26:22 +02:00
self . corner ( - 90 )
self . edge ( h_nut )
self . corner ( - 90 )
2016-08-17 15:07:41 +02:00
self . edge ( ( d_nut - d ) / 2.0 )
2013-06-15 23:26:22 +02:00
self . corner ( 90 )
self . edge ( l1 )
self . corner ( 90 )
2018-01-19 23:48:27 +01:00
self . edge ( ( length - d ) / 2.0 , tabs = tabs - ( tabs / / 2 ) )
2013-06-15 23:26:22 +02:00
2016-04-26 11:54:37 +02:00
def edgeCorner ( self , edge1 , edge2 , angle = 90 ) :
2016-06-30 11:40:42 +02:00
""" Make a corner between two Edges. Take width of edges into account """
2017-02-12 12:00:01 +01:00
edge1 = self . edges . get ( edge1 , edge1 )
edge2 = self . edges . get ( edge2 , edge2 )
2014-04-06 15:35:10 +02:00
self . edge ( edge2 . startwidth ( ) * math . tan ( math . radians ( angle / 2. ) ) )
2016-04-26 11:54:37 +02:00
self . corner ( angle )
2014-04-06 15:35:10 +02:00
self . edge ( edge1 . endwidth ( ) * math . tan ( math . radians ( angle / 2. ) ) )
def regularPolygon ( self , corners = 3 , radius = None , h = None , side = None ) :
2021-11-03 13:13:38 +01:00
""" Give measures of a regular polygon
2019-02-07 17:42:41 +01:00
2021-11-03 13:13:38 +01:00
: param corners : number of corners of the polygon
2014-04-06 15:35:10 +02:00
: param radius : distance center to one of the corners
: param h : distance center to one of the sides ( height of sector )
: param side : length of one side
2019-02-13 09:01:20 +01:00
: return : ( radius , h , side )
2014-04-06 15:35:10 +02:00
"""
if radius :
side = 2 * math . sin ( math . radians ( 180.0 / corners ) ) * radius
h = radius * math . cos ( math . radians ( 180.0 / corners ) )
elif h :
side = 2 * math . tan ( math . radians ( 180.0 / corners ) ) * h
radius = ( ( side / 2. ) * * 2 + h * * 2 ) * * 0.5
elif side :
h = 0.5 * side * math . tan ( math . radians ( 90 - 180. / corners ) )
radius = ( ( side / 2. ) * * 2 + h * * 2 ) * * 0.5
return radius , h , side
@restore
def regularPolygonAt ( self , x , y , corners , angle = 0 , r = None , h = None , side = None ) :
2021-11-03 13:13:38 +01:00
""" Draw regular polygon """
2014-04-06 15:35:10 +02:00
self . moveTo ( x , y , angle )
r , h , side = self . regularPolygon ( corners , r , h , side )
self . moveTo ( - side / 2.0 , - h - self . burn )
for i in range ( corners ) :
self . edge ( side )
self . corner ( 360. / corners )
def regularPolygonWall ( self , corners = 3 , r = None , h = None , side = None ,
edges = ' e ' , hole = None , callback = None , move = None ) :
2021-11-03 13:13:38 +01:00
""" Create regular polygon as a wall
2019-02-07 17:42:41 +01:00
2021-11-03 13:13:38 +01:00
: param corners : number of corners of the polygon
2023-01-02 00:32:42 +01:00
: param r : radius distance center to one of the corners
2014-04-06 15:35:10 +02:00
: param h : distance center to one of the sides ( height of sector )
: param side : length of one side
: param edges : ( Default value = " e " , may be string / list of length corners )
: param hole : diameter of central hole ( Default value = 0 )
: param callback : ( Default value = None , middle = 0 , then sides = 1. . )
: param move : ( Default value = None )
"""
r , h , side = self . regularPolygon ( corners , r , h , side )
t = self . thickness
2020-08-16 14:35:27 +02:00
if not hasattr ( edges , " __getitem__ " ) or len ( edges ) == 1 :
edges = [ edges ] * corners
edges = [ self . edges . get ( e , e ) for e in edges ]
edges + = edges # append for wrapping around
2014-04-06 15:35:10 +02:00
if corners % 2 :
2020-08-16 14:35:27 +02:00
th = r + h + edges [ 0 ] . spacing ( ) + (
max ( edges [ corners / / 2 ] . spacing ( ) ,
edges [ corners / / 2 + 1 ] . spacing ( ) ) /
math . sin ( math . radians ( 90 - 180 / corners ) ) )
2014-04-06 15:35:10 +02:00
else :
2020-08-16 14:35:27 +02:00
th = 2 * h + edges [ 0 ] . spacing ( ) + edges [ corners / / 2 ] . spacing ( )
tw = 0
for i in range ( corners ) :
ang = ( 180 + 360 * i ) / corners
tw = max ( tw , 2 * abs ( math . sin ( math . radians ( ang ) ) *
( r + max ( edges [ i ] . spacing ( ) , edges [ i + 1 ] . spacing ( ) ) /
math . sin ( math . radians ( 90 - 180 / corners ) ) ) ) )
2014-04-06 15:35:10 +02:00
if self . move ( tw , th , move , before = True ) :
return
2020-08-16 14:35:27 +02:00
self . moveTo ( 0.5 * tw - 0.5 * side , edges [ 0 ] . margin ( ) )
2014-04-06 15:35:10 +02:00
if hole :
self . hole ( side / 2. , h + edges [ 0 ] . startwidth ( ) + self . burn , hole / 2. )
self . cc ( callback , 0 , side / 2. , h + edges [ 0 ] . startwidth ( ) + self . burn )
for i in range ( corners ) :
self . cc ( callback , i + 1 , 0 , edges [ i ] . startwidth ( ) + self . burn )
edges [ i ] ( side )
self . edgeCorner ( edges [ i ] , edges [ i + 1 ] , 360.0 / corners )
self . move ( tw , th , move )
2013-04-01 09:42:29 +02:00
2013-06-03 09:49:51 +02:00
def grip ( self , length , depth ) :
2023-01-02 00:32:42 +01:00
""" Corrugated edge useful as a gipping area
2016-03-28 16:55:41 +02:00
: param length : length
: param depth : depth of the grooves
"""
2019-11-22 16:26:38 +01:00
grooves = max ( int ( length / / ( depth * 2.0 ) ) + 1 , 1 )
2013-06-03 09:49:51 +02:00
depth = length / grooves / 4.0
2014-03-03 21:45:01 +01:00
for groove in range ( grooves ) :
2013-06-03 09:49:51 +02:00
self . corner ( 90 , depth )
self . corner ( - 180 , depth )
self . corner ( 90 , depth )
def _latchHole ( self , length ) :
2016-03-28 16:55:41 +02:00
"""
: param length :
"""
2016-08-17 15:07:41 +02:00
self . edge ( 1.1 * self . thickness )
2013-06-03 09:49:51 +02:00
self . corner ( - 90 )
2016-08-17 15:07:41 +02:00
self . edge ( length / 2.0 + 0.2 * self . thickness )
2013-06-03 09:49:51 +02:00
self . corner ( - 90 )
2016-08-17 15:07:41 +02:00
self . edge ( 1.1 * self . thickness )
2013-06-03 09:49:51 +02:00
def _latchGrip ( self , length ) :
2016-03-28 16:55:41 +02:00
"""
: param length :
"""
2016-08-17 15:07:41 +02:00
self . corner ( 90 , self . thickness / 4.0 )
self . grip ( length / 2.0 - self . thickness / 2.0 - 0.2 * self . thickness , self . thickness / 2.0 )
self . corner ( 90 , self . thickness / 4.0 )
2013-06-03 09:49:51 +02:00
def latch ( self , length , positive = True , reverse = False ) :
2016-03-28 16:55:41 +02:00
""" Latch to fix a flex box door to the box
: param length : length in mm
: param positive : ( Default value = True ) False : Door side ; True : Box side
: param reverse : ( Default value = False ) True when running away from the latch
2013-06-03 09:49:51 +02:00
"""
if positive :
if reverse :
2016-09-26 21:49:40 +02:00
self . edge ( length / 2.0 )
2013-06-03 09:49:51 +02:00
self . corner ( - 90 )
self . edge ( self . thickness )
self . corner ( 90 )
2016-08-17 15:07:41 +02:00
self . edge ( length / 2.0 )
2013-06-03 09:49:51 +02:00
self . corner ( 90 )
self . edge ( self . thickness )
self . corner ( - 90 )
if not reverse :
2016-09-26 21:49:40 +02:00
self . edge ( length / 2.0 )
2013-06-03 09:49:51 +02:00
else :
if reverse :
self . _latchGrip ( length )
else :
self . corner ( 90 )
self . _latchHole ( length )
if not reverse :
self . _latchGrip ( length )
else :
self . corner ( 90 )
2013-07-20 17:47:18 +02:00
def handle ( self , x , h , hl , r = 30 ) :
2016-03-28 16:55:41 +02:00
""" Creates an Edge with a handle
: param x : width in mm
: param h : height in mm
: param hl : height if th grip hole
: param r : ( Default value = 30 ) radius of the corners
"""
2016-08-17 15:07:41 +02:00
d = ( x - hl - 2 * r ) / 2.0
2013-04-16 05:07:26 +02:00
# Hole
2019-02-02 21:06:00 +01:00
with self . saved_context ( ) :
self . moveTo ( d + 2 * r , 0 )
self . edge ( hl - 2 * r )
self . corner ( - 90 , r )
self . edge ( h - 3 * r )
self . corner ( - 90 , r )
self . edge ( hl - 2 * r )
self . corner ( - 90 , r )
self . edge ( h - 3 * r )
self . corner ( - 90 , r )
2013-04-16 05:07:26 +02:00
2016-08-17 15:07:41 +02:00
self . moveTo ( 0 , 0 )
2013-04-16 05:07:26 +02:00
2016-08-17 15:07:41 +02:00
self . curveTo ( d , 0 , d , 0 , d , - h + r )
2013-04-16 05:07:26 +02:00
self . curveTo ( r , 0 , r , 0 , r , r )
self . edge ( hl )
self . curveTo ( r , 0 , r , 0 , r , r )
2016-08-17 15:07:41 +02:00
self . curveTo ( h - r , 0 , h - r , 0 , h - r , - d )
2013-04-16 05:07:26 +02:00
2013-04-01 09:42:29 +02:00
### Navigation
2013-07-20 10:49:45 +02:00
def moveTo ( self , x , y = 0.0 , degrees = 0 ) :
2016-03-28 16:55:41 +02:00
"""
Move coordinate system to given point
: param x :
: param y : ( Default value = 0.0 )
: param degrees : ( Default value = 0 )
"""
2013-07-20 10:49:45 +02:00
self . ctx . move_to ( 0 , 0 )
2013-04-01 09:42:29 +02:00
self . ctx . translate ( x , y )
2016-08-17 15:07:41 +02:00
self . ctx . rotate ( degrees * math . pi / 180.0 )
2013-04-01 09:42:29 +02:00
self . ctx . move_to ( 0 , 0 )
2017-12-19 21:57:52 +01:00
def moveArc ( self , angle , r = 0.0 ) :
"""
: param angle :
: param r : ( Default value = 0.0 )
"""
if r < 0 :
r = - r
angle = - angle
rad = math . radians ( angle )
if angle > 0 :
self . moveTo ( r * math . sin ( rad ) ,
r * ( 1 - math . cos ( rad ) ) , angle )
else :
self . moveTo ( r * math . sin ( - rad ) ,
- r * ( 1 - math . cos ( rad ) ) , angle )
2018-12-08 12:46:54 +01:00
def _continueDirection ( self , angle = 0 ) :
2016-03-28 16:55:41 +02:00
"""
Set coordinate system to current position ( end point )
: param angle : ( Default value = 0 ) heading
"""
2013-04-01 09:42:29 +02:00
self . ctx . translate ( * self . ctx . get_current_point ( ) )
2013-04-01 18:32:07 +02:00
self . ctx . rotate ( angle )
2013-04-01 09:42:29 +02:00
2021-06-04 20:59:17 +02:00
def move ( self , x , y , where , before = False , label = " " ) :
2013-07-20 10:49:45 +02:00
""" Intended to be used by parts
2018-10-19 19:05:07 +02:00
where can be combinations of " up " or " down " , " left " or " right " , " only " ,
2020-05-01 14:05:31 +02:00
" mirror " and " rotated "
2013-07-20 10:49:45 +02:00
when " only " is included the move is only done when before is True
2023-01-02 00:32:42 +01:00
" mirror " will flip the part along the y - axis
2020-05-01 14:05:31 +02:00
" rotated " draws the parts rotated 90 counter clockwise
2013-07-20 10:49:45 +02:00
The function returns whether actual drawing of the part
2023-01-02 00:32:42 +01:00
should be omitted .
2016-03-28 16:55:41 +02:00
: param x : width of part
: param y : height of part
: param where : which direction to move
: param before : ( Default value = False ) called before or after part being drawn
2013-07-20 10:49:45 +02:00
"""
if not where :
2016-07-27 22:19:32 +02:00
where = " "
2013-07-20 10:49:45 +02:00
terms = where . split ( )
dontdraw = before and " only " in terms
2016-07-27 22:19:32 +02:00
x + = self . spacing
y + = self . spacing
2020-05-01 14:05:31 +02:00
if " rotated " in terms :
x , y = y , x
2013-07-20 10:49:45 +02:00
moves = {
" up " : ( 0 , y , False ) ,
2016-08-17 15:07:41 +02:00
" down " : ( 0 , - y , True ) ,
" left " : ( - x , 0 , True ) ,
" right " : ( x , 0 , False ) ,
" only " : ( 0 , 0 , None ) ,
2018-10-19 19:05:07 +02:00
" mirror " : ( 0 , 0 , None ) ,
2020-05-01 14:05:31 +02:00
" rotated " : ( 0 , 0 , None ) ,
2016-08-17 15:07:41 +02:00
}
2016-07-27 22:19:32 +02:00
if not before :
# restore position
self . ctx . restore ( )
2022-03-28 21:41:10 +02:00
if self . labels and label :
2021-06-04 20:59:17 +02:00
self . text ( label , x / 2 , y / 2 , align = " middle center " , color = Color . ANNOTATIONS , fontsize = 4 )
2022-12-04 16:54:51 +01:00
self . ctx . stroke ( )
2016-07-27 22:19:32 +02:00
2013-07-20 10:49:45 +02:00
for term in terms :
if not term in moves :
2014-03-03 21:45:01 +01:00
raise ValueError ( " Unknown direction: ' %s ' " % term )
2018-10-19 18:58:29 +02:00
mx , my , movebeforeprint = moves [ term ]
2013-07-20 10:49:45 +02:00
if movebeforeprint and before :
2018-10-19 18:58:29 +02:00
self . moveTo ( mx , my )
2013-07-20 10:49:45 +02:00
elif ( not movebeforeprint and not before ) or dontdraw :
2018-10-19 18:58:29 +02:00
self . moveTo ( mx , my )
2016-07-27 22:19:32 +02:00
if not dontdraw :
if before :
2022-06-05 21:46:20 +02:00
# paint debug rectangle
if self . debug :
with self . saved_context ( ) :
self . set_source_color ( Color . ANNOTATIONS )
self . ctx . rectangle ( 0 , 0 , x , y )
2016-07-27 22:19:32 +02:00
# save position
self . ctx . save ( )
2020-05-01 14:05:31 +02:00
if " rotated " in terms :
self . moveTo ( x , 0 , 90 )
x , y = y , x # change back for "mirror"
2018-10-19 19:05:07 +02:00
if " mirror " in terms :
self . moveTo ( x , 0 )
self . ctx . scale ( - 1 , 1 )
2016-08-17 15:07:41 +02:00
self . moveTo ( self . spacing / 2.0 , self . spacing / 2.0 )
2019-11-23 18:38:23 +01:00
self . ctx . new_part ( )
2013-07-20 10:49:45 +02:00
return dontdraw
2017-03-22 21:56:50 +01:00
@restore
def circle ( self , x , y , r ) :
"""
Draw a round disc
: param x : position
2023-01-02 00:32:42 +01:00
: param y : position
2017-03-22 21:56:50 +01:00
: param r : radius
"""
r + = self . burn
self . moveTo ( x + r , y )
a = 0
n = 10
da = 2 * math . pi / n
for i in range ( n ) :
self . ctx . arc ( - r , 0 , r , a , a + da )
a + = da
self . ctx . stroke ( )
2022-06-05 09:24:54 +02:00
@restore
@holeCol
2022-08-10 20:28:03 +02:00
def regularPolygonHole ( self , x , y , r = 0.0 , d = 0.0 , n = 6 , a = 0.0 , tabs = 0 , corner_radius = 0.0 ) :
2022-06-05 09:24:54 +02:00
"""
Draw a hole in shape of an n - edged regular polygon
: param x : position
2023-01-02 00:32:42 +01:00
: param y : position
2022-06-05 09:24:54 +02:00
: param r : radius
: param n : number of edges
: param a : rotation angle
"""
if not r :
r = d / 2.0
if n == 0 :
self . hole ( x , y , r = r , tabs = tabs )
return
2022-12-04 16:54:51 +01:00
2022-06-05 09:24:54 +02:00
if r < self . burn :
r = self . burn + 1E-9
r_ = r - self . burn
2022-12-04 16:54:51 +01:00
2022-08-10 20:28:03 +02:00
if corner_radius < self . burn :
corner_radius = self . burn
cr_ = corner_radius - self . burn
side_length = 2 * r_ * math . sin ( math . pi / n )
apothem = r_ * math . cos ( math . pi / n )
# the corner chord:
s = math . sqrt ( 2 * math . pow ( cr_ , 2 ) * ( 1 - math . cos ( 2 * math . pi / n ) ) )
# the missing portion of the rounded corner:
b = math . sin ( math . pi / n ) / math . sin ( 2 * math . pi / n ) * s
# the flat portion of the side:
flat_side_length = side_length - 2 * b
2022-12-04 16:54:51 +01:00
2022-06-05 09:24:54 +02:00
self . moveTo ( x , y , a )
self . moveTo ( r_ , 0 , 90 + 180 / n )
2022-08-10 20:28:03 +02:00
self . moveTo ( b , 0 , 0 )
for _ in range ( n ) :
self . edge ( flat_side_length )
self . corner ( 360 / n , cr_ )
2022-06-05 09:24:54 +02:00
2013-06-07 12:50:13 +02:00
@restore
2016-06-21 19:06:33 +02:00
@holeCol
2017-12-19 23:15:43 +01:00
def hole ( self , x , y , r = 0.0 , d = 0.0 , tabs = 0 ) :
2016-03-28 16:55:41 +02:00
"""
Draw a round hole
: param x : position
2023-01-02 00:32:42 +01:00
: param y : position
2016-03-28 16:55:41 +02:00
: param r : radius
"""
2017-11-15 17:18:01 +01:00
if not r :
r = d / 2.0
2017-12-19 23:15:43 +01:00
if r < self . burn :
r = self . burn + 1E-9
r_ = r - self . burn
self . moveTo ( x + r_ , y , - 90 )
self . corner ( - 360 , r , tabs )
2013-04-16 04:24:35 +02:00
2013-07-20 17:51:54 +02:00
@restore
2016-06-21 19:06:33 +02:00
@holeCol
2020-09-19 23:47:03 +02:00
def rectangularHole ( self , x , y , dx , dy , r = 0 , center_x = True , center_y = True ) :
2016-03-28 16:55:41 +02:00
"""
2020-09-19 23:47:03 +02:00
Draw a rectangular hole
2016-03-28 16:55:41 +02:00
2020-09-19 23:47:03 +02:00
: param x : position
: param y : position
2016-03-28 16:55:41 +02:00
: param dx : width
: param dy : height
: param r : ( Default value = 0 ) radius of the corners
2020-09-19 23:47:03 +02:00
: param center_x : ( Default value = True ) if True , x position is the center , else the start
: param center_y : ( Default value = True ) if True , y position is the center , else the start
2016-03-28 16:55:41 +02:00
"""
2018-12-08 12:42:25 +01:00
r = min ( r , dx / 2. , dy / 2. )
2020-09-19 23:47:03 +02:00
x_start = x if center_x else x + dx / 2.0
y_start = y - dy / 2.0 if center_y else y
self . moveTo ( x_start , y_start + self . burn , 180 )
2019-12-10 17:01:22 +01:00
self . edge ( dx / 2.0 - r ) # start with an edge to allow easier change of inner corners
for d in ( dy , dx , dy , dx / 2.0 + r ) :
2013-07-20 17:51:54 +02:00
self . corner ( - 90 , r )
2016-08-17 15:07:41 +02:00
self . edge ( d - 2 * r )
2013-07-20 17:51:54 +02:00
2017-05-05 11:24:08 +02:00
@restore
@holeCol
def dHole ( self , x , y , r = None , d = None , w = None , rel_w = 0.75 , angle = 0 ) :
2022-05-15 07:55:53 +02:00
"""
Draw a hole for a shaft with flat edge - D shaped hole
2022-05-25 23:41:12 +02:00
2022-05-15 07:55:53 +02:00
: param x : center position
: param y : center position
: param r : radius ( overrides d )
: param d : diameter
: param w : width measured against flat side in mm
: param rel_w : width in percent
2023-01-02 00:32:42 +01:00
: param angle : orientation ( rotation ) of the flat side
2022-05-15 07:55:53 +02:00
"""
2022-05-25 23:41:12 +02:00
2017-05-05 11:24:08 +02:00
if r is None :
r = d / 2.0
if w is None :
w = 2.0 * r * rel_w
w - = r
2020-02-01 15:30:18 +01:00
if r < = 0.0 :
2017-05-05 11:24:08 +02:00
return
if abs ( w ) > r :
return self . hole ( x , y , r )
a = math . degrees ( math . acos ( w / r ) )
self . moveTo ( x , y , angle - a )
self . moveTo ( r - self . burn , 0 , - 90 )
self . corner ( - 360 + 2 * a , r )
self . corner ( - a )
self . edge ( 2 * r * math . sin ( math . radians ( a ) ) )
2018-05-05 19:08:37 +02:00
@restore
@holeCol
def flatHole ( self , x , y , r = None , d = None , w = None , rel_w = 0.75 , angle = 0 ) :
2022-05-15 07:55:53 +02:00
"""
Draw a hole for a shaft with two opposed flat edges - ( ) shaped hole
2022-05-25 23:41:12 +02:00
2022-05-15 07:55:53 +02:00
: param x : center position
: param y : center position
: param r : radius ( overrides d )
: param d : diameter
: param w : width measured against flat side in mm
: param rel_w : width in percent
: param angle : orientation ( rotation ) of the flat sides
2022-05-25 23:41:12 +02:00
"""
2022-05-15 07:55:53 +02:00
2018-05-05 19:08:37 +02:00
if r is None :
r = d / 2.0
if w is None :
w = r * rel_w
else :
w = w / 2.0
if r < 0.0 :
return
if abs ( w ) > r :
return self . hole ( x , y , r )
a = math . degrees ( math . acos ( w / r ) )
self . moveTo ( x , y , angle - a )
self . moveTo ( r - self . burn , 0 , - 90 )
for i in range ( 2 ) :
self . corner ( - 180 + 2 * a , r )
self . corner ( - a )
self . edge ( 2 * r * math . sin ( math . radians ( a ) ) )
self . corner ( - a )
2022-05-15 07:55:53 +02:00
@restore
@holeCol
def mountingHole ( self , x , y , d_shaft , d_head = 0.0 , angle = 0 , tabs = 0 ) :
"""
Draw a pear shaped mounting hole for sliding over a screw head . Total height = 1.5 * d_shaft + d_head
: param x : position
2023-01-02 00:32:42 +01:00
: param y : position
2022-05-15 07:55:53 +02:00
: param d_shaft : diameter of the screw shaft
: param d_head : diameter of the screw head
: param angle : rotation angle of the hole
"""
2022-12-04 16:54:51 +01:00
2022-05-15 07:55:53 +02:00
if d_shaft < ( 2 * self . burn ) :
return # no hole if diameter is smaller then the capabilities of the machine
2022-12-04 16:54:51 +01:00
2022-05-15 07:55:53 +02:00
if not d_head or d_head < ( 2 * self . burn ) : # if no head diameter is given
self . hole ( x , y , d = d_shaft , tabs = tabs ) # only a round hole is generated
return
2022-12-04 16:54:51 +01:00
2022-05-15 07:55:53 +02:00
rs = d_shaft / 2
rh = d_head / 2
2022-12-04 16:54:51 +01:00
2022-05-15 07:55:53 +02:00
self . moveTo ( x , y , angle )
self . moveTo ( 0 , rs - self . burn , 0 )
self . corner ( - 180 , rs , tabs )
self . edge ( 2 * rs , tabs )
a = math . degrees ( math . asin ( rs / rh ) )
self . corner ( 90 - a , 0 , tabs )
self . corner ( - 360 + 2 * a , rh , tabs )
self . corner ( 90 - a , 0 , tabs )
self . edge ( 2 * rs , tabs )
2014-01-21 22:44:22 +01:00
@restore
2022-03-27 10:40:38 +02:00
def text ( self , text , x = 0 , y = 0 , angle = 0 , align = " " , fontsize = 10 , color = [ 0.0 , 0.0 , 0.0 ] , font = " Arial " ) :
2016-03-28 16:55:41 +02:00
"""
Draw text
: param text : text to render
: param x : ( Default value = 0 )
: param y : ( Default value = 0 )
: param angle : ( Default value = 0 )
: param align : ( Default value = " " ) string with combinations of ( top | middle | bottom ) and ( left | center | right ) separated by a space
"""
2014-01-21 22:44:22 +01:00
self . moveTo ( x , y , angle )
2017-11-22 21:20:10 +01:00
text = text . split ( " \n " )
lines = len ( text )
2020-04-20 23:38:00 +02:00
height = lines * fontsize + ( lines - 1 ) * 0.4 * fontsize
2014-01-21 22:44:22 +01:00
align = align . split ( )
2020-04-20 23:38:00 +02:00
halign = " left "
2014-01-21 22:44:22 +01:00
moves = {
2020-04-20 23:38:00 +02:00
" top " : - height ,
" middle " : - 0.5 * height ,
" bottom " : 0 ,
2020-07-04 23:41:50 +02:00
" left " : " left " ,
2020-04-20 23:38:00 +02:00
" center " : " middle " ,
" right " : " end " ,
2014-01-21 22:44:22 +01:00
}
for a in align :
if a in moves :
2020-04-20 23:38:00 +02:00
if isinstance ( moves [ a ] , str ) :
halign = moves [ a ]
else :
self . moveTo ( 0 , moves [ a ] )
2014-01-21 22:44:22 +01:00
else :
raise ValueError ( " Unknown alignment: %s " % align )
2017-11-22 21:20:10 +01:00
for line in reversed ( text ) :
2022-03-27 10:40:38 +02:00
self . ctx . show_text ( line , fs = fontsize , align = halign , rgb = color , font = font )
2020-04-20 23:38:00 +02:00
self . moveTo ( 0 , 1.4 * fontsize )
2014-01-21 22:44:22 +01:00
2017-04-26 22:32:34 +02:00
tx_sizes = {
1 : 0.61 ,
2 : 0.70 ,
3 : 0.82 ,
4 : 0.96 ,
5 : 1.06 ,
6 : 1.27 ,
7 : 1.49 ,
8 : 1.75 ,
9 : 1.87 ,
10 : 2.05 ,
15 : 2.40 ,
20 : 2.85 ,
25 : 3.25 ,
30 : 4.05 ,
40 : 4.85 ,
45 : 5.64 ,
50 : 6.45 ,
55 : 8.05 ,
60 : 9.60 ,
70 : 11.20 ,
80 : 12.80 ,
90 : 14.40 ,
100 : 16.00 ,
}
@restore
@holeCol
def TX ( self , size , x = 0 , y = 0 , angle = 0 ) :
2018-08-23 22:28:22 +02:00
""" Draw a star pattern
: param size : 1 to 100
: param x : ( Default value = 0 )
: param y : ( Default value = 0 )
: param angle : ( Default value = 0 )
"""
2017-04-26 22:32:34 +02:00
self . moveTo ( x , y , angle )
size = self . tx_sizes . get ( size , 0 )
ri = 0.5 * size * math . tan ( math . radians ( 30 ) )
ro = ri * ( 2 * * 0.5 - 1 )
self . moveTo ( size * 0.5 - self . burn , 0 , - 90 )
for i in range ( 6 ) :
self . corner ( 45 , ri )
self . corner ( - 150 , ro )
self . corner ( 45 , ri )
2017-03-29 07:38:10 +02:00
nema_sizes = {
# motor,flange, holes, screws
8 : ( 20.3 , 16 , 15.4 , 3 ) ,
11 : ( 28.2 , 22 , 23 , 4 ) ,
14 : ( 35.2 , 22 , 26 , 4 ) ,
16 : ( 39.2 , 22 , 31 , 4 ) ,
17 : ( 42.2 , 22 , 31 , 4 ) ,
23 : ( 56.4 , 38.1 , 47.1 , 5.2 ) ,
24 : ( 60 , 36 , 49.8 , 5.1 ) ,
34 : ( 86.3 , 73 , 69.8 , 6.6 ) ,
42 : ( 110 , 55.5 , 89 , 8.5 ) ,
}
2013-07-20 17:52:28 +02:00
@restore
2020-08-04 19:30:58 +02:00
def NEMA ( self , size , x = 0 , y = 0 , angle = 0 , screwholes = None ) :
2016-03-28 16:55:41 +02:00
""" Draw holes for mounting a NEMA stepper motor
: param size : Nominal size in tenths of inches
: param x : ( Default value = 0 )
: param y : ( Default value = 0 )
: param angle : ( Default value = 0 )
2023-01-02 00:32:42 +01:00
: param screwholes :
2016-03-28 16:55:41 +02:00
"""
2017-03-29 07:38:10 +02:00
width , flange , holedistance , diameter = self . nema_sizes [ size ]
2020-08-04 19:30:58 +02:00
if screwholes :
diameter = screwholes
2013-07-21 23:01:35 +02:00
self . moveTo ( x , y , angle )
2013-08-31 20:04:47 +02:00
if self . debug :
self . rectangularHole ( 0 , 0 , width , width )
2016-08-17 15:07:41 +02:00
self . hole ( 0 , 0 , 0.5 * flange )
2013-07-20 17:52:28 +02:00
for x in ( - 1 , 1 ) :
for y in ( - 1 , 1 ) :
2016-08-17 15:07:41 +02:00
self . hole ( x * 0.5 * holedistance ,
y * 0.5 * holedistance ,
0.5 * diameter )
2013-07-20 17:52:28 +02:00
2023-02-20 22:24:25 +01:00
def qrcode ( self , content , box_size = 1.0 , color = Color . ETCHING , move = None ) :
q = qrcode . QRCode ( image_factory = BoxesQrCodeFactory , box_size = box_size * 10 )
q . add_data ( content )
m = q . get_matrix ( )
tw , th = len ( m ) * box_size , len ( m [ 0 ] ) * box_size
if self . move ( tw , th , move , True ) :
return
self . set_source_color ( color )
q . make_image ( ctx = self . ctx )
self . move ( tw , th , move )
2022-06-05 09:24:54 +02:00
@restore
def showBorderPoly ( self , border , color = Color . ANNOTATIONS ) :
"""
draw border polygon ( for debugging only )
: param border : array with coordinate [ ( x0 , y0 ) , ( x1 , y1 ) , . . . ] of the border polygon
2023-01-02 00:32:42 +01:00
: param color :
2022-06-05 09:24:54 +02:00
"""
self . set_source_color ( color )
self . ctx . save ( )
self . ctx . move_to ( * border [ 0 ] )
for x , y in border [ 1 : ] :
self . ctx . line_to ( x , y )
self . ctx . line_to ( * border [ 0 ] )
self . ctx . restore ( )
i = 0
for x , y in border :
i + = 1
self . hole ( x , y , 0.5 , color = color )
self . text ( str ( i ) , x , y , fontsize = 2 , color = color )
@restore
@holeCol
def fillHoles ( self , pattern , border , max_radius , hspace = 3 , bspace = 0 , min_radius = 0.5 , style = " round " , bar_length = 50 , max_random = 1000 ) :
"""
fill a polygon defined by its outline with holes
: param pattern : defines the hole pattern - currently " random " , " hex " , " square " " hbar " or " vbar " are supported
: param border : array with coordinate [ ( x0 , y0 ) , ( x1 , y1 ) , . . . ] of the border polygon
: param max_radius : maximum hole radius
: param hspace : space between holes
: param bspace : space to border
: param min_radius : minimum hole radius
: param style : defines hole style - currently one of " round " , " triangle " , " square " , " hexagon " or " octagon "
: param bar_length : maximum bar length
: param max_random : maximum number of random holes
"""
if pattern not in [ " random " , " hex " , " square " , " hbar " , " vbar " ] :
return
a = 0
if style == " round " :
n = 0
elif style == " triangle " :
n = 3
a = 60
elif style == " square " :
n = 4
elif style == " hexagon " :
n = 6
a = 30
elif style == " octagon " :
n = 8
a = 22.5
else :
raise ValueError ( " fillHoles - unknown hole style: %s ) " % style )
# note to myself: ^y x>
if self . debug :
self . showBorderPoly ( border )
borderPoly = Polygon ( border )
min_x , min_y , max_x , max_y = borderPoly . bounds
if pattern == " vbar " :
border = [ ( max_y - y + min_y , x ) for x , y in border ]
borderPoly = Polygon ( border )
min_x , min_y , max_x , max_y = borderPoly . bounds
self . moveTo ( 0 , max_x + min_x , - 90 )
pattern = " hbar "
if self . debug :
self . showBorderPoly ( border , color = Color . MAGENTA )
row = 0
i = 0
# calc the next smaller radius to fit an 'optimum' number of circles
# for x direction
nx = math . ceil ( ( max_x - min_x - 2 * bspace + hspace ) / ( 2 * max_radius + hspace ) )
max_radius_x = ( max_x - min_x - 2 * bspace - ( nx - 1 ) * hspace ) / nx / 2
# for y direction
if pattern == " hex " :
ny = math . ceil ( ( max_y - min_y - 2 * bspace - 2 * max_radius ) / ( math . sqrt ( 3 ) / 2 * ( 2 * max_radius + hspace ) ) )
max_radius_y = ( max_y - min_y - 2 * bspace - math . sqrt ( 3 ) / 2 * ny * hspace ) / ( math . sqrt ( 3 ) * ny + 2 )
else :
ny = math . ceil ( ( max_y - min_y - 2 * bspace + hspace ) / ( 2 * max_radius + hspace ) )
max_radius_y = ( max_y - min_y - 2 * bspace - ( ny - 1 ) * hspace ) / ny / 2
if pattern == " random " :
grid = { }
misses = 0 # in a row
while i < max_random and misses < 20 :
i + = 1
misses + = 1
# random new point
x = random . randrange ( math . floor ( min_x + bspace ) , math . ceil ( max_x - bspace ) ) # randomness takes longer to compute
y = random . randrange ( math . floor ( min_y + bspace ) , math . ceil ( max_y - bspace ) ) # but generates a new pattern for each run
pt = Point ( x , y ) . buffer ( min_radius + bspace )
# check if point is within border
if borderPoly . contains ( pt ) :
pt1 = Point ( x , y )
grid_x = int ( x / / ( 2 * max_radius + hspace ) )
grid_y = int ( y / / ( 2 * max_radius + hspace ) )
# compute distance between hole and border
bdist = borderPoly . exterior . distance ( pt1 ) - bspace
# compute minimum distance to all other holes
hdist = max_radius
try : # learned from https://medium.com/techtofreedom/5-ways-to-break-out-of-nested-loops-in-python-4c505d34ace7
for gx in ( - 1 , 0 , 1 ) :
for gy in ( - 1 , 0 , 1 ) :
for pt2 in grid . get ( ( grid_x + gx , grid_y + gy ) , [ ] ) :
pt3 = Point ( pt2 . x , pt2 . y )
hdist = min ( hdist , pt1 . distance ( pt3 ) - pt2 . z - hspace )
if hdist < min_radius :
hdist = 0
raise StopIteration
except StopIteration :
pass
# find maximum radius depending on distances
r = min ( bdist , hdist )
# if too small, dismiss cycle
if r < min_radius :
continue
# if too large, limit to max size
if r > max_radius :
r = max_radius
# store in grid with radius as z value
grid . setdefault ( ( grid_x , grid_y ) , [ ] ) . append (
Point ( x , y , r ) )
misses = 0
# and finally paint the hole
self . regularPolygonHole ( x , y , r = r , n = n , a = a )
# rinse and repeat
elif pattern in ( " square " , " hex " ) :
# use 'optimum' hole size
max_radius = min ( max_radius_x , max_radius_y )
# check if at least one line fits (we do horizontal filling)
if ( max_y - min_y ) < ( 2 * max_radius + 2 * bspace ) :
return
# make cutPolys a little wider to avoid
# overlapping with lines to be cut
outerCutPoly = borderPoly . buffer ( - 1 * ( bspace - 0.000001 ) ,
join_style = 2 )
outerTestPoly = borderPoly . buffer ( - 1 * ( bspace - 0.01 ) ,
join_style = 2 )
# shrink original polygon to get place for full size polygons
innerCutPoly = borderPoly . buffer ( - 1 * ( bspace + max_radius - 0.0001 ) , join_style = 2 )
innerTestPoly = borderPoly . buffer ( - 1 * ( bspace + max_radius - 0.001 ) , join_style = 2 )
# get left and right boundaries of cut polygon
x_cpl , y_cpl , x_cpr , y_cpr = outerCutPoly . bounds
if self . debug :
self . showBorderPoly ( list ( outerCutPoly . exterior . coords ) )
self . showBorderPoly ( list ( innerCutPoly . exterior . coords ) )
2022-12-04 16:54:51 +01:00
2022-06-05 09:24:54 +02:00
# set startpoint
y = min_y + bspace + max_radius_y
while y < ( max_y - bspace - max_radius_y ) :
if pattern == " square " or row % 2 == 0 :
xs = min_x + bspace + max_radius_x
else :
xs = min_x + max_radius_x * 2 + hspace / 2 + bspace
# create line segments cut by the polygons
line_complete = LineString ( [ ( x_cpl , y ) , ( max_x + 1 , y ) ] )
# cut accurate
outer_line_split = split ( line_complete , outerCutPoly )
line_complete = LineString ( [ ( x_cpl , y ) , ( max_x + 1 , y ) ] )
inner_line_split = split ( line_complete , innerCutPoly )
inner_line_index = 0
if self . debug and False :
for line in inner_line_split . geoms :
self . hole ( line . bounds [ 0 ] , line . bounds [ 1 ] , 1.1 )
self . hole ( line . bounds [ 2 ] , line . bounds [ 3 ] , .9 )
# process each line
for line_this in outer_line_split . geoms :
if self . debug and False : # enable to debug missing lines
x_start , y_start , x_end , y_end = line_this . bounds
with self . saved_context ( ) :
self . moveTo ( x_start , y_start , 0 )
self . hole ( 0 , 0 , 0.5 )
self . edge ( x_end - x_start )
with self . saved_context ( ) :
self . moveTo ( x_start , y_start , 0 )
self . text ( str ( outerTestPoly . contains ( line_this ) ) , 0 , 0 , fontsize = 2 , color = Color . ANNOTATIONS )
with self . saved_context ( ) :
self . moveTo ( x_end , y_end , 0 )
self . hole ( 0 , 0 , 0.5 )
if not outerTestPoly . contains ( line_this ) :
continue
x_start , y_start , x_end , y_end = line_this . bounds
#initialize walking x coordinate
xw = ( math . ceil ( ( x_start - xs ) / ( 2 * max_radius_x + hspace ) ) * ( 2 * max_radius_x + hspace ) ) + xs
# look up matching inner line
while ( inner_line_index < len ( inner_line_split ) and
( inner_line_split . geoms [ inner_line_index ] . bounds [ 2 ] < xw
or not innerTestPoly . contains ( inner_line_split . geoms [ inner_line_index ] ) ) ) :
inner_line_index + = 1
# and process line
while not xw > x_end :
2023-01-02 00:32:42 +01:00
# are we in inner polygon already?
2022-06-05 09:24:54 +02:00
if ( len ( inner_line_split ) > inner_line_index and
xw > inner_line_split . geoms [ inner_line_index ] . bounds [ 0 ] ) :
# place inner, full size polygons
while xw < inner_line_split . geoms [ inner_line_index ] . bounds [ 2 ] :
self . regularPolygonHole ( xw , y , r = max_radius , n = n , a = a )
xw + = ( 2 * max_radius_x + hspace )
# forward to next inner line
while ( inner_line_index < len ( inner_line_split ) and
( inner_line_split . geoms [ inner_line_index ] . bounds [ 0 ] < xw
or not innerTestPoly . contains ( inner_line_split . geoms [ inner_line_index ] ) ) ) :
inner_line_index + = 1
if xw > x_end :
break
# Check distance to border to size the polygon
pt = Point ( xw , y )
r = min ( borderPoly . exterior . distance ( pt ) - bspace ,
max_radius )
# if too small, dismiss
if r > = min_radius :
self . regularPolygonHole ( xw , y , r = r , n = n , a = a )
xw + = ( 2 * max_radius_x + hspace )
row + = 1
if pattern == " square " :
y + = 2 * max_radius_y + hspace - 0.0001
else :
y + = ( math . sqrt ( 3 ) / 2 * ( 2 * max_radius_y + hspace ) ) - 0.0001
elif pattern == " hbar " :
# 'optimum' hole size to be used
max_radius = max_radius_y
# check if at least one bar fits
if ( max_y - min_y ) < ( 2 * max_radius + 2 * bspace ) :
return
#shrink original polygon
shrinkPoly = borderPoly . buffer ( - 1 * ( bspace + max_radius - 0.01 ) , join_style = 2 )
cutPoly = borderPoly . buffer ( - 1 * ( bspace + max_radius - 0.000001 ) , join_style = 2 )
if self . debug :
self . showBorderPoly ( list ( shrinkPoly . exterior . coords ) )
segment_length = [ bar_length / 2 , bar_length ]
segment_max = 1
segment_toggle = False
# set startpoint
y = min_y + bspace + max_radius
# and calc step width
step_y = 2 * max_radius_y + hspace - 0.0001
while y < ( max_y - bspace - max_radius ) :
# toggle segment length each new line
if segment_toggle :
segment_max = 0
segment_toggle ^ = 1
2023-01-02 00:32:42 +01:00
# create line from left to right and cut according to shrunk polygon
2022-06-05 09:24:54 +02:00
line_complete = LineString ( [ ( min_x - 1 , y ) , ( max_x + 1 , y ) ] )
line_split = split ( line_complete , cutPoly )
# process each line
for line_this in line_split . geoms :
if self . debug and False : # enable to debug missing lines
x_start , y_start , x_end , y_end = line_this . bounds
with self . saved_context ( ) :
self . moveTo ( x_start , y_start , 0 )
self . hole ( 0 , 0 , 0.5 )
self . edge ( x_end - x_start )
with self . saved_context ( ) :
self . moveTo ( x_start , y_start , 0 )
self . text ( str ( shrinkPoly . contains ( line_this ) ) , 0 , 0 , fontsize = 2 , color = Color . ANNOTATIONS )
with self . saved_context ( ) :
self . moveTo ( x_end , y_end , 0 )
self . hole ( 0 , 0 , 0.5 )
if shrinkPoly . contains ( line_this ) :
# long segment are cut down further
if line_this . length > segment_length [ segment_max ] :
line_working = line_this
length = line_working . length
while length > 0 :
x_start , y_start , xw_end , yw_end = line_working . bounds
# calculate point with required distance from start point
p = line_working . interpolate ( segment_length [ segment_max ] )
# and use its coordinates as endpoint for this segment
x_end = p . x
y_end = p . y
# draw segment
self . set_source_color ( Color . INNER_CUT )
with self . saved_context ( ) :
self . moveTo ( x_start , y_start + max_radius , 0 )
self . edge ( x_end - x_start )
self . corner ( - 180 , max_radius )
self . edge ( x_end - x_start )
self . corner ( - 180 , max_radius )
if self . debug and False : # enable to debug cutting lines
self . set_source_color ( Color . ANNOTATIONS )
with self . saved_context ( ) :
self . moveTo ( x_start , y_start , 0 )
self . edge ( x_end - x_start )
s = " long - y: " + str ( round ( y , 1 ) ) + " xs: " + str ( round ( x_start , 1 ) ) + " xe: " + str ( round ( x_end , 1 ) ) + " l: " + str ( round ( length , 1 ) ) + " max: " + str ( round ( segment_length [ segment_max ] , 1 ) )
with self . saved_context ( ) :
self . text ( s , x_start , y_start , fontsize = 2 , color = Color . ANNOTATIONS )
# subtract length of segmant from total segment length
length - = ( x_end - x_start + hspace + 2 * max_radius )
# create remaining line to work with
line_working = LineString ( [ ( x_end + hspace + 2 * max_radius , y_end ) , ( xw_end , yw_end ) ] )
# next segment shall be long
segment_max = 1
else :
# short segment can be drawn instantly
x_start , y_start , x_end , y_end = line_this . bounds
self . set_source_color ( Color . INNER_CUT )
with self . saved_context ( ) :
self . moveTo ( x_start , y_start + max_radius , 0 )
self . edge ( x_end - x_start )
self . corner ( - 180 , max_radius )
self . edge ( x_end - x_start )
self . corner ( - 180 , max_radius )
if self . debug and False : # enable to debug short lines
self . set_source_color ( Color . ANNOTATIONS )
with self . saved_context ( ) :
self . moveTo ( x_start , y_start , 0 )
self . edge ( x_end - x_start )
s = " short - y: " + str ( round ( y , 1 ) ) + " xs: " + str ( round ( x_start , 1 ) ) + " xe: " + str ( round ( x_end , 1 ) ) + " l: " + str ( round ( line_this . length , 1 ) ) + " max: " + str ( round ( segment_length [ segment_max ] , 1 ) )
with self . saved_context ( ) :
self . text ( s , x_start , y_start , fontsize = 2 , color = Color . ANNOTATIONS )
segment_max = 1
# short segment shall be skipped if a short segment shall start the line
if segment_toggle :
segment_max = 0
y + = step_y
else :
raise ValueError ( " fillHoles - unknown hole pattern: %s ) " % pattern )
2013-04-18 04:44:33 +02:00
def hexHolesRectangle ( self , x , y , settings = None , skip = None ) :
2016-03-28 16:55:41 +02:00
""" Fills a rectangle with holes in a hex pattern.
Settings have :
2013-04-16 16:50:29 +02:00
r : radius of holes
b : space between holes
style : what types of holes ( not yet implemented )
2016-03-28 16:55:41 +02:00
: param x : width
2016-04-09 20:54:05 +02:00
: param y : height
2016-03-28 16:55:41 +02:00
: param settings : ( Default value = None )
: param skip : ( Default value = None ) function to check if hole should be present
2013-04-16 16:50:29 +02:00
gets x , y , r , b , posx , posy
"""
2020-08-14 11:54:36 +02:00
2013-04-18 04:44:33 +02:00
if settings is None :
settings = self . hexHolesSettings
2020-08-01 19:09:23 +02:00
r , b , style = settings . diameter / 2 , settings . distance , settings . style
2013-04-18 04:44:33 +02:00
2016-08-17 15:07:41 +02:00
w = r + b / 2.0
dist = w * math . cos ( math . pi / 6.0 )
2013-04-16 16:50:29 +02:00
# how many half circles do fit
2016-08-17 15:07:41 +02:00
cx = int ( ( x - 2 * r ) / / ( w ) ) + 2
cy = int ( ( y - 2 * r ) / / ( dist ) ) + 2
2013-04-16 16:50:29 +02:00
# what's left on the sides
2016-08-17 15:07:41 +02:00
lx = ( x - ( 2 * r + ( cx - 2 ) * w ) ) / 2.0
ly = ( y - ( 2 * r + ( ( cy / / 2 ) * 2 ) * dist - 2 * dist ) ) / 2.0
2013-04-16 16:50:29 +02:00
2016-08-17 15:07:41 +02:00
for i in range ( cy / / 2 ) :
for j in range ( ( cx - ( i % 2 ) ) / / 2 ) :
px = 2 * j * w + r + lx
py = i * 2 * dist + r + ly
2013-04-16 16:50:29 +02:00
if i % 2 :
2013-04-17 13:23:23 +02:00
px + = w
2013-04-16 16:50:29 +02:00
if skip and skip ( x , y , r , b , px , py ) :
continue
2020-08-14 12:40:36 +02:00
self . hole ( px , py , r = r )
2013-04-16 16:50:29 +02:00
def __skipcircle ( self , x , y , r , b , posx , posy ) :
2016-08-17 15:07:41 +02:00
cx , cy = x / 2.0 , y / 2.0
return ( dist ( posx - cx , posy - cy ) > ( cx - r ) )
2013-04-16 16:50:29 +02:00
2013-04-18 04:44:33 +02:00
def hexHolesCircle ( self , d , settings = None ) :
2016-03-28 16:55:41 +02:00
"""
Fill circle with holes in a hex pattern
: param d : diameter of the circle
: param settings : ( Default value = None )
"""
2016-08-17 15:07:41 +02:00
d2 = d / 2.0
2013-04-18 04:44:33 +02:00
self . hexHolesRectangle ( d , d , settings = settings , skip = self . __skipcircle )
2013-04-16 16:50:29 +02:00
2013-04-18 04:44:33 +02:00
def hexHolesPlate ( self , x , y , rc , settings = None ) :
2016-03-28 16:55:41 +02:00
"""
Fill a plate with holes in a hex pattern
: param x : width
: param y : height
: param rc : radius of the corners
: param settings : ( Default value = None )
"""
2016-08-17 15:07:41 +02:00
2013-04-17 13:25:30 +02:00
def skip ( x , y , r , b , posx , posy ) :
2016-03-28 16:55:41 +02:00
"""
2020-08-14 11:54:36 +02:00
: param x :
: param y :
: param r :
: param b :
: param posx :
: param posy :
2016-03-28 16:55:41 +02:00
"""
2016-08-17 15:07:41 +02:00
posx = abs ( posx - ( x / 2.0 ) )
posy = abs ( posy - ( y / 2.0 ) )
2013-04-17 13:25:30 +02:00
2016-08-17 15:07:41 +02:00
wx = 0.5 * x - rc - r
wy = 0.5 * y - rc - r
2013-04-17 13:25:30 +02:00
if ( posx < = wx ) or ( posy < = wx ) :
return 0
2016-08-17 15:07:41 +02:00
return dist ( posx - wx , posy - wy ) > rc
2013-04-18 04:44:33 +02:00
self . hexHolesRectangle ( x , y , settings , skip = skip )
2013-04-17 13:25:30 +02:00
2013-04-18 04:44:33 +02:00
def hexHolesHex ( self , h , settings = None , grow = None ) :
2016-03-28 16:55:41 +02:00
"""
Fill a hexagon with holes in a hex pattern
: param h : height
: param settings : ( Default value = None )
: param grow : ( Default value = None )
"""
2013-04-18 04:44:33 +02:00
if settings is None :
settings = self . hexHolesSettings
2020-08-01 19:09:23 +02:00
r , b , style = settings . diameter / 2 , settings . distance , settings . style
2013-04-16 04:24:35 +02:00
self . ctx . rectangle ( 0 , 0 , h , h )
2016-08-17 15:07:41 +02:00
w = r + b / 2.0
dist = w * math . cos ( math . pi / 6.0 )
cy = 2 * int ( ( h - 4 * dist ) / / ( 4 * w ) ) + 1
2013-04-16 04:24:35 +02:00
2016-08-17 15:07:41 +02:00
leftover = h - 2 * r - ( cy - 1 ) * 2 * r
if grow == ' space ' :
b + = leftover / ( cy - 1 ) / 2
2013-04-16 04:24:35 +02:00
2023-01-02 00:32:42 +01:00
# recalculate with adjusted values
2016-08-17 15:07:41 +02:00
w = r + b / 2.0
dist = w * math . cos ( math . pi / 6.0 )
2013-04-16 04:24:35 +02:00
2016-08-17 15:07:41 +02:00
self . moveTo ( h / 2.0 - ( cy / / 2 ) * 2 * w , h / 2.0 )
2014-03-03 21:45:01 +01:00
for j in range ( cy ) :
2016-08-17 15:07:41 +02:00
self . hole ( 2 * j * w , 0 , r )
for i in range ( 1 , cy / 2 + 1 ) :
for j in range ( cy - i ) :
self . hole ( j * 2 * w + i * w , i * 2 * dist , r )
self . hole ( j * 2 * w + i * w , - i * 2 * dist , r )
2013-04-16 04:24:35 +02:00
2016-07-12 16:12:28 +02:00
def flex2D ( self , x , y , width = 1 ) :
2018-08-23 22:35:52 +02:00
"""
Fill a rectangle with a pattern allowing bending in both axis
: param x : width
: param y : height
: param width : width between the lines of the pattern in multiples of thickness
"""
2016-07-12 16:12:28 +02:00
width * = self . thickness
2016-08-17 15:07:41 +02:00
cx = int ( x / / ( 5 * width ) )
cy = int ( y / / ( 5 * width ) )
2020-02-01 15:22:35 +01:00
if cx == 0 or cy == 0 :
return
wx = x / 5. / cx
2016-07-12 16:12:28 +02:00
wy = y / 5. / cy
2016-08-17 15:07:41 +02:00
armx = ( 4 * wx , 90 , 4 * wy , 90 , 2 * wx , 90 , 2 * wy )
army = ( 4 * wy , 90 , 4 * wx , 90 , 2 * wy , 90 , 2 * wx )
2016-07-12 16:12:28 +02:00
for i in range ( cx ) :
for j in range ( cy ) :
2016-08-17 15:07:41 +02:00
if ( i + j ) % 2 :
2019-02-02 21:06:00 +01:00
with self . saved_context ( ) :
self . moveTo ( ( 5 * i ) * wx , ( 5 * j ) * wy )
self . polyline ( * armx )
with self . saved_context ( ) :
self . moveTo ( ( 5 * i + 5 ) * wx , ( 5 * j + 5 ) * wy , - 180 )
self . polyline ( * armx )
2016-07-12 16:12:28 +02:00
else :
2019-02-02 21:06:00 +01:00
with self . saved_context ( ) :
self . moveTo ( ( 5 * i + 5 ) * wx , ( 5 * j ) * wy , 90 )
self . polyline ( * army )
with self . saved_context ( ) :
self . moveTo ( ( 5 * i ) * wx , ( 5 * j + 5 ) * wy , - 90 )
self . polyline ( * army )
2016-07-12 16:12:28 +02:00
self . ctx . stroke ( )
2020-05-10 15:26:34 +02:00
@restore
def fingerHoleRectangle ( self , dx , dy , x = 0. , y = 0. , angle = 0. , outside = False ) :
2020-05-10 16:13:54 +02:00
"""
Place finger holes for four walls - attaching a box on this plane
: param dx : size in x direction
: param dy : size in y direction
: param x : x position of the center
: param y : y position of the center
: param angle : angle in which the rectangle is placed
2023-01-02 00:32:42 +01:00
: param outside : measure size from the outside of the walls - not the inside
2020-05-10 16:13:54 +02:00
"""
2020-05-10 15:26:34 +02:00
self . moveTo ( x , y , angle )
d = 0.5 * self . thickness
if outside :
d = - d
self . fingerHolesAt ( dx / 2 + d , - dy / 2 , dy , 90 )
self . fingerHolesAt ( - dx / 2 - d , - dy / 2 , dy , 90 )
self . fingerHolesAt ( - dx / 2 , - dy / 2 - d , dx , 0 )
self . fingerHolesAt ( - dx / 2 , dy / 2 + d , dx , 0 )
2013-04-16 11:32:13 +02:00
##################################################
### parts
##################################################
2013-04-16 04:24:35 +02:00
2018-08-06 19:07:36 +02:00
def _splitWall ( self , pieces , side ) :
""" helper for roundedPlate and surroundingWall
figures out what sides to split
"""
return [
( False , False , False , False , True ) ,
( True , False , False , False , True ) ,
( True , False , True , False , True ) ,
( True , True , True , False , True ) ,
( True , True , True , True , True ) ,
] [ pieces ] [ side ]
2017-01-20 21:35:32 +01:00
def roundedPlate ( self , x , y , r , edge = " f " , callback = None ,
2013-06-15 23:26:22 +02:00
holesMargin = None , holesSettings = None ,
2013-07-20 10:49:45 +02:00
bedBolts = None , bedBoltSettings = None ,
2018-08-06 19:07:36 +02:00
wallpieces = 1 ,
2020-02-03 19:31:51 +01:00
extend_corners = True ,
2013-07-20 10:49:45 +02:00
move = None ) :
2016-03-28 16:55:41 +02:00
""" Plate with rounded corner fitting to .surroundingWall()
2018-08-06 19:07:36 +02:00
For the callbacks the sides are counted depending on wallpieces
2013-05-14 17:52:33 +02:00
2016-03-28 16:55:41 +02:00
: param x : width
2021-11-03 13:13:38 +01:00
: param y : height
2016-03-28 16:55:41 +02:00
: param r : radius of the corners
2023-01-02 00:32:42 +01:00
: param edge :
2016-03-28 16:55:41 +02:00
: param callback : ( Default value = None )
: param holesMargin : ( Default value = None ) set to get hex holes
: param holesSettings : ( Default value = None )
: param bedBolts : ( Default value = None )
: param bedBoltSettings : ( Default value = None )
2018-08-06 19:07:36 +02:00
: param wallpieces : ( Default value = 1 ) # of separate surrounding walls
2021-11-03 13:13:38 +01:00
: param extend_corners : ( Default value = True ) have corners outset with the edges
2016-03-28 16:55:41 +02:00
: param move : ( Default value = None )
2013-05-14 17:52:33 +02:00
"""
2020-02-03 19:31:51 +01:00
corner_holes = True
2013-05-14 17:52:33 +02:00
2020-02-03 19:31:51 +01:00
t = self . thickness
2020-02-03 17:03:23 +01:00
edge = self . edges . get ( edge , edge )
overallwidth = x + 2 * edge . spacing ( )
overallheight = y + 2 * edge . spacing ( )
2013-07-20 10:49:45 +02:00
if self . move ( overallwidth , overallheight , move , before = True ) :
return
2017-01-20 21:35:32 +01:00
lx = x - 2 * r
ly = y - 2 * r
2020-02-28 17:36:33 +01:00
self . moveTo ( edge . spacing ( ) ,
2020-02-03 17:03:23 +01:00
edge . margin ( ) )
2013-07-20 19:29:41 +02:00
self . moveTo ( r , 0 )
2013-07-20 10:49:45 +02:00
2018-08-06 19:07:36 +02:00
if wallpieces > 4 :
wallpieces = 4
wallcount = 0
for nr , l in enumerate ( ( lx , ly , lx , ly ) ) :
if self . _splitWall ( wallpieces , nr ) :
for i in range ( 2 ) :
2020-02-03 19:39:01 +01:00
self . cc ( callback , wallcount , y = edge . startwidth ( ) + self . burn )
2020-02-03 17:03:23 +01:00
edge ( l / 2.0 ,
bedBolts = self . getEntry ( bedBolts , wallcount ) ,
bedBoltSettings = self . getEntry ( bedBoltSettings , wallcount ) )
2018-08-06 19:07:36 +02:00
wallcount + = 1
else :
2020-02-03 19:39:01 +01:00
self . cc ( callback , wallcount , y = edge . startwidth ( ) + self . burn )
2020-02-03 17:03:23 +01:00
edge ( l ,
bedBolts = self . getEntry ( bedBolts , wallcount ) ,
bedBoltSettings = self . getEntry ( bedBoltSettings , wallcount ) )
2018-08-06 19:07:36 +02:00
wallcount + = 1
2020-02-03 19:31:51 +01:00
if extend_corners :
if corner_holes :
with self . saved_context ( ) :
self . moveTo ( 0 , edge . startwidth ( ) )
self . polyline ( 0 , ( 90 , r ) , 0 , - 90 , t , - 90 , 0 ,
( - 90 , r + t ) , 0 , - 90 , t , - 90 , 0 , )
self . ctx . stroke ( )
self . corner ( 90 , r + edge . startwidth ( ) )
else :
self . step ( - edge . endwidth ( ) )
self . corner ( 90 , r )
self . step ( edge . startwidth ( ) )
2013-05-14 17:52:33 +02:00
self . ctx . restore ( )
self . ctx . save ( )
2020-02-03 17:03:23 +01:00
self . moveTo ( edge . margin ( ) ,
edge . margin ( ) )
2013-07-20 19:29:41 +02:00
2013-05-14 17:52:33 +02:00
if holesMargin is not None :
self . moveTo ( holesMargin , holesMargin )
if r > holesMargin :
r - = holesMargin
else :
r = 0
2016-08-17 15:07:41 +02:00
self . hexHolesPlate ( x - 2 * holesMargin , y - 2 * holesMargin , r ,
2013-05-14 17:52:33 +02:00
settings = holesSettings )
2019-02-07 22:37:08 +01:00
2013-07-20 10:49:45 +02:00
self . move ( overallwidth , overallheight , move )
2013-04-16 04:27:48 +02:00
2021-12-17 21:27:06 +01:00
def surroundingWallPiece ( self , cbnr , x , y , r , pieces = 1 ) :
"""
Return the geometry of a pices of surroundingWall with the given
callback number .
2022-01-03 13:31:42 +01:00
: param cbnr : number of the callback corresponding to this part of the wall
2021-12-17 21:27:06 +01:00
: param x : width of matching roundedPlate
: param y : height of matching roundedPlate
: param r : corner radius of matching roundedPlate
: param pieces : ( Default value = 1 ) number of separate pieces
2022-01-03 13:31:42 +01:00
: return : ( left , length , right ) left and right are Booleans that are True if the start or end of the wall is on that side .
2021-12-17 21:27:06 +01:00
"""
if pieces < = 2 and ( y - 2 * r ) < 1E-3 :
# remove zero length y sides
sides = ( x / 2 - r , x - 2 * r , x - 2 * r )
if pieces > 0 : # hack to get the right splits
pieces + = 1
else :
sides = ( x / 2 - r , y - 2 * r , x - 2 * r , y - 2 * r , x - 2 * r )
wallcount = 0
for nr , l in enumerate ( sides ) :
if self . _splitWall ( pieces , nr ) and nr > 0 :
if wallcount == cbnr :
return ( False , l / 2 , True )
wallcount + = 1
if wallcount == cbnr :
return ( True , l / 2 , False )
wallcount + = 1
else :
if wallcount == cbnr :
return ( False , l , False )
wallcount + = 1
return ( False , 0.0 , False )
2013-04-16 04:27:48 +02:00
def surroundingWall ( self , x , y , r , h ,
2013-04-16 11:32:13 +02:00
bottom = ' e ' , top = ' e ' ,
2016-03-30 20:55:55 +02:00
left = " D " , right = " d " ,
2018-08-06 19:07:36 +02:00
pieces = 1 ,
2020-02-03 19:31:51 +01:00
extend_corners = True ,
2013-07-20 10:49:45 +02:00
callback = None ,
move = None ) :
2018-08-06 19:07:36 +02:00
"""
2023-01-02 00:32:42 +01:00
Wall ( s ) with flex filing around a roundedPlate ( )
2016-03-28 16:55:41 +02:00
2018-08-06 19:07:36 +02:00
For the callbacks the sides are counted depending on pieces
2016-03-28 16:55:41 +02:00
: param x : width of matching roundedPlate
: param y : height of matching roundedPlate
: param r : corner radius of matching roundedPlate
2020-08-14 11:54:36 +02:00
: param h : inner height of the wall ( without edges )
2016-03-28 16:55:41 +02:00
: param bottom : ( Default value = ' e ' ) Edge type
: param top : ( Default value = ' e ' ) Edge type
2018-08-06 19:07:36 +02:00
: param left : ( Default value = ' D ' ) left edge ( s )
: param right : ( Default value = ' d ' ) right edge ( s )
: param pieces : ( Default value = 1 ) number of separate pieces
2016-03-28 16:55:41 +02:00
: param callback : ( Default value = None )
: param move : ( Default value = None )
2013-04-16 04:27:48 +02:00
"""
2020-02-03 19:31:51 +01:00
t = self . thickness
2016-08-17 15:07:41 +02:00
c4 = ( r + self . burn ) * math . pi * 0.5 # circumference of quarter circle
2016-03-30 21:09:19 +02:00
c4 = c4 / self . edges [ " X " ] . settings . stretch
2013-07-20 10:49:45 +02:00
top = self . edges . get ( top , top )
bottom = self . edges . get ( bottom , bottom )
2016-03-30 20:55:55 +02:00
left = self . edges . get ( left , left )
right = self . edges . get ( right , right )
2013-07-20 10:49:45 +02:00
2016-04-18 22:05:11 +02:00
# XXX assumes startwidth == endwidth
2020-02-03 19:31:51 +01:00
if extend_corners :
topwidth = t
bottomwidth = t
else :
topwidth = top . startwidth ( )
bottomwidth = bottom . startwidth ( )
2013-07-20 10:49:45 +02:00
2018-08-06 19:07:36 +02:00
overallwidth = 2 * x + 2 * y - 8 * r + 4 * c4 + ( self . edges [ " d " ] . spacing ( ) + self . edges [ " D " ] . spacing ( ) + self . spacing ) * pieces
2021-09-21 23:01:53 +02:00
overallheight = h + max ( t , top . spacing ( ) ) + max ( t , bottom . spacing ( ) )
2013-07-20 10:49:45 +02:00
if self . move ( overallwidth , overallheight , move , before = True ) :
return
2016-04-26 11:54:37 +02:00
self . moveTo ( left . spacing ( ) , bottom . margin ( ) )
2013-04-16 04:27:48 +02:00
2018-08-06 19:07:36 +02:00
wallcount = 0
tops = [ ] # edges needed on the top for this wall segment
if pieces < = 2 and ( y - 2 * r ) < 1E-3 :
# remove zero length y sides
c4 * = 2
sides = ( x / 2 - r , x - 2 * r , x - 2 * r )
if pieces > 0 : # hack to get the right splits
pieces + = 1
2016-03-29 21:29:57 +02:00
else :
2018-08-06 19:07:36 +02:00
sides = ( x / 2 - r , y - 2 * r , x - 2 * r , y - 2 * r , x - 2 * r )
for nr , l in enumerate ( sides ) :
if self . _splitWall ( pieces , nr ) and nr > 0 :
self . cc ( callback , wallcount , y = bottomwidth + self . burn )
wallcount + = 1
bottom ( l / 2. )
tops . append ( l / 2. )
# complete wall segment
2019-02-02 21:06:00 +01:00
with self . saved_context ( ) :
self . edgeCorner ( bottom , right , 90 )
right ( h )
self . edgeCorner ( right , top , 90 )
for n , d in enumerate ( reversed ( tops ) ) :
if n % 2 : # flex
2020-02-03 19:31:51 +01:00
self . step ( topwidth - top . endwidth ( ) )
2019-02-02 21:06:00 +01:00
self . edge ( d )
2020-02-03 19:31:51 +01:00
self . step ( top . startwidth ( ) - topwidth )
2019-02-02 21:06:00 +01:00
else :
top ( d )
self . edgeCorner ( top , left , 90 )
left ( h )
self . edgeCorner ( left , bottom , 90 )
2018-08-06 19:07:36 +02:00
if nr == len ( sides ) - 1 :
break
# start new wall segment
tops = [ ]
self . moveTo ( right . margin ( ) + left . margin ( ) + self . spacing )
self . cc ( callback , wallcount , y = bottomwidth + self . burn )
wallcount + = 1
bottom ( l / 2. )
tops . append ( l / 2. )
else :
self . cc ( callback , wallcount , y = bottomwidth + self . burn )
wallcount + = 1
bottom ( l )
tops . append ( l )
2020-02-03 19:31:51 +01:00
self . step ( bottomwidth - bottom . endwidth ( ) )
2018-08-06 19:07:36 +02:00
self . edges [ " X " ] ( c4 , h + topwidth + bottomwidth )
2020-02-03 19:31:51 +01:00
self . step ( bottom . startwidth ( ) - bottomwidth )
2018-08-06 19:07:36 +02:00
tops . append ( c4 )
2013-04-16 04:27:48 +02:00
2013-07-20 10:49:45 +02:00
self . move ( overallwidth , overallheight , move )
2013-05-14 17:52:33 +02:00
def rectangularWall ( self , x , y , edges = " eeee " ,
2017-02-07 18:56:07 +01:00
ignore_widths = [ ] ,
2013-06-15 23:26:22 +02:00
holesMargin = None , holesSettings = None ,
2013-07-20 10:49:45 +02:00
bedBolts = None , bedBoltSettings = None ,
2013-07-20 17:54:07 +02:00
callback = None ,
2021-06-04 20:59:17 +02:00
move = None ,
label = " " ) :
2016-03-28 16:55:41 +02:00
"""
Rectangular wall for all kind of box like objects
: param x : width
: param y : height
: param edges : ( Default value = " eeee " ) bottom , right , top , left
2017-02-07 18:56:07 +01:00
: param ignore_widths : list of edge_widths added to adjacent edge
2016-03-28 16:55:41 +02:00
: param holesMargin : ( Default value = None )
: param holesSettings : ( Default value = None )
: param bedBolts : ( Default value = None )
: param bedBoltSettings : ( Default value = None )
: param callback : ( Default value = None )
: param move : ( Default value = None )
2021-06-04 20:59:17 +02:00
: param label : rendered to identify parts , it is not ment to be cut or etched ( Default value = " " )
2016-03-28 16:55:41 +02:00
"""
2013-05-14 17:52:33 +02:00
if len ( edges ) != 4 :
2014-03-03 21:45:01 +01:00
raise ValueError ( " four edges required " )
2013-11-26 22:53:06 +01:00
edges = [ self . edges . get ( e , e ) for e in edges ]
2016-08-17 15:07:41 +02:00
edges + = edges # append for wrapping around
2013-07-20 10:49:45 +02:00
overallwidth = x + edges [ - 1 ] . spacing ( ) + edges [ 1 ] . spacing ( )
overallheight = y + edges [ 0 ] . spacing ( ) + edges [ 2 ] . spacing ( )
if self . move ( overallwidth , overallheight , move , before = True ) :
return
2017-02-18 18:53:34 +01:00
if 7 not in ignore_widths :
self . moveTo ( edges [ - 1 ] . spacing ( ) )
2022-08-08 21:36:22 +02:00
self . moveTo ( 0 , edges [ 0 ] . margin ( ) )
2013-04-16 11:32:13 +02:00
for i , l in enumerate ( ( x , y , x , y ) ) :
2016-08-17 15:07:41 +02:00
self . cc ( callback , i , y = edges [ i ] . startwidth ( ) + self . burn )
2017-02-07 18:56:07 +01:00
e1 , e2 = edges [ i ] , edges [ i + 1 ]
if ( 2 * i - 1 in ignore_widths or
2 * i - 1 + 8 in ignore_widths ) :
l + = edges [ i - 1 ] . endwidth ( )
if 2 * i in ignore_widths :
l + = edges [ i + 1 ] . startwidth ( )
e2 = self . edges [ " e " ]
2022-03-27 10:40:38 +02:00
if 2 * i + 1 in ignore_widths :
2017-02-07 18:56:07 +01:00
e1 = self . edges [ " e " ]
2013-07-20 10:49:45 +02:00
edges [ i ] ( l ,
bedBolts = self . getEntry ( bedBolts , i ) ,
bedBoltSettings = self . getEntry ( bedBoltSettings , i ) )
2017-02-07 18:56:07 +01:00
self . edgeCorner ( e1 , e2 , 90 )
2013-04-01 22:20:22 +02:00
2013-05-14 17:52:33 +02:00
if holesMargin is not None :
2018-09-13 09:23:56 +02:00
self . moveTo ( holesMargin ,
2016-08-17 15:07:41 +02:00
holesMargin + edges [ 0 ] . startwidth ( ) )
2019-07-20 18:26:53 +02:00
self . hexHolesRectangle ( x - 2 * holesMargin , y - 2 * holesMargin , settings = holesSettings )
2016-07-27 22:19:32 +02:00
2021-06-04 20:59:17 +02:00
self . move ( overallwidth , overallheight , move , label = label )
2016-09-18 17:20:24 +02:00
2018-02-03 16:44:56 +01:00
def flangedWall ( self , x , y , edges = " FFFF " , flanges = None , r = 0.0 ,
2021-06-04 20:59:17 +02:00
callback = None , move = None , label = " " ) :
2018-02-03 16:44:56 +01:00
""" Rectangular wall with flanges extending the regular size
2021-12-04 16:02:25 +01:00
This is similar to the rectangularWall but it may extend to either
side . Sides with flanges may only have e , E , or F edges - the later
being replaced with fingerHoles .
2019-02-07 17:42:41 +01:00
2018-02-03 16:44:56 +01:00
: param x : width
: param y : height
: param edges : ( Default value = " FFFF " ) bottom , right , top , left
: param flanges : ( Default value = None ) list of width of the flanges
: param r : radius of the corners of the flange
: param callback : ( Default value = None )
: param move : ( Default value = None )
2022-12-04 16:54:51 +01:00
: param label : rendered to identify parts , it is not ment to be cut or etched ( Default value = " " )
2018-02-03 16:44:56 +01:00
"""
t = self . thickness
if not flanges :
flanges = [ 0.0 ] * 4
while len ( flanges ) < 4 :
flanges . append ( 0.0 )
2021-12-04 16:02:25 +01:00
edges = [ self . edges . get ( e , e ) for e in edges ]
# double to allow looping around
edges = edges + edges
flanges = flanges + flanges
2018-02-03 16:44:56 +01:00
2021-12-04 16:02:25 +01:00
tw = x + edges [ 1 ] . spacing ( ) + flanges [ 1 ] + edges [ 3 ] . spacing ( ) + flanges [ 3 ]
th = y + edges [ 0 ] . spacing ( ) + flanges [ 0 ] + edges [ 2 ] . spacing ( ) + flanges [ 2 ]
2018-02-03 16:44:56 +01:00
if self . move ( tw , th , move , True ) :
return
rl = min ( r , max ( flanges [ - 1 ] , flanges [ 0 ] ) )
2021-12-04 16:02:25 +01:00
self . moveTo ( rl + edges [ - 1 ] . margin ( ) , edges [ 0 ] . margin ( ) )
2018-02-03 16:44:56 +01:00
for i in range ( 4 ) :
l = y if i % 2 else x
rl = min ( r , max ( flanges [ i - 1 ] , flanges [ i ] ) )
rr = min ( r , max ( flanges [ i ] , flanges [ i + 1 ] ) )
self . cc ( callback , i , x = - rl )
if flanges [ i ] :
2022-07-19 09:25:17 +02:00
if edges [ i ] is self . edges [ " F " ] or edges [ i ] is self . edges [ " h " ] :
2022-12-21 22:36:52 +01:00
self . fingerHolesAt ( flanges [ i - 1 ] + edges [ i - 1 ] . endwidth ( ) - rl , 0.5 * t + flanges [ i ] , l ,
2018-02-03 16:44:56 +01:00
angle = 0 )
2021-12-04 16:02:25 +01:00
self . edge ( l + flanges [ i - 1 ] + flanges [ i + 1 ] + edges [ i - 1 ] . endwidth ( ) + edges [ i + 1 ] . startwidth ( ) - rl - rr )
2018-02-03 16:44:56 +01:00
else :
2021-12-04 16:02:25 +01:00
self . edge ( flanges [ i - 1 ] + edges [ i - 1 ] . endwidth ( ) - rl )
edges [ i ] ( l )
self . edge ( flanges [ i + 1 ] + edges [ i + 1 ] . startwidth ( ) - rr )
2018-02-03 16:44:56 +01:00
self . corner ( 90 , rr )
2021-06-04 20:59:17 +02:00
self . move ( tw , th , move , label = label )
2018-02-03 16:44:56 +01:00
2017-02-04 14:53:46 +01:00
def rectangularTriangle ( self , x , y , edges = " eee " , r = 0.0 , num = 1 ,
2016-09-18 17:20:24 +02:00
bedBolts = None , bedBoltSettings = None ,
callback = None ,
2021-06-04 20:59:17 +02:00
move = None ,
label = " " ) :
2016-09-18 17:20:24 +02:00
"""
Rectangular triangular wall
: param x : width
: param y : height
2016-09-26 18:33:22 +02:00
: param edges : ( Default value = " eee " ) bottom , right [ , diagonal ]
2023-01-02 00:32:42 +01:00
: param r : radius towards the hypotenuse
2016-09-26 18:33:22 +02:00
: param num : ( Default value = 1 ) number of triangles
2016-09-18 17:20:24 +02:00
: param bedBolts : ( Default value = None )
: param bedBoltSettings : ( Default value = None )
: param callback : ( Default value = None )
: param move : ( Default value = None )
2022-12-04 16:54:51 +01:00
: param label : rendered to identify parts , it is not ment to be cut or etched ( Default value = " " )
2016-09-18 17:20:24 +02:00
"""
edges = [ self . edges . get ( e , e ) for e in edges ]
2016-09-26 18:33:22 +02:00
if len ( edges ) == 2 :
edges . append ( self . edges [ " e " ] )
if len ( edges ) != 3 :
raise ValueError ( " two or three edges required " )
2017-02-05 20:56:36 +01:00
r = min ( r , x , y )
2021-04-17 11:44:34 +02:00
a = math . atan2 ( y - r , float ( x - r ) )
alpha = math . degrees ( a )
2022-07-30 23:37:54 +02:00
if a > 0 :
width = x + ( edges [ - 1 ] . spacing ( ) + self . spacing ) / math . sin ( a ) + edges [ 1 ] . spacing ( ) + self . spacing
else :
width = x + ( edges [ - 1 ] . spacing ( ) + self . spacing ) + edges [ 1 ] . spacing ( ) + self . spacing
2021-12-01 23:37:02 +01:00
height = y + edges [ 0 ] . spacing ( ) + edges [ 2 ] . spacing ( ) * math . cos ( a ) + 2 * self . spacing + self . spacing
2016-09-26 18:33:22 +02:00
if num > 1 :
2021-12-01 23:37:02 +01:00
width = 2 * width - x + r - self . spacing
dx = width - x - edges [ 1 ] . spacing ( ) - self . spacing / 2
dy = edges [ 0 ] . spacing ( ) + self . spacing / 2
2016-09-26 18:33:22 +02:00
2021-12-01 23:37:02 +01:00
overallwidth = width * ( num / / 2 + num % 2 ) - self . spacing
overallheight = height - self . spacing
2016-09-26 18:33:22 +02:00
2016-09-18 17:20:24 +02:00
if self . move ( overallwidth , overallheight , move , before = True ) :
return
2018-01-21 16:48:43 +01:00
if self . debug :
self . rectangularHole ( width / 2. , height / 2. , width , height )
2021-12-01 23:37:02 +01:00
self . moveTo ( dx - self . spacing / 2 , dy - self . spacing / 2 )
2016-09-26 18:33:22 +02:00
for n in range ( num ) :
for i , l in enumerate ( ( x , y ) ) :
self . cc ( callback , i , y = edges [ i ] . startwidth ( ) + self . burn )
edges [ i ] ( l ,
bedBolts = self . getEntry ( bedBolts , i ) ,
bedBoltSettings = self . getEntry ( bedBoltSettings , i ) )
2021-09-19 12:39:58 +02:00
if i == 0 :
self . edgeCorner ( edges [ i ] , edges [ i + 1 ] , 90 )
self . edgeCorner ( edges [ i ] , " e " , 90 )
2016-09-26 18:33:22 +02:00
2017-02-04 14:53:46 +01:00
self . corner ( alpha , r )
2016-09-26 18:33:22 +02:00
self . cc ( callback , 2 )
2021-09-19 12:39:58 +02:00
self . step ( edges [ 2 ] . startwidth ( ) )
2017-02-04 14:53:46 +01:00
edges [ 2 ] ( ( ( x - r ) * * 2 + ( y - r ) * * 2 ) * * 0.5 )
2021-09-19 12:39:58 +02:00
self . step ( - edges [ 2 ] . endwidth ( ) )
2017-02-04 14:53:46 +01:00
self . corner ( 90 - alpha , r )
self . corner ( 90 )
2016-09-26 18:33:22 +02:00
self . ctx . stroke ( )
2021-04-17 11:44:34 +02:00
self . moveTo ( width - 2 * dx , height - 2 * dy , 180 )
2016-09-26 18:33:22 +02:00
if n % 2 :
2021-04-17 11:44:34 +02:00
self . moveTo ( width )
2016-09-18 17:20:24 +02:00
2021-06-04 20:59:17 +02:00
self . move ( overallwidth , overallheight , move , label = label )
2016-10-01 22:10:38 +02:00
2019-06-21 23:16:59 +02:00
def trapezoidWall ( self , w , h0 , h1 , edges = " eeee " ,
2021-06-04 20:59:17 +02:00
callback = None , move = None ,
label = " " ) :
2019-06-20 15:22:44 +02:00
"""
Rectangular trapezoidal wall
: param w : width
: param h0 : left height
: param h1 : right height
: param edges : ( Default value = " eee " ) bottom , right , left
: param callback : ( Default value = None )
: param move : ( Default value = None )
2021-06-04 20:59:17 +02:00
: param label : rendered to identify parts , it is not ment to be cut or etched ( Default value = " " )
2019-06-20 15:22:44 +02:00
"""
edges = [ self . edges . get ( e , e ) for e in edges ]
overallwidth = w + edges [ - 1 ] . spacing ( ) + edges [ 1 ] . spacing ( )
overallheight = max ( h0 , h1 ) + edges [ 0 ] . spacing ( )
if self . move ( overallwidth , overallheight , move , before = True ) :
return
a = math . degrees ( math . atan ( ( h1 - h0 ) / w ) )
l = ( ( h0 - h1 ) * * 2 + w * * 2 ) * * 0.5
self . moveTo ( edges [ - 1 ] . spacing ( ) , edges [ 0 ] . margin ( ) )
self . cc ( callback , 0 , y = edges [ 0 ] . startwidth ( ) )
edges [ 0 ] ( w )
self . edgeCorner ( edges [ 0 ] , edges [ 1 ] , 90 )
self . cc ( callback , 1 , y = edges [ 1 ] . startwidth ( ) )
edges [ 1 ] ( h1 )
self . edgeCorner ( edges [ 1 ] , self . edges [ " e " ] , 90 )
self . corner ( a )
self . cc ( callback , 2 )
edges [ 2 ] ( l )
self . corner ( - a )
self . edgeCorner ( self . edges [ " e " ] , edges [ - 1 ] , 90 )
self . cc ( callback , 3 , y = edges [ - 1 ] . startwidth ( ) )
edges [ 3 ] ( h0 )
self . edgeCorner ( edges [ - 1 ] , edges [ 0 ] , 90 )
2021-06-04 20:59:17 +02:00
self . move ( overallwidth , overallheight , move , label = label )
2019-03-08 17:52:52 +01:00
2019-06-21 23:16:59 +02:00
def trapezoidSideWall ( self , w , h0 , h1 , edges = " eeee " ,
2021-06-04 20:59:17 +02:00
radius = 0.0 , callback = None , move = None ,
label = " " ) :
2019-06-21 23:16:59 +02:00
"""
Rectangular trapezoidal wall
: param w : width
: param h0 : left height
: param h1 : right height
: param edges : ( Default value = " eeee " ) bottom , right , left
2021-11-03 13:13:38 +01:00
: param radius : ( Default value = 0.0 ) radius of upper corners
2019-06-21 23:16:59 +02:00
: param callback : ( Default value = None )
: param move : ( Default value = None )
2021-06-04 20:59:17 +02:00
: param label : rendered to identify parts , it is not ment to be cut or etched ( Default value = " " )
2019-06-21 23:16:59 +02:00
"""
edges = [ self . edges . get ( e , e ) for e in edges ]
overallwidth = w + edges [ - 1 ] . spacing ( ) + edges [ 1 ] . spacing ( )
overallheight = max ( h0 , h1 ) + edges [ 0 ] . spacing ( )
if self . move ( overallwidth , overallheight , move , before = True ) :
return
r = min ( radius , abs ( h0 - h1 ) )
ws = w - r
if h0 > h1 :
ws + = edges [ 1 ] . endwidth ( )
else :
ws + = edges [ 3 ] . startwidth ( )
hs = abs ( h1 - h0 ) - r
a = math . degrees ( math . atan ( hs / ws ) )
l = ( ws * * 2 + hs * * 2 ) * * 0.5
self . moveTo ( edges [ - 1 ] . spacing ( ) , edges [ 0 ] . margin ( ) )
self . cc ( callback , 0 , y = edges [ 0 ] . startwidth ( ) )
edges [ 0 ] ( w )
self . edgeCorner ( edges [ 0 ] , edges [ 1 ] , 90 )
self . cc ( callback , 1 , y = edges [ 1 ] . startwidth ( ) )
edges [ 1 ] ( h1 )
if h0 > h1 :
self . polyline ( 0 , ( 90 - a , r ) )
self . cc ( callback , 2 )
edges [ 2 ] ( l )
self . polyline ( 0 , ( a , r ) , edges [ 3 ] . startwidth ( ) , 90 )
else :
self . polyline ( 0 , 90 , edges [ 1 ] . endwidth ( ) , ( a , r ) )
self . cc ( callback , 2 )
edges [ 2 ] ( l )
self . polyline ( 0 , ( 90 - a , r ) )
self . cc ( callback , 3 , y = edges [ - 1 ] . startwidth ( ) )
edges [ 3 ] ( h0 )
self . edgeCorner ( edges [ - 1 ] , edges [ 0 ] , 90 )
2023-01-16 12:16:13 +01:00
self . move ( overallwidth , overallheight , move , label = label )
2019-06-21 23:16:59 +02:00
2019-03-08 17:52:52 +01:00
### polygonWall and friends
2023-01-01 15:26:07 +01:00
def _polygonWallExtend ( self , borders , edges , close = False ) :
2019-03-08 17:52:52 +01:00
posx , posy = 0 , 0
ext = [ 0.0 ] * 4
angle = 0
def checkpoint ( ext , x , y ) :
ext [ 0 ] = min ( ext [ 0 ] , x )
ext [ 1 ] = min ( ext [ 1 ] , y )
ext [ 2 ] = max ( ext [ 2 ] , x )
ext [ 3 ] = max ( ext [ 3 ] , y )
2023-01-04 13:30:14 +01:00
# trace edge margins
nborders = [ ]
for i , val in enumerate ( borders ) :
if i % 2 :
nborders . append ( val )
else :
edge = edges [ ( i / / 2 ) % len ( edges ) ]
margin = edge . margin ( )
try :
l = val [ 0 ]
except TypeError :
l = val
if margin :
nborders . extend ( [ 0.0 , - 90 , margin , 90 , l , 90 , margin , - 90 , 0.0 ] )
else :
nborders . append ( val )
borders = nborders
2019-03-08 17:52:52 +01:00
for i in range ( len ( borders ) ) :
if i % 2 :
try :
a , r = borders [ i ]
except TypeError :
angle = ( angle + borders [ i ] ) % 360
continue
2019-04-07 17:27:29 +02:00
if a > 0 :
centerx = posx + r * math . cos ( math . radians ( angle + 90 ) )
centery = posy + r * math . sin ( math . radians ( angle + 90 ) )
else :
centerx = posx + r * math . cos ( math . radians ( angle - 90 ) )
centery = posy + r * math . sin ( math . radians ( angle - 90 ) )
2019-03-08 17:52:52 +01:00
for direction in ( 0 , 90 , 180 , 270 ) :
2019-04-07 17:27:29 +02:00
if ( a > 0 and
angle < = direction and ( angle + a ) % 360 > = direction ) :
direction - = 90
elif ( a < 0 and
angle > = direction and ( angle + a ) % 360 < = direction ) :
direction - = 90
else :
continue
checkpoint ( ext , centerx + r * math . cos ( math . radians ( direction ) ) , centery + r * math . sin ( math . radians ( direction ) ) )
#print("%4s %4s %4s %f %f" % (angle, direction+90, angle+a, centerx + r * math.cos(math.radians(direction)), centery + r * math.sin(math.radians(direction))))
2019-03-08 17:52:52 +01:00
angle = ( angle + a ) % 360
2019-04-07 17:27:29 +02:00
if a > 0 :
posx = centerx + r * math . cos ( math . radians ( angle - 90 ) )
posy = centery + r * math . sin ( math . radians ( angle - 90 ) )
else :
posx = centerx + r * math . cos ( math . radians ( angle + 90 ) )
posy = centery + r * math . sin ( math . radians ( angle + 90 ) )
2019-03-08 17:52:52 +01:00
else :
posx + = borders [ i ] * math . cos ( math . radians ( angle ) )
posy + = borders [ i ] * math . sin ( math . radians ( angle ) )
checkpoint ( ext , posx , posy )
return ext
2023-03-04 16:28:28 +01:00
def _closePolygon ( self , borders ) :
posx , posy = 0 , 0
angle = 0.0
if borders and borders [ - 1 ] is not None :
return borders
borders = borders [ : - 1 ]
for i in range ( len ( borders ) ) :
if i % 2 :
try :
a , r = borders [ i ]
except TypeError :
angle = ( angle + borders [ i ] ) % 360
continue
if a > 0 :
centerx = posx + r * math . cos ( math . radians ( angle + 90 ) )
centery = posy + r * math . sin ( math . radians ( angle + 90 ) )
else :
centerx = posx + r * math . cos ( math . radians ( angle - 90 ) )
centery = posy + r * math . sin ( math . radians ( angle - 90 ) )
angle = ( angle + a ) % 360
if a > 0 :
posx = centerx + r * math . cos ( math . radians ( angle - 90 ) )
posy = centery + r * math . sin ( math . radians ( angle - 90 ) )
else :
posx = centerx + r * math . cos ( math . radians ( angle + 90 ) )
posy = centery + r * math . sin ( math . radians ( angle + 90 ) )
else :
posx + = borders [ i ] * math . cos ( math . radians ( angle ) )
posy + = borders [ i ] * math . sin ( math . radians ( angle ) )
if len ( borders ) % 2 == 0 :
borders . append ( 0.0 )
a = math . degrees ( math . atan2 ( - posy , - posx ) )
#print(a, angle, a - angle)
borders . append ( ( a - angle + 360 ) % 360 )
borders . append ( ( posx * * 2 + posy * * 2 ) * * .5 )
borders . append ( - a )
#print(borders)
return borders
2019-04-07 17:28:40 +02:00
def polygonWall ( self , borders , edge = " f " , turtle = False ,
2023-04-01 16:58:06 +02:00
correct_corners = True ,
2022-12-03 23:30:00 +01:00
callback = None , move = None , label = " " ) :
"""
Polygon wall for all kind of multi - edged objects
: param borders : array of distance and angles to draw
2023-01-02 00:32:42 +01:00
: param edge : ( Default value = " f " ) Edges to apply . If the array of borders contains more segments that edges , the edge will wrap . Only edge types without start and end width supported for now .
2022-12-03 23:30:00 +01:00
: param turtle : ( Default value = False )
2023-04-01 16:58:06 +02:00
: param correct_corners : ( Default value = True )
2022-12-03 23:30:00 +01:00
: param callback : ( Default value = None )
: param move : ( Default value = None )
: param label : rendered to identify parts , it is not ment to be cut or etched ( Default value = " " )
2023-03-04 16:28:28 +01:00
borders is alternating between length of the edge and angle of the corner . For now neither tabs nor radii are supported . None at the end closes the polygon .
2022-12-03 23:30:00 +01:00
"""
try :
edges = [ self . edges . get ( e , e ) for e in edge ]
except TypeError :
edges = [ self . edges . get ( edge , edge ) ]
2019-03-08 17:52:52 +01:00
t = self . thickness # XXX edge.margin()
2023-03-04 16:28:28 +01:00
borders = self . _closePolygon ( borders )
2023-01-01 15:26:07 +01:00
minx , miny , maxx , maxy = self . _polygonWallExtend ( borders , edges )
2019-03-08 17:52:52 +01:00
tw , th = maxx - minx , maxy - miny
2019-04-07 17:28:40 +02:00
if not turtle :
if self . move ( tw , th , move , True ) :
return
2020-08-14 11:54:36 +02:00
2019-04-07 17:28:40 +02:00
self . moveTo ( - minx , - miny )
2019-03-08 17:52:52 +01:00
length_correction = 0.
for i in range ( 0 , len ( borders ) , 2 ) :
2023-02-14 14:34:52 +01:00
self . cc ( callback , i / / 2 )
2019-03-08 17:52:52 +01:00
self . edge ( length_correction )
l = borders [ i ] - length_correction
next_angle = borders [ i + 1 ]
2023-04-01 16:58:06 +02:00
if ( correct_corners and
isinstance ( next_angle , ( int , float ) ) and next_angle < 0 ) :
2023-01-13 15:32:32 +01:00
length_correction = t * math . tan ( math . radians ( - next_angle / 2 ) )
2019-03-08 17:52:52 +01:00
else :
length_correction = 0.0
l - = length_correction
2022-12-03 23:30:00 +01:00
edge = edges [ ( i / / 2 ) % len ( edges ) ]
edge ( l )
2019-03-08 17:52:52 +01:00
self . edge ( length_correction )
self . corner ( next_angle , tabs = 1 )
2019-04-07 17:28:40 +02:00
if not turtle :
2022-12-03 23:30:00 +01:00
self . move ( tw , th , move , label = label )
2019-03-08 17:52:52 +01:00
@restore
2023-01-02 16:23:24 +01:00
def polygonWalls ( self , borders , h , bottom = " F " , top = " F " , symmetrical = True ) :
2023-02-12 17:43:20 +01:00
if not borders :
return
2023-03-04 16:28:28 +01:00
borders = self . _closePolygon ( borders )
2019-03-08 17:52:52 +01:00
bottom = self . edges . get ( bottom , bottom )
top = self . edges . get ( top , top )
t = self . thickness # XXX edge.margin()
leftsettings = copy . deepcopy ( self . edges [ " f " ] . settings )
lf , lF , lh = leftsettings . edgeObjects ( self , add = False )
rightsettings = copy . deepcopy ( self . edges [ " f " ] . settings )
rf , rF , rh = rightsettings . edgeObjects ( self , add = False )
length_correction = 0.
angle = borders [ - 1 ]
i = 0
part_cnt = 0
self . moveTo ( 0 , bottom . margin ( ) )
while i < len ( borders ) :
2023-01-02 16:23:24 +01:00
if symmetrical :
2019-03-08 17:52:52 +01:00
if part_cnt % 2 :
left , right = lf , rf
else :
2020-05-02 00:43:33 +02:00
# last part of an uneven lot
if ( part_cnt == ( len ( borders ) / / 2 ) - 1 ) :
left , right = lF , rf
else :
left , right = lF , rF
2019-03-08 17:52:52 +01:00
else :
left , right = lf , rF
top_lengths = [ ]
top_edges = [ ]
self . moveTo ( left . spacing ( ) + self . spacing , 0 )
l = borders [ i ] - length_correction
leftsettings . setValues ( self . thickness , angle = angle )
angle = borders [ i + 1 ]
while isinstance ( angle , ( tuple , list ) ) :
bottom ( l )
angle , radius = angle
2019-04-07 17:29:32 +02:00
lr = abs ( math . radians ( angle ) * radius )
2019-03-08 17:52:52 +01:00
self . edges [ " X " ] ( lr , h + 2 * t ) # XXX
top_lengths . append ( l )
top_lengths . append ( lr )
top_edges . append ( top )
top_edges . append ( " E " )
i + = 2
l = borders [ i ]
angle = borders [ i + 1 ]
rightsettings . setValues ( self . thickness , angle = angle )
if angle < 0 :
2023-01-13 15:32:32 +01:00
length_correction = t * math . tan ( math . radians ( - angle / 2 ) )
2019-03-08 17:52:52 +01:00
else :
length_correction = 0.0
l - = length_correction
bottom ( l )
top_lengths . append ( l )
top_edges . append ( top )
with self . saved_context ( ) :
self . edgeCorner ( bottom , right , 90 )
right ( h )
self . edgeCorner ( right , top , 90 )
top_edges . reverse ( )
top_lengths . reverse ( )
edges . CompoundEdge ( self , top_edges , top_lengths ) ( sum ( top_lengths ) )
self . edgeCorner ( top , left , 90 )
left ( h )
self . edgeCorner ( left , bottom , 90 )
2020-10-16 11:36:20 +02:00
self . ctx . stroke ( )
2019-03-08 17:52:52 +01:00
self . moveTo ( right . spacing ( ) + self . spacing )
part_cnt + = 1
i + = 2
2016-10-01 22:10:38 +02:00
##################################################
### Place Parts
##################################################
def partsMatrix ( self , n , width , move , part , * l , * * kw ) :
2018-08-23 22:18:16 +02:00
""" place many of the same part
: param n : number of parts
2019-12-10 19:14:07 +01:00
: param width : number of parts in a row ( 0 for same as n )
2023-01-02 00:32:42 +01:00
: param move : ( Default value = " " )
2018-08-23 22:18:16 +02:00
: param part : callable that draws a part and knows move param
2023-01-02 00:32:42 +01:00
: param l : params for part
: param kw : keyword params for part
2018-08-23 22:18:16 +02:00
"""
2017-08-28 23:10:56 +02:00
if n < = 0 :
return
2019-12-10 19:14:07 +01:00
if not width :
width = n
2016-10-01 22:10:38 +02:00
rows = n / / width + ( 1 if n % width else 0 )
if not move :
move = " "
move = move . split ( )
#move down / left before
for m in move :
if m == " left " :
kw [ " move " ] = " left only "
for i in range ( width ) :
part ( * l , * * kw )
if m == " down " :
kw [ " move " ] = " down only "
for i in range ( rows ) :
part ( * l , * * kw )
# draw matrix
for i in range ( rows ) :
2019-02-02 21:06:00 +01:00
with self . saved_context ( ) :
for j in range ( width ) :
if " only " in move :
break
if width * i + j > = n :
break
kw [ " move " ] = " right "
part ( * l , * * kw )
2016-10-01 22:10:38 +02:00
kw [ " move " ] = " up only "
part ( * l , * * kw )
# Move back down
if " up " not in move :
kw [ " move " ] = " down only "
for i in range ( rows ) :
part ( * l , * * kw )
# Move right
if " right " in move :
kw [ " move " ] = " right only "
2017-08-07 17:08:16 +02:00
for i in range ( width ) :
2016-10-01 22:10:38 +02:00
part ( * l , * * kw )
2019-05-11 17:17:11 +02:00
def mirrorX ( self , f , offset = 0.0 ) :
""" Wrap a function to draw mirrored at the y axis
: param f : function to wrap
: param offset : ( default value = 0.0 ) axis to mirror at
"""
def r ( ) :
self . moveTo ( offset , 0 )
with self . saved_context ( ) :
self . ctx . scale ( - 1 , 1 )
f ( )
return r
def mirrorY ( self , f , offset = 0.0 ) :
""" Wrap a function to draw mirrored at the x axis
: param f : function to wrap
: param offset : ( default value = 0.0 ) axis to mirror at
"""
def r ( ) :
self . moveTo ( 0 , offset )
with self . saved_context ( ) :
self . ctx . scale ( 1 , - 1 )
f ( )
return r