2013-06-13 04:30:55 +07:00
"""
This module plots many shapes a polygon objects containing a series of lines
The Polygon object can be found in reprap . toolpath
"""
# Python module properties
__author__ = " Stefan Blanke (greenarrow) (greenarrow@users.sourceforge.net) "
__license__ = " GPL 3.0 "
__credits__ = " Author of potrace "
__licence__ = """
pyRepRap 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 .
pyRepRap 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 pyRepRap . If not , see < http : / / www . gnu . org / licenses / > .
"""
import math , os
import toolpath
# Fill modes
FILL_LOCUS = 1
FILL_LINES = 2
debug = False
def line ( line ) :
""" Returns polygon for line (x1, y2, x2, y2) as a Polygon object """
poly = toolpath . Polygon ( )
x1 , y1 , x2 , y2 = line
poly . addPoint ( toolpath . Point ( x1 , y1 ) )
poly . addPoint ( toolpath . Point ( x2 , y2 ) )
return poly
def point ( point ) :
""" Returns polygon for point (x, y) as a Polygon Object """
poly = toolpath . Polygon ( )
x , y = point
poly . addPoint ( toolpath . Point ( x , y ) )
return poly
def arc ( x , y , radius , startAngle , endAngle , resolution ) :
""" Returns polygon an arc with x, y, radius, start angle (radians), end engle (radians) and resolution (lines per mm) a Polygon object """
# This function works in degrees but takes parameters in radians
poly = toolpath . Polygon ( )
if debug : print " Plotting arc at " , x , y , " from " , startAngle , " ( " , math . degrees ( startAngle ) , " ) to " , endAngle , " ( " , math . degrees ( endAngle ) , " ) "
startAngle , endAngle = math . degrees ( startAngle ) , math . degrees ( endAngle )
circumference = float ( 2 ) * math . pi * float ( radius )
angleDiv = ( float ( 360 ) / float ( circumference * resolution ) ) # + ( 360 % int( circumference * resolution ) )
lastX , lastY = _calcCircle ( startAngle , radius )
# compensate for arc going beyond 360 deg
if startAngle > endAngle :
endAngle + = 360
# make detail proportional to radius to always give good resolution
for theta in _frange ( startAngle , endAngle + angleDiv , angleDiv ) :
cx , cy = _calcCircle ( theta , radius )
poly . addPoint ( toolpath . Point ( x + cx , y + cy ) )
return poly
def circle ( x , y , radius , resolution , fillDensity = False ) :
""" Returns polygon for a filled circle with x, y, radius, resolution (lines per mm) and fill density (lines per mm) as a Polygon object """
poly = toolpath . Polygon ( )
if fillDensity :
numFills = int ( float ( fillDensity ) * float ( radius ) )
else :
numFills = 1
for d in range ( 1 , numFills + 1 ) :
r = ( float ( d ) / float ( numFills ) ) * float ( radius )
if debug : print " using r " , r , " mm "
poly . addPolygon ( arc ( x , y , r , math . radians ( 0 ) , math . radians ( 360 ) , resolution ) )
return poly
def ellipse ( x , y , a , b , resolution , fillDensity = False ) :
""" Returns polygon for a filled ellipse with x, y, a, b, resolution (lines per mm) and fill density (lines per mm) as a Polygon object """
poly = toolpath . Polygon ( )
if not resolution :
resolution = self . circleResolution
if fillDensity :
if a > b :
largerDimension = a
else :
largerDimension = b
numFills = int ( float ( fillDensity ) * float ( largerDimension ) )
else :
numFills = 1
startAngle , endAngle = 0 , 360
# Not really cicumference but will do?
circumference = float ( 2 ) * math . pi * float ( float ( a + b ) / 2 )
angleDiv = ( float ( 360 ) / float ( circumference * resolution ) ) # + ( 360 % int( circumference * resolution ) )
lastX , lastY = _calcEllipse ( startAngle , a , b )
# Compensate for arc going beyond 360 deg
if startAngle > endAngle :
endAngle + = 360
for d in range ( 1 , numFills + 1 ) :
ra = ( float ( d ) / float ( numFills ) ) * float ( a )
rb = ( float ( d ) / float ( numFills ) ) * float ( b )
for theta in _frange ( startAngle , endAngle + angleDiv , angleDiv ) :
newX , newY = _calcEllipse ( theta , ra , rb )
aLine = poly . addPoint ( toolpath . Point ( newX + x , newY + y ) )
if debug : print " aLine " , aLine
return poly
# Return polygon for a filled rectangle
def rectangle ( x , y , width , height , fillDensity = False ) :
""" Returns polygon for line x, y, width, height and fill density (lines per mm) as a Polygon object """
poly = toolpath . Polygon ( )
numFillsY = int ( float ( fillDensity ) * float ( height ) )
cornerX , cornerY = x - ( width / 2 ) , y - ( height / 2 )
invert = False
for dy in range ( 0 , numFillsY + 1 ) :
ry = ( float ( dy ) / float ( numFillsY ) ) * float ( height )
line1 = ( cornerX , cornerY + ry , cornerX + width , cornerY + ry )
line2 = ( cornerX , cornerY + ry , cornerX + width , cornerY - ry )
if invert :
line1 = _reverseLine ( line1 )
invert = not invert
x1 , y1 , x2 , y2 = line1
poly . addPoint ( toolpath . Point ( x1 , y1 ) )
poly . addPoint ( toolpath . Point ( x2 , y2 ) )
poly . addPoint ( toolpath . Point ( cornerX , cornerY ) )
poly . addPoint ( toolpath . Point ( cornerX , cornerY + height ) )
poly . addPoint ( toolpath . Point ( cornerX + width , cornerY ) )
poly . addPoint ( toolpath . Point ( cornerX + width , cornerY + height ) )
return poly
def circleStroke ( x1 , y1 , x2 , y2 , radius , resolution , fillDensity = False ) :
""" Returns polygon for a photoplotter moving stroke using a circular aperture with x, y, radius, resolution (lines per mm) and fill density (lines per mm) as a Polygon object """
poly = toolpath . Polygon ( )
deltaY = y2 - y1
deltaX = x2 - x1
if x1 == x2 and y1 == y2 :
#print "this is not a move, this is why software like eagle that uses drm on your own files....is crap"
poly . addPolygon ( circle ( x1 , y1 , radius , resolution = resolution , fillDensity = fillDensity ) )
else :
if debug : print " PMWC, fill density " , fillDensity
#Plot central line
poly . addPoint ( toolpath . Point ( x1 , y1 ) )
poly . addPoint ( toolpath . Point ( x2 , y2 ) )
# For each locus
numFills = int ( float ( fillDensity ) * float ( radius ) )
for d in range ( 1 , numFills + 1 ) :
r = ( float ( d ) / float ( numFills ) ) * float ( radius )
if debug : print " using r " , r , " mm "
theta = _angleFromDeltas ( deltaX , deltaY )
rsintheta = r * math . sin ( theta )
rcostheta = r * math . cos ( theta )
# Makes sure angle is in correct quadrant
if deltaX > 0 :
startOffset = math . radians ( 90 )
endOffset = math . radians ( - 90 )
else :
startOffset = math . radians ( - 90 )
endOffset = math . radians ( 90 )
if deltaY < 0 :
startOffset = - startOffset
endOffset = - endOffset
# Plot side lines and end arcs (simi-circles) of locus
# reversing these point lets locus be drawn in one continual motion
poly . addPoint ( toolpath . Point ( x2 - rsintheta , y2 + rcostheta ) )
poly . addPoint ( toolpath . Point ( x1 - rsintheta , y1 + rcostheta ) )
2013-09-26 06:42:44 +07:00
poly . addPolygon ( arc ( x1 , y1 , r , theta + startOffset , theta + endOffset , resolution ) )
2013-06-13 04:30:55 +07:00
poly . addPoint ( toolpath . Point ( x1 + rsintheta , y1 - rcostheta ) )
poly . addPoint ( toolpath . Point ( x2 + rsintheta , y2 - rcostheta ) )
2013-09-26 06:42:44 +07:00
poly . addPolygon ( arc ( x2 , y2 , r , theta - startOffset , theta - endOffset , resolution ) )
2013-06-13 04:30:55 +07:00
return poly
def fill ( polygon , fillDensity ) :
pass
def raster ( fileName , originalWidth , originalHeight , svg = False ) :
""" Returns polygon for a vectorised raster as a Polygon object.
Uses external potrace program to convert raster file into polygon ( s )
"""
poly = toolpath . Polygon ( )
if svg :
os . system ( " potrace --svg --output " + fileName [ : - 3 ] + " svg " + fileName )
os . system ( " potrace --alphamax 0 --turdsize 5 --backend gimppath --output " + fileName [ : - 3 ] + " gimppath " + fileName )
os . system ( " rm " + fileName )
f = open ( fileName [ : - 3 ] + " gimppath " )
pathLines = f . readlines ( )
f . close ( )
os . system ( " rm " + fileName [ : - 3 ] + " gimppath " )
scale = 0.005 # temp - competely arbitary
# 1 / 200, i.e 1 / (resolution = 20 * 100 for some reason)
for l in pathLines :
parts = l . split ( ' ' )
isPoint = False
for i , p in enumerate ( parts ) :
if p == ' TYPE: ' :
ptype = int ( parts [ i + 1 ] )
isPoint = True
elif p == ' X: ' :
x = float ( parts [ i + 1 ] ) * scale
elif p == ' Y: ' :
y = float ( parts [ i + 1 ] ) * scale
if isPoint :
poly . addPoint ( toolpath . Point ( x , y ) )
#print "NEW POINT", x, y, ptype
# This should not be assumed?
poly . closed = True
"""
#this needs to be done on all paths at same time
maxX , maxY = 0 , 0
for p in points :
x , y , t = p
maxX = max ( maxX , x )
maxY = max ( maxY , y )
print " max " , maxX , maxY
#print "read", len(points), "points"
scaleX = originalWidth / maxX
scaleY = originalHeight / maxY
print " scales " , scaleX , scaleY
for i in range ( len ( points ) ) :
x , y , y = points [ i ]
x = x * scaleX
y = y * scaleY
points [ i ] = x , y , t
"""
#should make this return a list of all found polygons
return poly
############# General Maths Functions #############
# Return the coordinates of a point on a circle at theta (rad) with radius.
def _calcCircle ( theta , radius ) :
x = math . cos ( math . radians ( theta ) ) * radius
y = math . sin ( math . radians ( theta ) ) * radius
return x , y
# Return the coordinates of a point on an ellipse at theta (rad) with a and b.
def _calcEllipse ( theta , a , b ) :
x = math . cos ( math . radians ( theta ) ) * a
y = math . sin ( math . radians ( theta ) ) * b
return x , y
# Reverse line (swap x1, y1 and x2, y2)
def _reverseLine ( line ) :
x1 , y1 , x2 , y2 = line
return x2 , y2 , x1 , y1
# Return the angle between the line between two points (2D coordinates) and vetical?
def _angleFromDeltas ( dx , dy ) :
radius = math . sqrt ( ( dx * dx ) + ( dy * dy ) )
#if radius != 0:
dx , dy = dx / radius , dy / radius
if dx > 0 :
if dy > 0 :
return math . asin ( dx )
elif dy < 0 :
return math . acos ( dx ) + math . radians ( 90 )
else :
#print "moo1"
return 0
elif dx < 0 :
if dy > 0 :
return math . asin ( dy ) + math . radians ( 270 )
elif dy < 0 :
return math . radians ( 180 ) - math . asin ( dx )
else :
#print "moo2"
return 0
else :
return math . radians ( - 90 ) # i think this should really be 90, it just makes thae program work wen its -90 :)
#else:
# print "Radius cannot be zero!, returning 0 (_angleFromDeltas, shapeplotter.py)"
# Range function accepting floats (by Dinu Gherman)
def _frange ( start , end = None , inc = None ) :
if end == None :
end = start + 0.0
start = 0.0
if inc == None :
inc = 1.0
L = [ ]
while 1 :
next = start + len ( L ) * inc
if inc > 0 and next > = end :
break
elif inc < 0 and next < = end :
break
L . append ( next )
return L
# Return length of vector
def _calcVectorLength ( line ) :
x1 , y1 , x2 , y2 = line
deltaX = max ( x1 , x2 ) - min ( x1 , x2 )
deltaY = max ( y1 , y2 ) - min ( y1 , y2 )
return math . sqrt ( math . pow ( deltaX , 2 ) + math . pow ( deltaY , 2 ) )