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/>.
2013-04-01 09:42:29 +02:00
2016-07-11 23:01:07 +02:00
try :
import cairocffi
2016-08-17 15:07:41 +02:00
2016-07-11 23:01:07 +02:00
cairocffi . install_as_pycairo ( )
except ImportError :
pass
2013-04-01 09:42:29 +02:00
import cairo
import math
2017-02-23 17:34:57 +01:00
import sys
2016-03-03 13:27:22 +01:00
import argparse
2016-03-04 12:09:59 +01:00
from argparse import ArgumentParser
2016-03-03 13:27:22 +01:00
import re
2013-05-14 17:52:33 +02:00
from functools import wraps
2017-02-23 17:27:20 +01:00
from xml . sax . saxutils import quoteattr
2016-03-25 14:02:52 +01:00
from boxes import edges
2016-06-27 09:10:00 +02:00
from boxes import formats
2017-02-23 17:34:57 +01:00
from boxes import svgutil
2016-07-11 14:59:11 +02:00
from boxes import gears
2016-07-13 09:36:49 +02:00
from boxes import pulley
2016-07-15 18:29:14 +02:00
from boxes import parts
2013-04-01 09:42:29 +02:00
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
: param dy : delat y
"""
2016-08-17 15:07:41 +02:00
return ( dx * dx + dy * dy ) * * 0.5
2013-04-01 09:42:29 +02:00
2013-05-14 17:52:33 +02:00
def restore ( func ) :
2016-03-28 16:55:41 +02:00
"""
Wrapper : Restore coordiantes after function
: 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 ) :
self . ctx . save ( )
2013-06-07 12:50:13 +02:00
pt = self . ctx . get_current_point ( )
2013-05-14 17:52:33 +02:00
func ( self , * args , * * kw )
self . ctx . restore ( )
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 ) :
self . ctx . stroke ( )
self . ctx . set_source_rgb ( 0.0 , 0.0 , 1.0 )
func ( self , * args , * * kw )
self . ctx . stroke ( )
self . ctx . set_source_rgb ( 0.0 , 0.0 , 0.0 )
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
def __init__ ( self , boxes , settings ) :
self . boxes = boxes
self . ctx = boxes . ctx
self . settings = settings
@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
"""
2016-03-03 13:27:22 +01:00
m = re . match ( r " ( \ d+( \ . \ d+)?)/( \ d+) " , s )
if m :
n = int ( m . group ( 3 ) )
2016-08-17 15:07:41 +02:00
print ( [ float ( m . group ( 1 ) ) ] * n )
return [ float ( m . group ( 1 ) ) / n ] * n
2016-03-03 13:27:22 +01:00
m = re . match ( r " ( \ d+( \ . \ d+)?) \ *( \ d+) " , s )
if m :
n = int ( m . group ( 3 ) )
2016-08-17 15:07:41 +02:00
return [ float ( m . group ( 1 ) ) ] * n
2016-03-03 13:27:22 +01:00
try :
return [ float ( part ) for part in s . split ( " : " ) ]
except ValueError :
raise argparse . ArgumentTypeError ( " Don ' t understand sections string " )
2016-08-17 15:07:41 +02:00
2016-04-10 22:51:57 +02:00
class ArgparseEdgeType :
2016-04-17 14:26:27 +02:00
names = edges . getDescriptions ( )
2016-04-10 22:51:57 +02:00
edges = [ ]
def __init__ ( self , edges = None ) :
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
def html ( self , name , default ) :
options = " \n " . join (
( """ <option value= " %s " %s > %s %s </option> """ %
( e , ' selected= " selected " ' if e == default else " " ,
e , self . names . get ( e , " " ) ) for e in self . edges ) )
return """ <select name= " %s " size= " 1 " > \n %s </select> \n """ % ( name , options )
2017-02-23 17:27:20 +01:00
def inx ( self , name , viewname , arg ) :
return ( ' <param name= " %s " type= " enum " gui-text= " %s " gui-description= %s > \n ' %
( name , viewname , quoteattr ( arg . help or " " ) ) +
' ' . join ( ( ' <item value= " %s " > %s %s </item> \n ' % (
e , e , self . names . get ( e , " " ) )
for e in self . edges ) ) +
' </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
def html ( self , name , default ) :
return """ <input name= " %s " type= " hidden " value= " 0 " >
< input name = " %s " type = " checkbox " value = " 1 " % s > """ % \
( name , name , ' checked= " checked " ' if default else " " )
boolarg = BoolArg ( )
2016-08-17 15:07:41 +02:00
2016-04-10 22:51:25 +02:00
##############################################################################
### Main class
##############################################################################
2013-04-01 09:42:29 +02:00
class Boxes :
2016-03-28 16:55:41 +02:00
""" Main class -- Generator should sub class 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 "
2016-07-31 16:36:33 +02:00
2016-03-03 13:27:22 +01:00
def __init__ ( self ) :
2016-06-27 09:10:00 +02:00
self . formats = formats . Formats ( )
2016-03-08 09:33:45 +01:00
self . argparser = ArgumentParser ( description = self . __doc__ )
2016-10-31 18:12:39 +01:00
self . edgesettings = { }
2017-02-23 17:34:57 +01:00
self . inkscapefile = None
2016-11-01 16:58:48 +01:00
self . argparser . _action_groups [ 1 ] . title = self . __class__ . __name__ + " Settings "
2016-10-31 18:12:39 +01:00
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 ,
2016-03-03 13:27:22 +01:00
help = " thickness of the material " )
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 ( ) ,
help = " format of resulting file " )
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 ,
2016-03-03 13:27:22 +01:00
help = " print surrounding boxes for some structures " )
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 ,
2016-07-31 18:28:57 +02:00
help = " print reference rectangle with given length " )
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 ,
2016-10-20 20:07:23 +02:00
help = " burn correction in mm (bigger values for tighter fit) " )
2016-03-03 13:27:22 +01:00
2016-06-07 20:20:35 +02:00
def open ( self ) :
2016-03-28 16:55:41 +02:00
"""
Prepare for rendering
Call this function from your . render ( ) method
"""
2016-08-17 15:07:41 +02:00
self . spacing = 2 * self . burn + 0.5 * self . thickness
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
self . hexHolesSettings = ( 5 , 3 , ' circle ' ) # r, dist, style
2016-06-27 09:10:00 +02:00
self . surface , self . ctx = self . formats . getSurface ( self . format , self . output )
2016-08-17 15:07:41 +02:00
self . ctx . set_line_width ( 2 * self . burn )
2013-07-20 10:49:45 +02:00
self . _buildObjects ( )
2016-07-31 18:28:57 +02:00
if self . reference :
self . move ( 10 , 10 , " up " , before = True )
self . ctx . rectangle ( 0 , 0 , self . reference , 10 )
if self . reference < 40 :
2016-08-17 15:07:41 +02:00
self . text ( " % .fmm " % self . reference , self . reference + 5 , 5 ,
2016-07-31 18:28:57 +02:00
align = " middle left " )
else :
2016-08-17 15:07:41 +02:00
self . text ( " % .fmm " % self . reference , self . reference / 2.0 , 5 ,
2016-07-31 18:28:57 +02:00
align = " middle center " )
self . move ( 10 , 10 , " up " )
2017-02-23 17:29:18 +01:00
self . ctx . stroke ( )
2013-07-20 10:49:45 +02:00
2016-03-03 13:27:22 +01:00
def buildArgParser ( self , * l ) :
2016-03-28 16:55:41 +02:00
"""
Add commonly used commandf line parameters
: param \* l : parameter names
"""
2016-03-03 13:27:22 +01:00
for arg in l :
if arg == " x " :
self . argparser . add_argument (
2016-08-17 15:07:41 +02:00
" --x " , action = " store " , type = float , default = 100.0 ,
2016-03-03 13:27:22 +01:00
help = " inner width in mm " )
elif arg == " y " :
self . argparser . add_argument (
2016-08-17 15:07:41 +02:00
" --y " , action = " store " , type = float , default = 100.0 ,
2016-03-03 13:27:22 +01:00
help = " inner depth in mm " )
elif arg == " sx " :
self . argparser . add_argument (
2016-08-17 15:07:41 +02:00
" --sx " , action = " store " , type = argparseSections ,
2016-03-03 13:27:22 +01:00
default = " 50*3 " ,
2016-04-02 21:15:05 +02:00
help = """ sections left to right in mm. Possible formats: overallwidth/numberof sections e.g. " 250/5 " ; sectionwidth*numberofsections e.g. " 50*5 " ; section widths separated by " : " e.g. " 30:25.5:70 "
2016-03-03 13:27:22 +01:00
""" )
elif arg == " sy " :
self . argparser . add_argument (
2016-08-17 15:07:41 +02:00
" --sy " , action = " store " , type = argparseSections ,
2016-03-03 13:27:22 +01:00
default = " 50*3 " ,
2016-03-16 11:41:14 +01:00
help = """ sections back to front in mm. See --sx for format """ )
2016-03-03 13:27:22 +01:00
elif arg == " h " :
self . argparser . add_argument (
2016-08-17 15:07:41 +02:00
" --h " , action = " store " , type = float , default = 100.0 ,
2016-03-03 13:27:22 +01:00
help = " inner height in mm " )
elif arg == " hi " :
self . argparser . add_argument (
2016-08-17 15:07:41 +02:00
" --hi " , action = " store " , type = float , default = 0.0 ,
2016-03-08 22:27:55 +01:00
help = " inner height of inner walls in mm (leave to zero for same as outer walls) " )
2016-04-09 11:47:12 +02:00
elif arg == " bottom_edge " :
self . argparser . add_argument (
2016-08-17 15:07:41 +02:00
" --bottom_edge " , action = " store " ,
2016-04-19 21:02:05 +02:00
type = ArgparseEdgeType ( " Fhs " ) , choices = list ( " Fhs " ) ,
default = " h " ,
2016-04-09 11:47:12 +02:00
help = " edge type for bottom edge " )
elif arg == " top_edge " :
self . argparser . add_argument (
2016-08-17 15:07:41 +02:00
" --top_edge " , action = " store " ,
2017-02-11 17:03:49 +01:00
type = ArgparseEdgeType ( " ecESikvfL " ) , choices = list ( " ecESikvfL " ) ,
2016-04-19 21:02:05 +02:00
default = " e " , help = " edge type for top edge " )
2016-08-17 15:07:41 +02:00
elif arg == " outside " :
2016-07-09 22:52:03 +02:00
self . argparser . add_argument (
2016-12-18 23:28:15 +01:00
" --outside " , action = " store " , type = boolarg , default = True ,
2016-07-09 23:15:10 +02:00
help = " treat sizes as outside measurements that include the walls " )
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 ] = { }
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-02-23 17:34:57 +01:00
args = args or sys . argv
if args [ - 1 ] [ 0 ] != " - " :
self . inkscapefile = args [ - 1 ]
del args [ - 1 ]
args = [ a for a in args if not a . startswith ( ' --tab= ' ) ]
2016-08-17 15:07:41 +02:00
for key , value in vars ( self . argparser . parse_args ( args = args ) ) . items ( ) :
2016-10-31 18:12:39 +01:00
# treat edge settings separately
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 )
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
if getattr ( self , ' output ' , None ) == ' box.svg ' :
self . output = ' box. ' + getattr ( self , " format " , " svg " )
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 ) :
2016-03-28 16:55:41 +02: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 )
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 ) :
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 ) :
walls + = e2 . startwidth + e2 . margin ( )
elif e2 :
walls + = self . thickness
try :
2016-08-17 15:07:41 +02:00
factor = ( total - walls ) / total
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 ) :
""" Implement this method in your sub class.
You will typically need to call . parseArgs ( ) before calling this one """
2016-06-07 20:20:35 +02:00
self . open ( )
2016-03-29 19:15:32 +02:00
# Change settings and creat new Edges and part classes here
raise NotImplemented
self . close ( )
2013-04-01 09:42:29 +02:00
2013-06-29 13:55:57 +02:00
def cc ( self , callback , number , x = 0.0 , y = None ) :
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 ) :
self . ctx . save ( )
self . moveTo ( x , y )
if number is None :
callback ( )
else :
callback ( number )
self . ctx . restore ( )
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
Call at the end of your . render ( ) method """
2014-03-21 21:08:54 +01:00
self . ctx . stroke ( )
self . surface . flush ( )
self . surface . finish ( )
2013-07-20 10:49:45 +02:00
2016-06-27 09:10:00 +02:00
self . formats . convert ( self . output , self . format )
2017-02-23 17:34:57 +01:00
if self . inkscapefile :
2017-02-25 19:59:53 +01:00
svgutil . svgMerge ( self . output , self . inkscapefile , sys . stdout . buffer )
2014-03-29 08:23:18 +01:00
2013-04-01 09:42:29 +02:00
############################################################
### Turtle graphics commands
############################################################
def corner ( self , degrees , radius = 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
if radius > 0.5 * self . thickness :
while degrees > 100 :
self . corner ( 90 , radius )
degrees - = 90
while degrees < - 100 :
self . corner ( - 90 , radius )
degrees - = - 90
2016-08-17 15:07:41 +02:00
rad = degrees * math . pi / 180
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
2013-04-01 18:32:07 +02:00
self . continueDirection ( rad )
2013-04-01 09:42:29 +02:00
def edge ( self , length ) :
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 )
2013-04-01 09:42:29 +02:00
self . ctx . line_to ( length , 0 )
self . ctx . translate ( * self . ctx . get_current_point ( ) )
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
: param x1 :
: param y1 :
: param x2 :
: param y2 :
: param x3 :
: param y3 :
"""
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 )
self . continueDirection ( rad )
2014-04-20 21:54:33 +02:00
def polyline ( self , * args ) :
2016-03-28 16:55:41 +02:00
"""
Draw multiple connected lines
2016-05-25 19:29:35 +02:00
: param \* args : Alternating length in mm and angle . angle 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 :
self . edge ( arg )
2013-06-15 23:26:22 +02:00
def bedBoltHole ( self , length , bedBoltSettings = None ) :
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
: param bedBoltSettings : ( Default value = None ) Dimmensions of the slot
"""
2013-06-15 23:26:22 +02:00
d , d_nut , h_nut , l , l1 = bedBoltSettings or self . bedBoltSettings
2016-08-17 15:07:41 +02:00
self . edge ( ( length - d ) / 2.0 )
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 )
2016-08-17 15:07:41 +02:00
self . edge ( ( length - d ) / 2.0 )
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 ) :
""" Give messures of a regular polygone
: param corners : number of corners of the polygone
: 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
: return ( radius , h , side )
"""
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 ) :
""" Draw regular polygone """
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 ) :
""" Create regular polygone as a wall
: param corners : number of corners of the polygone
: 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
: 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
if corners % 2 :
th = r + h + 2 * t
else :
th = 2 * h + 2 * t
tw = 2 * r + 3 * t
if self . move ( tw , th , move , before = True ) :
return
self . moveTo ( r - 0.5 * side , 0 )
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
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 )
2016-12-17 11:37:11 +01:00
self . ctx . stroke ( )
2014-04-06 15:35:10 +02:00
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 ) :
2016-03-28 16:55:41 +02:00
""" Corrugated edge useful as an gipping area
: param length : length
: param depth : depth of the grooves
"""
2016-08-17 15:07:41 +02:00
grooves = int ( length / / ( depth * 2.0 ) ) + 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
if d < 0 :
2014-03-03 21:45:01 +01:00
print ( " Handle too wide " )
2013-04-16 05:07:26 +02:00
self . ctx . save ( )
# Hole
2016-08-17 15:07:41 +02:00
self . moveTo ( d + 2 * r , 0 )
self . edge ( hl - 2 * r )
2013-04-16 05:07:26 +02:00
self . corner ( - 90 , r )
2016-08-17 15:07:41 +02:00
self . edge ( h - 3 * r )
2013-04-16 05:07:26 +02:00
self . corner ( - 90 , r )
2016-08-17 15:07:41 +02:00
self . edge ( hl - 2 * r )
2013-04-16 05:07:26 +02:00
self . corner ( - 90 , r )
2016-08-17 15:07:41 +02:00
self . edge ( h - 3 * r )
2013-04-16 05:07:26 +02:00
self . corner ( - 90 , r )
self . ctx . restore ( )
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 )
2013-04-01 18:32:07 +02: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
2013-07-20 10:49:45 +02:00
def move ( self , x , y , where , before = False ) :
""" Intended to be used by parts
where can be combinations of " up " , " down " , " left " , " right " and " only "
when " only " is included the move is only done when before is True
The function returns whether actual drawing of the part
should be omited .
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
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 ) ,
}
2016-07-27 22:19:32 +02:00
if not before :
# restore position
self . ctx . restore ( )
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 )
2013-07-20 10:49:45 +02:00
x , y , movebeforeprint = moves [ term ]
if movebeforeprint and before :
self . moveTo ( x , y )
elif ( not movebeforeprint and not before ) or dontdraw :
self . moveTo ( x , y )
2016-07-27 22:19:32 +02:00
if not dontdraw :
if before :
# save position
self . ctx . save ( )
2016-08-17 15:07:41 +02:00
self . moveTo ( self . spacing / 2.0 , self . spacing / 2.0 )
2013-07-20 10:49:45 +02:00
return dontdraw
2013-06-07 12:50:13 +02:00
@restore
2016-06-21 19:06:33 +02:00
@holeCol
2013-04-16 04:24:35 +02:00
def hole ( self , x , y , r ) :
2016-03-28 16:55:41 +02:00
"""
Draw a round hole
: param x : position
: param y : postion
: param r : radius
"""
2016-03-30 20:52:21 +02:00
r - = self . burn
if r < 0 :
r = 1E-9
2016-08-17 15:07:41 +02:00
self . moveTo ( x + r , y )
2016-11-02 21:50:44 +01:00
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
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
2013-07-20 17:51:54 +02:00
def rectangularHole ( self , x , y , dx , dy , r = 0 ) :
2016-03-28 16:55:41 +02:00
"""
Draw an rectangulat hole
: param x : position
: param y : position
: param dx : width
: param dy : height
: param r : ( Default value = 0 ) radius of the corners
"""
2016-08-17 15:07:41 +02:00
self . moveTo ( x + r - dx / 2.0 , y - dy / 2.0 , 180 )
2013-07-20 17:51:54 +02:00
for d in ( dy , dx , dy , dx ) :
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
2014-01-21 22:44:22 +01:00
@restore
def text ( self , text , x = 0 , y = 0 , angle = 0 , align = " " ) :
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 )
( tx , ty , width , height , dx , dy ) = self . ctx . text_extents ( text )
align = align . split ( )
moves = {
2016-08-17 15:07:41 +02:00
" top " : ( 0 , - height ) ,
" middle " : ( 0 , - 0.5 * height ) ,
" bottom " : ( 0 , 0 ) ,
" left " : ( 0 , 0 ) ,
" center " : ( - 0.5 * width , 0 ) ,
" right " : ( - width , 0 ) ,
2014-01-21 22:44:22 +01:00
}
for a in align :
if a in moves :
self . moveTo ( * moves [ a ] )
else :
raise ValueError ( " Unknown alignment: %s " % align )
self . ctx . scale ( 1 , - 1 )
self . ctx . show_text ( text )
2013-07-20 17:52:28 +02:00
@restore
2013-07-21 23:01:35 +02:00
def NEMA ( self , size , x = 0 , y = 0 , angle = 0 ) :
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 )
"""
2013-07-20 17:52:28 +02:00
nema = {
# motor,flange, holes, screws
2016-08-17 15:07:41 +02:00
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
width , flange , holedistance , diameter = nema [ size ]
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
2013-04-18 04:44:33 +02:00
# hexHoles
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
2016-03-28 16:55:41 +02:00
2013-04-16 16:50:29 +02:00
"""
2013-04-18 04:44:33 +02:00
if settings is None :
settings = self . hexHolesSettings
r , b , style = settings
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
self . hole ( px , py , r )
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
"""
: param x :
: param y :
: param r :
: param b :
: param posx :
: param posy :
"""
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
r , b , style = settings
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
# recalulate 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 ) :
width * = self . thickness
2016-08-17 15:07:41 +02:00
cx = int ( x / / ( 5 * width ) )
2016-07-12 16:12:28 +02:00
wx = x / 5. / cx
2016-08-17 15:07:41 +02:00
cy = int ( y / / ( 5 * width ) )
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 :
2016-07-12 16:12:28 +02:00
self . ctx . save ( )
2016-08-17 15:07:41 +02:00
self . moveTo ( ( 5 * i ) * wx , ( 5 * j ) * wy )
2016-07-12 16:12:28 +02:00
self . polyline ( * armx )
self . ctx . restore ( )
self . ctx . save ( )
2016-08-17 15:07:41 +02:00
self . moveTo ( ( 5 * i + 5 ) * wx , ( 5 * j + 5 ) * wy , - 180 )
2016-07-12 16:12:28 +02:00
self . polyline ( * armx )
self . ctx . restore ( )
else :
self . ctx . save ( )
2016-08-17 15:07:41 +02:00
self . moveTo ( ( 5 * i + 5 ) * wx , ( 5 * j ) * wy , 90 )
2016-07-12 16:12:28 +02:00
self . polyline ( * army )
self . ctx . restore ( )
self . ctx . save ( )
2016-08-17 15:07:41 +02:00
self . moveTo ( ( 5 * i ) * wx , ( 5 * j + 5 ) * wy , - 90 )
2016-07-12 16:12:28 +02:00
self . polyline ( * army )
self . ctx . restore ( )
self . ctx . stroke ( )
2013-04-16 11:32:13 +02:00
##################################################
### parts
##################################################
2013-04-16 04:24:35 +02:00
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 ,
move = None ) :
2016-03-28 16:55:41 +02:00
""" Plate with rounded corner fitting to .surroundingWall()
First edge is split to have a joint in the middle of the side
2013-04-16 04:27:48 +02:00
callback is called at the beginning of the straight edges
2013-05-14 17:52:33 +02:00
0 , 1 for the two part of the first edge , 2 , 3 , 4 for the others
2016-03-28 16:55:41 +02:00
: param x : width
: param y : hight
: param r : radius of the corners
: 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 )
: param move : ( Default value = None )
2013-05-14 17:52:33 +02:00
"""
2017-01-20 21:35:32 +01:00
overallwidth = x + 2 * self . edges [ edge ] . spacing ( )
overallheight = y + 2 * self . edges [ 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
r + = self . edges [ edge ] . startwidth ( )
self . moveTo ( self . edges [ edge ] . margin ( ) ,
self . edges [ edge ] . margin ( ) )
2013-07-20 19:29:41 +02:00
self . moveTo ( r , 0 )
2013-07-20 10:49:45 +02:00
2013-04-16 04:27:48 +02:00
self . cc ( callback , 0 )
2017-01-20 21:35:32 +01:00
self . edges [ edge ] ( lx / 2.0 , bedBolts = self . getEntry ( bedBolts , 0 ) ,
2016-08-17 15:07:41 +02:00
bedBoltSettings = self . getEntry ( bedBoltSettings , 0 ) )
2013-04-16 04:27:48 +02:00
self . cc ( callback , 1 )
2017-01-20 21:35:32 +01:00
self . edges [ edge ] ( lx / 2.0 , bedBolts = self . getEntry ( bedBolts , 1 ) ,
2016-08-17 15:07:41 +02:00
bedBoltSettings = self . getEntry ( bedBoltSettings , 1 ) )
2017-01-20 21:35:32 +01:00
for i , l in zip ( range ( 3 ) , ( ly , lx , ly ) ) :
2013-04-16 04:27:48 +02:00
self . corner ( 90 , r )
2016-08-17 15:07:41 +02:00
self . cc ( callback , i + 2 )
2017-01-20 21:35:32 +01:00
self . edges [ edge ] ( l , bedBolts = self . getEntry ( bedBolts , i + 2 ) ,
2016-08-17 15:07:41 +02:00
bedBoltSettings = self . getEntry ( bedBoltSettings , i + 2 ) )
2013-04-16 04:27:48 +02:00
self . corner ( 90 , r )
2013-05-14 17:52:33 +02:00
self . ctx . restore ( )
self . ctx . save ( )
2017-01-20 21:35:32 +01:00
self . moveTo ( self . edges [ edge ] . margin ( ) ,
self . edges [ 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 )
2013-12-15 20:41:18 +01:00
self . ctx . stroke ( )
2013-07-20 10:49:45 +02:00
self . move ( overallwidth , overallheight , move )
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 " ,
2013-07-20 10:49:45 +02:00
callback = None ,
move = None ) :
2016-03-28 16:55:41 +02:00
""" h : inner height, not counting the joints
2013-04-16 04:27:48 +02:00
callback is called a beginn of the flat sides with
2016-03-28 16:55:41 +02:00
* 0 for right half of first x side ;
* 1 and 3 for y sides ;
* 2 for second x side
* 4 for second half of the first x side
: param x : width of matching roundedPlate
: param y : height of matching roundedPlate
: param r : corner radius of matching roundedPlate
: param h : height of the wall
: param bottom : ( Default value = ' e ' ) Edge type
: param top : ( Default value = ' e ' ) Edge type
: param callback : ( Default value = None )
: param move : ( Default value = None )
2013-04-16 04:27:48 +02:00
"""
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
topwidth = top . startwidth ( )
bottomwidth = bottom . startwidth ( )
2013-07-20 10:49:45 +02:00
2016-08-17 15:07:41 +02:00
overallwidth = 2 * x + 2 * y - 8 * r + 4 * c4 + \
self . edges [ " d " ] . spacing ( ) + self . edges [ " D " ] . spacing ( )
2013-07-20 10:49:45 +02:00
overallheight = h + top . spacing ( ) + bottom . spacing ( )
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
2016-08-17 15:07:41 +02:00
self . cc ( callback , 0 , y = bottomwidth + self . burn )
bottom ( x / 2.0 - r )
if ( y - 2 * r ) < 1E-3 :
self . edges [ " X " ] ( 2 * c4 , h + topwidth + bottomwidth )
self . cc ( callback , 2 , y = bottomwidth + self . burn )
bottom ( x - 2 * r )
self . edges [ " X " ] ( 2 * c4 , h + topwidth + bottomwidth )
self . cc ( callback , 4 , y = bottomwidth + self . burn )
2016-03-29 21:29:57 +02:00
else :
for i , l in zip ( range ( 4 ) , ( y , x , y , 0 ) ) :
2016-08-17 15:07:41 +02:00
self . edges [ " X " ] ( c4 , h + topwidth + bottomwidth )
self . cc ( callback , i + 1 , y = bottomwidth + self . burn )
2016-03-29 21:29:57 +02:00
if i < 3 :
2016-08-17 15:07:41 +02:00
bottom ( l - 2 * r )
bottom ( x / 2.0 - r )
2013-04-16 04:27:48 +02:00
2016-04-26 11:54:37 +02:00
self . edgeCorner ( bottom , right , 90 )
2016-03-30 20:55:55 +02:00
right ( h )
2016-04-26 11:54:37 +02:00
self . edgeCorner ( right , top , 90 )
2013-04-16 04:27:48 +02:00
2016-08-17 15:07:41 +02:00
top ( x / 2.0 - r )
2013-04-16 04:27:48 +02:00
for i , l in zip ( range ( 4 ) , ( y , x , y , 0 ) ) :
self . edge ( c4 )
if i < 3 :
2016-08-17 15:07:41 +02:00
top ( l - 2 * r )
top ( x / 2.0 - r )
2013-04-16 04:27:48 +02:00
2016-04-26 11:54:37 +02:00
self . edgeCorner ( top , left , 90 )
2016-03-30 20:55:55 +02:00
left ( h )
2016-04-26 11:54:37 +02:00
self . edgeCorner ( left , bottom , 90 )
2013-04-16 04:27:48 +02:00
2013-12-15 20:41:18 +01:00
self . ctx . stroke ( )
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 ,
2013-07-20 10:49:45 +02:00
move = None ) :
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 )
"""
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 ( ) )
if 6 not in ignore_widths :
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 " ]
if 2 * i + 1 in ignore_widths :
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 :
2016-08-17 15:07:41 +02:00
self . moveTo ( holesMargin + edges [ - 1 ] . endwidth ( ) ,
holesMargin + edges [ 0 ] . startwidth ( ) )
self . hexHolesRectangle ( x - 2 * holesMargin , y - 2 * holesMargin )
2016-07-27 22:19:32 +02:00
2013-12-15 20:41:18 +01:00
self . ctx . stroke ( )
2013-07-20 10:49:45 +02:00
self . move ( overallwidth , overallheight , move )
2016-09-18 17:20:24 +02: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 ,
move = None ) :
"""
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 ]
2017-02-04 14:53:46 +01:00
: param r : radius towards the hypothenuse
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 )
"""
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 )
2016-09-26 18:33:22 +02:00
width = x + edges [ - 1 ] . spacing ( ) + edges [ 1 ] . spacing ( )
height = y + edges [ 0 ] . spacing ( ) + edges [ 2 ] . spacing ( )
if num > 1 :
2017-02-05 20:56:36 +01:00
width + = edges [ - 1 ] . spacing ( ) + edges [ 1 ] . spacing ( ) + 2 * self . spacing
2017-02-04 14:53:46 +01:00
height + = 0.7 * r + edges [ 0 ] . spacing ( ) + edges [ 2 ] . spacing ( ) + self . spacing
2016-09-26 18:33:22 +02:00
overallwidth = width * ( num / / 2 + num % 2 )
overallheight = height
2017-02-05 20:56:36 +01:00
alpha = math . degrees ( math . atan ( ( y - r ) / float ( x - r ) ) )
2016-09-18 17:20:24 +02:00
if self . move ( overallwidth , overallheight , move , before = True ) :
return
2016-09-26 18:33:22 +02:00
if num > 1 :
self . moveTo ( self . spacing + edges [ - 1 ] . spacing ( ) )
for n in range ( num ) :
self . moveTo ( edges [ - 1 ] . spacing ( ) + self . spacing , edges [ 0 ] . margin ( ) )
if n % 2 == 1 :
2017-02-05 20:56:36 +01:00
self . moveTo ( 2 * edges [ 1 ] . spacing ( ) + self . spacing , 0 )
2016-09-26 18:33:22 +02:00
if num > 1 :
self . moveTo ( edges [ 1 ] . spacing ( ) , 0 )
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 ) )
self . edgeCorner ( edges [ i ] , edges [ i + 1 ] , 90 )
2017-02-04 14:53:46 +01:00
self . corner ( alpha , r )
2016-09-26 18:33:22 +02:00
self . cc ( callback , 2 )
2017-02-04 14:53:46 +01:00
edges [ 2 ] ( ( ( x - r ) * * 2 + ( y - r ) * * 2 ) * * 0.5 )
self . corner ( 90 - alpha , r )
self . corner ( 90 )
2016-09-26 18:33:22 +02:00
self . ctx . stroke ( )
if n % 2 :
self . moveTo ( - edges [ 1 ] . spacing ( ) - 2 * self . spacing - edges [ - 1 ] . spacing ( ) , height - edges [ 1 ] . spacing ( ) , 180 )
else :
self . moveTo ( width + 1 * edges [ 1 ] . spacing ( ) - self . spacing - 2 * edges [ - 1 ] . spacing ( ) , height - edges [ 1 ] . spacing ( ) , 180 )
2016-09-18 17:20:24 +02:00
self . move ( overallwidth , overallheight , move )
2016-10-01 22:10:38 +02:00
##################################################
### Place Parts
##################################################
def partsMatrix ( self , n , width , move , part , * l , * * kw ) :
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 ) :
self . ctx . save ( )
for j in range ( width ) :
if width * i + j > = n :
break
kw [ " move " ] = " right "
part ( * l , * * kw )
self . ctx . restore ( )
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 "
for i in range ( n ) :
part ( * l , * * kw )