2016-02-12 21:22:32 +01:00
#!/usr/bin/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
import cairo
import math
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
2016-03-25 14:02:52 +01:00
from boxes import edges
2013-04-01 09:42:29 +02:00
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
"""
2013-04-17 13:25:30 +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
"""
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 )
2013-05-14 17:52:33 +02:00
return f
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-03-23 09:12:14 +01: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 ) ,
" M8 " : ( 13 , 6.8 ) ,
" 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
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 ]
2013-07-25 14:22:55 +02:00
side = size / 3 * * 0.5
self . boxes . moveTo ( x , y , angle )
self . boxes . moveTo ( - 0.5 * side , 0.5 * size , angle )
for i in range ( 6 ) :
self . boxes . edge ( side )
self . boxes . corner ( - 60 )
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 ) )
print ( [ float ( m . group ( 1 ) ) ] * n )
return [ float ( m . group ( 1 ) ) / n ] * n
m = re . match ( r " ( \ d+( \ . \ d+)?) \ *( \ d+) " , s )
if m :
n = int ( m . group ( 3 ) )
return [ float ( m . group ( 1 ) ) ] * n
try :
return [ float ( part ) for part in s . split ( " : " ) ]
except ValueError :
raise argparse . ArgumentTypeError ( " Don ' t understand sections string " )
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-03-03 13:27:22 +01:00
def __init__ ( self ) :
2016-03-08 09:33:45 +01:00
self . argparser = ArgumentParser ( description = self . __doc__ )
2016-04-02 17:39:26 +02:00
self . argparser . add_argument (
" --fingerjointfinger " , action = " store " , type = float , default = 1.0 ,
help = " width of the fingers in multiples of thickness " )
self . argparser . add_argument (
" --fingerjointspace " , action = " store " , type = float , default = 1.0 ,
help = " width of the space between fingers in multiples of thickness " )
self . argparser . add_argument (
" --fingerjointsurrounding " , action = " store " , type = float , default = 1.0 ,
help = " amount of space needed at the end in multiples of normal spaces " )
2016-03-03 13:27:22 +01:00
self . argparser . add_argument (
" --thickness " , action = " store " , type = float , default = 4.0 ,
help = " thickness of the material " )
self . argparser . add_argument (
" --output " , action = " store " , type = str , default = " box.svg " ,
help = " name of resulting file " )
self . argparser . add_argument (
" --debug " , action = " store_true " , default = False ,
help = " print surrounding boxes for some structures " )
self . argparser . add_argument (
" --burn " , action = " store " , type = float , default = 0.05 ,
help = " burn correction in mm " )
def open ( self , width , height ) :
2016-03-28 16:55:41 +02:00
"""
Prepare for rendering
Call this function from your . render ( ) method
: param width : width of canvas in mm
: param height : height of canvas in mm
"""
2013-07-20 10:49:45 +02:00
self . spacing = 2 * self . burn + 0.5 * self . thickness
2013-04-16 16:50:29 +02:00
self . fingerHoleEdgeWidth = 1.0 # multitudes of self.thickness
2013-06-15 23:26:22 +02:00
self . bedBoltSettings = ( 3 , 5.5 , 2 , 20 , 15 ) #d, d_nut, h_nut, l, l1
2013-04-18 04:44:33 +02:00
self . hexHolesSettings = ( 5 , 3 , ' circle ' ) # r, dist, style
2013-04-05 20:43:34 +02:00
self . _init_surface ( width , height )
2013-07-20 10:49:45 +02:00
self . _buildObjects ( )
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 (
" --x " , action = " store " , type = float , default = 100.0 ,
help = " inner width in mm " )
elif arg == " y " :
self . argparser . add_argument (
" --y " , action = " store " , type = float , default = 100.0 ,
help = " inner depth in mm " )
elif arg == " sx " :
self . argparser . add_argument (
" --sx " , action = " store " , type = argparseSections ,
default = " 50*3 " ,
2016-03-16 11:41:14 +01:00
help = """ sections left to right in mm. Possible formats: overallwidth/numberof sections e.g. " 250/5 " ; sectionwith*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 (
" --sy " , action = " store " , type = argparseSections ,
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 (
" --h " , action = " store " , type = float , default = 100.0 ,
help = " inner height in mm " )
elif arg == " hi " :
self . argparser . add_argument (
2016-03-08 22:27:55 +01:00
" --hi " , action = " store " , type = float , default = 0.0 ,
help = " inner height of inner walls in mm (leave to zero for same as outer walls) " )
2016-03-03 13:27:22 +01:00
else :
raise ValueError ( " No default for argument " , arg )
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
"""
2016-03-04 12:09:59 +01:00
self . argparser . parse_args ( args = args , namespace = self )
2016-03-03 13:27:22 +01:00
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-03-06 22:50:39 +01:00
#if not hasattr(self, name):
setattr ( self , name , part )
2016-03-25 14:02:52 +01:00
if isinstance ( part , edges . Edge ) :
2013-07-20 10:49:45 +02:00
self . edges [ part . char ] = part
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 ) )
2013-07-20 10:49:45 +02:00
# Share settings object
2016-03-25 14:02:52 +01:00
s = edges . FingerJointSettings ( self . thickness )
2016-04-02 17:39:26 +02:00
s . setValues ( self . thickness ,
finger = getattr ( self , " fingerjointfinger " , 1.0 ) ,
space = getattr ( self , " fingerjointspace " , 1.0 ) ,
surroundingspaces = getattr ( self , " fingerjointsurrounding " , 1.0 ) )
2016-03-25 14:02:52 +01:00
self . addPart ( edges . FingerJointEdge ( self , s ) )
self . addPart ( edges . FingerJointEdgeCounterPart ( self , s ) )
self . addPart ( edges . FingerHoleEdge ( self , s ) )
self . addPart ( edges . FingerHoles ( self , s ) )
s = edges . DoveTailSettings ( self . thickness )
self . addPart ( edges . DoveTailJoint ( self , s ) )
self . addPart ( edges . DoveTailJointCounterPart ( self , s ) )
s = edges . FlexSettings ( self . thickness )
self . addPart ( edges . FlexEdge ( self , s ) )
2013-04-01 09:42:29 +02:00
2013-07-25 14:22:55 +02:00
self . addPart ( NutHole ( self , None ) )
2013-04-05 20:43:34 +02:00
def _init_surface ( self , width , height ) :
2016-03-28 16:55:41 +02:00
"""
Initialize cairo canvas
: param width : canvas size
: param height : canvas height
"""
2014-03-29 08:23:18 +01:00
#mm2pt = 90 / 25.4 / 1.25
mm2pt = 1
#width *= mm2pt
#height *= mm2pt #3.543307
2013-04-01 09:42:29 +02:00
self . surface = cairo . SVGSurface ( self . output , width , height )
self . ctx = ctx = cairo . Context ( self . surface )
ctx . translate ( 0 , height )
2013-06-25 19:45:09 +02:00
ctx . scale ( mm2pt , - mm2pt )
2013-04-01 09:42:29 +02:00
2014-03-29 08:23:18 +01:00
#ctx.set_source_rgb(1.0, 1.0, 1.0)
#ctx.rectangle(0, 0, width, height)
#ctx.fill()
2013-04-01 09:42:29 +02:00
ctx . set_source_rgb ( 0.0 , 0.0 , 0.0 )
2013-06-15 23:26:22 +02:00
ctx . set_line_width ( 2 * self . burn )
2013-04-01 09:42:29 +02:00
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 """
self . open ( 100 , 100 )
# 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
2013-04-16 04:27:48 +02:00
self . ctx . save ( )
self . moveTo ( x , y )
if callable ( callback ) :
callback ( number )
elif hasattr ( callback , ' __getitem__ ' ) :
try :
callback = callback [ number ]
if callable ( callback ) :
callback ( )
2013-04-16 05:21:38 +02:00
except ( KeyError , IndexError ) :
2013-04-16 04:27:48 +02:00
pass
except :
self . ctx . restore ( )
raise
self . ctx . restore ( )
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 ) :
if len ( param ) > idx :
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
2014-03-29 08:23:18 +01:00
f = open ( self . output , " r+ " )
s = f . read ( 1024 )
pos = s . find ( ' pt " ' )
if pos > 0 :
f . seek ( pos )
f . write ( " mm " )
else :
2016-02-12 21:22:32 +01:00
print ( " Could not replace pt with mm " )
2014-03-29 08:23:18 +01:00
pos = s . find ( ' pt " ' , pos + 3 )
if pos > 0 :
f . seek ( pos )
f . write ( " mm " )
else :
2016-02-12 21:22:32 +01:00
print ( " Could not replace pt with mm " )
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 )
"""
2013-04-01 09:42:29 +02:00
rad = degrees * math . pi / 180
if degrees > 0 :
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 :
2013-06-08 15:19:28 +02:00
self . ctx . arc_negative ( 0 , - ( radius - self . burn ) , radius - self . burn ,
2013-04-01 09:42:29 +02:00
0.5 * math . pi , rad + 0.5 * math . pi )
2013-06-15 23:26:22 +02:00
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-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
"""
2013-06-15 23:26:22 +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 )
dx = x3 - x2
dy = y3 - y2
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
: param \* args : Alternating length in mm and angle
"""
2014-04-20 21:54:33 +02:00
for i , arg in enumerate ( args ) :
if i % 2 :
self . corner ( arg )
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
self . edge ( ( length - d ) / 2.0 )
self . corner ( 90 )
self . edge ( l1 )
self . corner ( 90 )
self . edge ( ( d_nut - d ) / 2.0 )
self . corner ( - 90 )
self . edge ( h_nut )
self . corner ( - 90 )
self . edge ( ( d_nut - d ) / 2.0 )
self . corner ( 90 )
self . edge ( l - l1 - h_nut )
self . corner ( - 90 )
self . edge ( d )
self . corner ( - 90 )
self . edge ( l - l1 - h_nut )
self . corner ( 90 )
self . edge ( ( d_nut - d ) / 2.0 )
self . corner ( - 90 )
self . edge ( h_nut )
self . corner ( - 90 )
self . edge ( ( d_nut - d ) / 2.0 )
self . corner ( 90 )
self . edge ( l1 )
self . corner ( 90 )
self . edge ( ( length - d ) / 2.0 )
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
"""
2013-06-03 09:49:51 +02:00
grooves = int ( length / / ( depth * 2.0 ) ) + 1
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 :
"""
2013-06-25 13:40:22 +02:00
self . edge ( 1.1 * self . thickness )
2013-06-03 09:49:51 +02:00
self . corner ( - 90 )
2013-06-25 13:40:22 +02:00
self . edge ( length / 2.0 + 0.2 * self . thickness )
2013-06-03 09:49:51 +02:00
self . corner ( - 90 )
2013-06-25 13:40:22 +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 :
"""
2013-06-03 09:49:51 +02:00
self . corner ( 90 , self . thickness / 4.0 )
2013-06-25 13:40:22 +02:00
self . grip ( length / 2.0 - self . thickness / 2.0 - 0.2 * self . thickness , self . thickness / 2.0 )
2013-06-03 09:49:51 +02:00
self . corner ( 90 , self . thickness / 4.0 )
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 :
self . edge ( length / 2.0 - self . burn )
self . corner ( - 90 )
self . edge ( self . thickness )
self . corner ( 90 )
self . edge ( length / 2.0 )
self . corner ( 90 )
self . edge ( self . thickness )
self . corner ( - 90 )
if not reverse :
self . edge ( length / 2.0 - self . burn )
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
"""
2013-04-16 05:07:26 +02:00
d = ( x - hl - 2 * r ) / 2.0
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
2013-07-20 17:47:18 +02:00
self . moveTo ( d + 2 * r , 0 )
2013-04-16 05:07:26 +02:00
self . edge ( hl - 2 * r )
self . corner ( - 90 , r )
2013-07-20 17:47:18 +02:00
self . edge ( h - 3 * r )
2013-04-16 05:07:26 +02:00
self . corner ( - 90 , r )
self . edge ( hl - 2 * r )
self . corner ( - 90 , r )
2013-07-20 17:47:18 +02:00
self . edge ( h - 3 * r )
2013-04-16 05:07:26 +02:00
self . corner ( - 90 , r )
self . ctx . restore ( )
self . moveTo ( 0 , 0 )
self . curveTo ( d , 0 , d , 0 , d , - h + r )
self . curveTo ( r , 0 , r , 0 , r , r )
self . edge ( hl )
self . curveTo ( r , 0 , r , 0 , r , r )
self . curveTo ( h - r , 0 , h - r , 0 , h - r , - d )
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 )
self . ctx . rotate ( degrees * math . pi / 180.0 )
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 :
return False
terms = where . split ( )
dontdraw = before and " only " in terms
moves = {
" up " : ( 0 , y , False ) ,
" down " : ( 0 , - y , True ) ,
" left " : ( - x , 0 , True ) ,
" right " : ( x , 0 , False ) ,
" only " : ( 0 , 0 , None ) ,
}
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 )
return dontdraw
2013-04-16 04:27:48 +02:00
# Building blocks
2013-07-20 17:48:11 +02:00
def fingerHolesAt ( self , x , y , length , angle = 90 ,
2013-07-20 10:49:45 +02:00
bedBolts = None , bedBoltSettings = None ) :
2016-03-28 16:55:41 +02:00
"""
Draw holes for a matching finger joint edge
: param x : position
: param y : position
: param length : length of matching edge
: param angle : ( Default value = 90 )
: param bedBolts : ( Default value = None )
: param bedBoltSettings : ( Default value = None )
"""
2013-04-01 22:20:22 +02:00
self . ctx . save ( )
2013-07-20 17:48:11 +02:00
self . moveTo ( x , y , angle )
2013-07-20 10:49:45 +02:00
self . fingerHoles ( length , bedBolts , bedBoltSettings )
2013-04-01 22:20:22 +02:00
self . ctx . restore ( )
2013-06-07 12:50:13 +02:00
@restore
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
2013-04-16 04:24:35 +02:00
self . moveTo ( x + r , y )
self . ctx . arc ( - r , 0 , r , 0 , 2 * math . pi )
2013-07-20 17:51:54 +02:00
@restore
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
"""
2013-07-20 17:51:54 +02:00
self . moveTo ( x + r - dx / 2.0 , y - dy / 2.0 , 180 )
for d in ( dy , dx , dy , dx ) :
self . corner ( - 90 , r )
2016-03-15 20:28:44 +01: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 = {
" top " : ( 0 , - height ) ,
" middle " : ( 0 , - 0.5 * height ) ,
" bottom " : ( 0 , 0 ) ,
" left " : ( 0 , 0 ) ,
" center " : ( - 0.5 * width , 0 ) ,
" right " : ( - width , 0 ) ,
}
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
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 ) ,
}
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 )
2013-07-20 17:52:28 +02:00
self . hole ( 0 , 0 , 0.5 * flange )
for x in ( - 1 , 1 ) :
for y in ( - 1 , 1 ) :
self . hole ( x * 0.5 * holedistance ,
y * 0.5 * holedistance ,
0.5 * diameter )
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
: param y : heigth
: 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
2013-04-16 04:24:35 +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
cx = int ( ( x - 2 * r ) / / ( w ) ) + 2
cy = int ( ( y - 2 * r ) / / ( dist ) ) + 2
# what's left on the sides
lx = ( x - ( 2 * r + ( cx - 2 ) * w ) ) / 2.0
2013-04-18 04:44:33 +02:00
ly = ( y - ( 2 * r + ( ( cy / / 2 ) * 2 ) * dist - 2 * dist ) ) / 2.0
2013-04-16 16:50:29 +02:00
2014-03-03 21:45:01 +01:00
for i in range ( cy / / 2 ) :
for j in range ( ( cx - ( i % 2 ) ) / / 2 ) :
2013-04-16 16:50:29 +02:00
px = 2 * j * w + r + lx
py = i * 2 * dist + r + ly
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 ) :
cx , cy = x / 2.0 , y / 2.0
2013-04-17 13:25:30 +02:00
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 )
"""
2013-04-16 16:50:29 +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 )
"""
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 :
"""
2013-04-17 13:25:30 +02:00
posx = abs ( posx - ( x / 2.0 ) )
posy = abs ( posy - ( y / 2.0 ) )
2013-04-18 04:44:33 +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
2013-04-18 04:44:33 +02:00
return dist ( posx - wx , posy - wy ) > rc
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 )
w = r + b / 2.0
dist = w * math . cos ( math . pi / 6.0 )
cy = 2 * int ( ( h - 4 * dist ) / / ( 4 * w ) ) + 1
leftover = h - 2 * r - ( cy - 1 ) * 2 * r
if grow == ' space ' :
b + = leftover / ( cy - 1 ) / 2
# recalulate with adjusted values
w = r + b / 2.0
dist = w * math . cos ( math . pi / 6.0 )
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 ) :
2013-04-16 04:24:35 +02:00
self . hole ( 2 * j * w , 0 , r )
2014-03-03 21:45:01 +01:00
for i in range ( 1 , cy / 2 + 1 ) :
for j in range ( cy - i ) :
2013-04-16 04:24:35 +02:00
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 11:32:13 +02:00
##################################################
### parts
##################################################
2013-04-16 04:24:35 +02:00
2013-05-14 17:52:33 +02:00
def roundedPlate ( self , x , y , r , 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
"""
2013-07-20 10:49:45 +02:00
overallwidth = x + 2 * self . fingerJointEdge . spacing ( )
overallheight = y + 2 * self . fingerJointEdge . spacing ( )
if self . move ( overallwidth , overallheight , move , before = True ) :
return
2013-04-16 04:27:48 +02:00
self . ctx . save ( )
2013-07-20 19:29:41 +02:00
self . moveTo ( self . fingerJointEdge . margin ( ) ,
self . fingerJointEdge . margin ( ) )
self . moveTo ( r , 0 )
2013-07-20 10:49:45 +02:00
2013-04-16 04:27:48 +02:00
self . cc ( callback , 0 )
2013-07-20 10:49:45 +02:00
self . fingerJointEdge ( x / 2.0 - r , bedBolts = self . getEntry ( bedBolts , 0 ) ,
2013-06-15 23:26:22 +02:00
bedBoltSettings = self . getEntry ( bedBoltSettings , 0 ) )
2013-04-16 04:27:48 +02:00
self . cc ( callback , 1 )
2013-07-20 10:49:45 +02:00
self . fingerJointEdge ( x / 2.0 - r , bedBolts = self . getEntry ( bedBolts , 1 ) ,
2013-06-15 23:26:22 +02:00
bedBoltSettings = self . getEntry ( bedBoltSettings , 1 ) )
2013-04-16 04:27:48 +02:00
for i , l in zip ( range ( 3 ) , ( y , x , y ) ) :
self . corner ( 90 , r )
self . cc ( callback , i + 2 )
2013-07-20 10:49:45 +02:00
self . fingerJointEdge ( l - 2 * r , bedBolts = self . getEntry ( bedBolts , i + 2 ) ,
2013-06-15 23:26:22 +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 ( )
2013-07-20 19:29:41 +02:00
self . moveTo ( self . fingerJointEdge . margin ( ) ,
self . fingerJointEdge . margin ( ) )
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
self . hexHolesPlate ( x - 2 * holesMargin , y - 2 * holesMargin , r ,
settings = holesSettings )
2013-04-16 04:27:48 +02:00
self . ctx . restore ( )
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
"""
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
topwidth = top . width ( )
bottomwidth = bottom . width ( )
overallwidth = 2 * x + 2 * y - 8 * r + 4 * c4 + \
self . edges [ " d " ] . spacing ( ) + self . edges [ " D " ] . spacing ( )
overallheight = h + top . spacing ( ) + bottom . spacing ( )
if self . move ( overallwidth , overallheight , move , before = True ) :
return
self . ctx . save ( )
2016-03-30 20:55:55 +02:00
self . moveTo ( left . margin ( ) , bottom . margin ( ) )
2013-04-16 04:27:48 +02:00
self . cc ( callback , 0 , y = bottomwidth + self . burn )
2013-07-20 10:49:45 +02:00
bottom ( x / 2.0 - r )
2016-03-29 21:29:57 +02:00
if ( y - 2 * r ) < 1E-3 :
self . flexEdge ( 2 * c4 , h + topwidth + bottomwidth )
self . cc ( callback , 2 , y = bottomwidth + self . burn )
bottom ( x - 2 * r )
self . flexEdge ( 2 * c4 , h + topwidth + bottomwidth )
else :
for i , l in zip ( range ( 4 ) , ( y , x , y , 0 ) ) :
self . flexEdge ( c4 , h + topwidth + bottomwidth )
self . cc ( callback , i + 1 , y = bottomwidth + self . burn )
if i < 3 :
bottom ( l - 2 * r )
2013-07-20 10:49:45 +02:00
bottom ( x / 2.0 - r )
2013-04-16 04:27:48 +02:00
self . corner ( 90 )
self . edge ( bottomwidth )
2016-03-30 20:55:55 +02:00
right ( h )
2013-04-16 04:27:48 +02:00
self . edge ( topwidth )
self . corner ( 90 )
2013-07-20 10:49:45 +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 :
2013-07-20 10:49:45 +02:00
top ( l - 2 * r )
top ( x / 2.0 - r )
2013-04-16 04:27:48 +02:00
self . corner ( 90 )
self . edge ( topwidth )
2016-03-30 20:55:55 +02:00
left ( h )
2013-04-16 04:27:48 +02:00
self . edge ( bottomwidth )
self . corner ( 90 )
2013-07-20 10:49:45 +02:00
self . ctx . restore ( )
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 " ,
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
: 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 ]
2013-04-16 11:32:13 +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
self . ctx . save ( )
self . moveTo ( edges [ - 1 ] . margin ( ) , edges [ 0 ] . margin ( ) )
2013-04-16 11:32:13 +02:00
for i , l in enumerate ( ( x , y , x , y ) ) :
2013-07-20 10:49:45 +02:00
self . edge ( edges [ i - 1 ] . width ( ) )
2013-08-31 20:06:30 +02:00
self . cc ( callback , i , y = edges [ i ] . width ( ) + self . burn )
2013-07-20 10:49:45 +02:00
edges [ i ] ( l ,
bedBolts = self . getEntry ( bedBolts , i ) ,
bedBoltSettings = self . getEntry ( bedBoltSettings , i ) )
self . edge ( edges [ i + 1 ] . width ( ) )
self . corner ( 90 - edges [ i ] . endAngle ( ) - edges [ i + 1 ] . startAngle ( ) )
2013-04-01 22:20:22 +02:00
2013-05-14 17:52:33 +02:00
if holesMargin is not None :
2013-07-20 10:49:45 +02:00
self . moveTo ( holesMargin + edges [ - 1 ] . width ( ) ,
holesMargin + edges [ 0 ] . width ( ) )
2013-05-14 17:52:33 +02:00
self . hexHolesRectangle ( x - 2 * holesMargin , y - 2 * holesMargin )
2013-07-20 10:49:45 +02:00
self . ctx . restore ( )
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
2013-04-01 09:42:29 +02:00
##################################################
### main
##################################################
2016-03-03 13:27:22 +01:00
class DemoBox ( Boxes ) :
2016-03-08 09:33:45 +01:00
""" A simple fully enclosed box showcasing different finger joints """
2016-03-03 13:27:22 +01:00
def __init__ ( self ) :
Boxes . __init__ ( self )
self . buildArgParser ( " x " , " y " , " h " )
def render ( self ) :
2016-03-28 16:55:41 +02:00
""" """
2016-03-03 13:27:22 +01:00
x , y , h , t = self . x , self . y , self . h , self . thickness
self . open ( 2 * x + 10 * self . thickness , y + 2 * h + 20 * self . thickness )
2013-04-01 22:20:22 +02:00
self . ctx . save ( )
2016-03-03 13:27:22 +01:00
self . moveTo ( t , t )
self . rectangularWall ( x , y , " ffff " )
self . moveTo ( x + 4 * t , 0 )
2013-04-16 11:32:13 +02:00
self . rectangularWall ( x , y , " FFFF " )
2013-04-01 22:20:22 +02:00
self . ctx . restore ( )
2013-04-16 04:27:48 +02:00
2016-03-03 13:27:22 +01:00
self . moveTo ( t , y + 4 * t )
2013-04-16 11:32:13 +02:00
for i in range ( 2 ) :
for l in ( x , y ) :
self . rectangularWall ( l , h , " hffF " )
2016-03-04 12:09:59 +01:00
self . moveTo ( l + 4 * t , 0 )
self . moveTo ( - x - y - 8 * t , h + 4 * t )
2013-04-16 11:32:13 +02:00
2014-03-21 21:08:54 +01:00
self . close ( )
2013-04-01 09:42:29 +02:00
if __name__ == ' __main__ ' :
2016-03-03 13:27:22 +01:00
b = DemoBox ( )
b . parseArgs ( )
b . render ( )