# !/usr/bin/python # # sewing_patterns.py # Inkscape extension-Effects-Sewing Patterns # Copyright (C) 2010, 2011, 2012 Susan Spencer, Steve Conklin < www.taumeta.org > # # 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 2 of the License, or # (at your option) any later version. Attribution must be given in # all derived works. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see < http://www.gnu.org/licenses/ > import sys, subprocess, math, inkex, gettext sys.path.append('/usr/share/inkscape/extensions') from simplestyle import * def debug(errmsg, file = 'bell'): #audio files directory: /usr/share/sounds/ubuntu/stereo sounds(file) inkex.errormsg(gettext.gettext(errmsg)) def sounds(file): subprocess.call(['/usr/bin/canberra-gtk-play', '--id', file ]) #--- class Point(): '''Python object class to create variables that represent a named Cartesian point. Accepts id, x, y. Returns object with .id, .x, .y attributes. Example: a5 = Point('a5', 10.0, 15.32) returns variable a5 where a5.id = 'a5', a5.x = 10.0, a5.y = 15.32xt If called with no parameters the defaults are id = '', x = 0, y = 0 The python object's id attribute enables it to be represented on the canvas as an svg object with the same id. ''' def __init__(self, id = '', x = 0.0, y = 0.0): #if no parameters are passed in then the default values id = '', x = 0, y = 0 are used self.id = id self.x = x self.y = y self.y = y def patternPointXY(parent, id, x, y): '''Accepts parent, id, x, y. Returns object of class Point. Calls addPoint() & addText() to create a pattern point on canvas.''' # create python variable pnt = Point(id, x, y) # create svg circle red 5px radius addCircle(parent, id, x, y, radius = 5, fill = 'red', stroke = 'red', stroke_width = '1', reference = 'true') #draw label 8px right and 8px above circle addText(parent, id + '_text', x + 8, y-8, id, fontsize = '30', textalign = 'left', textanchor = 'left', reference = 'true') #the id is used for two things here: the text object's id and the text object's content. return pnt # return python variable for use in remainder of pattern def patternPoint(parent, id, pnt): """Wrapper for patternPointXY. Accepts a Point object instead of X & Y values.""" return patternPointXY(parent, id, pnt.x, pnt.y) def controlPointXY(parent, id, x, y): '''Accepts parent, id, x, y. Returns object of class Point. Calls addPoint() & addText() to create a pattern point with label on canvas.''' # create python variable pnt = Point(id, x, y) # create unfilled grey circle 5px radius addCircle(parent, id, x, y, radius = 5, fill = 'none', stroke = 'gray', stroke_width = '1', reference = 'true') #draw label 8px right and 8px above circle addText(parent, id + '_text', x + 8, y-8, id, fontsize = '30', textalign = 'left', textanchor = 'left', reference = 'true') #the id is used twice: the text object id and the text object content. return pnt # return python variable for use in remainder of pattern def controlPoint(parent, id, pnt): """Wrapper for controlPointXY. Accepts a Point object instead of X & Y values.""" return controlPointXY(parent, id, pnt.x, pnt.y) def pointList( * args): """Accepts list of args. Returns array of args.""" list = [] for arg in args: list.append(arg) return list #---tests for position--- def isRight(pnt1, pnt2): '''returns 1 if pnt2 is to the right of pnt1''' right = 0 if pnt2.x > pnt1.x: right = 1 return right def isLeft(pnt1, pnt2): '''returns 1 if pnt2 is to the left of pnt1''' left = 0 if pnt2.x < pnt1.x: left = 1 return left def isAbove(pnt1, pnt2): '''returns 1 if pnt2 is above pnt1''' up = 0 if pnt2.y < pnt1.y: up = 1 return up def isBelow(pnt1, pnt2): '''returns 1 if pnt2 is below pnt1''' down = 0 if pnt2.y > pnt1.y: down = 1 return down def lowest(pnts): """Accepts array pnts[]. Returns lowest point in array.""" low = Point('', pnts[0].x, pnts[0].y) for item in pnts: if isBelow(low, item): #if item is below current low updatePoint('', low, item) return low def highest(pnts): """Accepts array pnts[]. Returns highest point in array.""" high = Point('', pnts[0].x, pnts[0].y) for item in pnts: if isAbove(high, item): #if item is above current high updatePoint(high, item) return high def leftmost(pnts): """Accepts array pnts[]. Returns leftmost point in array.""" left = Point('', pnts[0].x, pnts[0].y) for item in pnts: if isLeft(left, item): updatePoint(left, item) return left def rightmost(pnts): """Accepts array pnts[]. Returns rightmost point in array.""" right = Point('', pnts[0].x, pnts[0].y) for item in pnts: if isRight(right, item): updatePoint(right, item) return right #---functions to calculate points. These functions do not create SVG objects--- def updatePoint(p1, p2): '''Accepts p1 and p2 of class Point. Updates p1 with x & y values from p2''' p1.x = p2.x p1.y = p2.y return def right(p1, n): '''Accepts point p1 and float n. Returns (x,y) to the right of p1 at (p1.x + n, p1.y)''' return Point('', p1.x + n, p1.y) def left(p1, n): '''Accepts point p1 and float n. Returns p2 to the left of p1 at (p1.x-n, p1.y)''' return Point('', p1.x-n, p1.y) def up(p1, n): '''Accepts point p1 and float n. Returns p2 above p1 at (p1.x, p1.y-n)''' return Point('', p1.x, p1.y-n) def down(p1, n): '''Accepts point p1 and float n. Returns p2 below p1 at (p1.x, p1.y + n)''' return Point('', p1.x, p1.y + n) def symmetric(p1, p2, type = 'vertical'): """ Accepts p1 and p2 of class Point, and optional type is either 'vertical' or 'horizontal with default 'vertical'. Returns p3 of class Point as "mirror image" of p1 relative to p2 If type == 'vertical': pnt is on opposite side of vertical line x = p2.x from p1 If type == 'horizontal': pnt is on opposite side of horizontal line y = p2.y from p1 """ p3 = Point() dx = p2.x - p1.x dy = p2.y - p1.y if (type == 'vertical'): p3.x = p2.x + dx p3.y = p1.y elif (type == 'horizontal'): p3.x = p1.x p3.y = p2.y + dy return p3 def polar(p1, length, angle): ''' Adapted from http://www.teacherschoice.com.au/maths_library/coordinates/polar_-_rectangular_conversion.htm Accepts p1 as type Point, length as float, angle as float. angle is in radians Returns p2 as type Point, calculated at length and angle from p1, Angles start at position 3:00 and move clockwise due to y increasing downwards on Cairo Canvas ''' id = '' r = length x = p1.x + (r * math.cos(angle)) y = p1.y + (r * math.sin(angle)) p2 = Point(id, x, y) return p2 def midPoint(p1, p2, n = 0.5): '''Accepts points p1 & p2, and n where 0 < n < 1. Returns point p3 as midpoint b/w p1 & p2''' p3 = Point('', (p1.x + p2.x) * n, (p1.y + p2.y) * n) return p3 #---measurements def distance(p1, p2): '''Accepts two points p1 & p2. Returns the distance between p1 & p2.''' return ( ((p2.x-p1.x) ** 2) + ((p2.y-p1.y) ** 2) ) ** 0.5 def angleOfDegree(degree): '''Accepts degree. Returns radians.''' return degree * math.pi/180.0 def angleOfLine(p1, p2): """ Accepts points p1 & p2. Returns the angle of the vector between them. Uses atan2.""" return math.atan2(p2.y-p1.y, p2.x-p1.x) def angleOfVector(p1, v, p2): '''Accepts three points o1, v, and p2. Returns radians of the angle formed between the three points.''' return abs(angleOfLine(v, p1)-angleOfLine(v, p2)) def slopeOfLine(p1, p2): """ Accepts two point objects and returns the slope """ if ((p2.x-p1.x) != 0): m = (p2.y-p1.y)/(p2.x-p1.x) else: #TODO: better error handling here debug('Vertical Line in slopeOfLine') m = None return m def slopeOfAngle(radians): ''' Accepts angle (radians) Returns the slope as tangent radians ''' #get tangent of radians return math.tan(radians) #---intersections & extensions def extendLine(p1, p2, length, rotation=0): """ Accepts two directed points of a line, and a length to extend the line Finds point along line at length from p2 in direction p1->p2 """ return onLineAtLength(p2, p1, -length) def onLineAtLength(p1, p2, length, rotation=0): """ Accepts points p1 and p2, distance, and an optional rotation angle. Returns coordinate pair on the line at length measured from p1 towards p2 If length is negative, will return a coordinate pair at length measured from p1 in opposite direction from p2. The result is optionally rotated about the first point by the rotation angle in degrees """ lineangle = angleOfLine(p1, p2) angle = lineangle + rotation * (math.pi/180) x = (length * math.cos(angle)) + p1.x y = (length * math.sin(angle)) + p1.y return Point('', x, y) def onLineAtX(p1, p2, x): #on line p1-p2, given x find y if (p1.x == p2.x):# vertical line raise ValueError('Points form a vertical line, infinite answers possible') return None else: m = (p2.y - p1.y)/(p2.x - p1.x) b = p2.y - (m * p2.x) return Point('', x, (m * x) + b) def onLineAtY(p1, p2, y): #on line p1-p2, find x given y if (p1.y == p2.y): #if horizontal line raise ValueError('Points form a horizontal line, infinite answers possible') return None elif (p1.x == p2.x): # if vertical line return Point('', p1.x, y) else: m = (p1.y - p2.y)/(p1.x - p2.x) b = p2.y - (m * p2.x) return Point('', (y - b)/m, y) def intersectLines(p1, p2, p3, p4): """ Find intersection between two lines. Accepts p1, p2, p3, p4 as class Point. Returns p5 as class Point Intersection does not have to be within the supplied line segments """ x, y = 0.0, 0.0 if (p1.x == p2.x): #if 1st line vertical, use slope of 2nd line x = p1.x m2 = slopeOfLine(p3, p4) b2 = p3.y-m2 * p3.x y = m2 * x + b2 elif (p3.x == p4.x): #if 2nd line vertical, use slope of 1st line x = p3.x m1 = slopeOfLine(p1, p2) b1 = p1.y-m1 * p1.x y = m1 * x + b1 else: #otherwise use ratio of difference between points m1 = (p2.y-p1.y)/(p2.x-p1.x) m2 = (p4.y-p3.y)/(p4.x-p3.x) b1 = p1.y-m1 * p1.x b2 = p3.y-m2 * p3.x #if (abs(b1-b2) < 0.01) and (m1 == m2): #x = p1.x #else: #x = (b2-b1)/(m1-m2) if (m1 == m2): #TODO: better error handling here debug(' ** ** * Parallel lines in intersectLines ** ** * ') else: x = (b2-b1)/(m1-m2) y = (m1 * x) + b1 # arbitrary choice, could have used m2 & b2 p5 = Point("", x, y) return p5 def intersectLineRay(P1, P2, R1, angle): ''' Accepts two points defining a line, and a point and angle defining a ray. Returns point where they intersect. ''' #define a line R1-R2 by finding point R2 along ray 25 pixels (arbitary) from R1 R2 = polar(R1, 1 * 25, angle) pnt = intersectLines(P1, P2, R1, R2) return pnt def onRayAtX(P, angle, x): ''' Accepts point P and angle of line. Returns point along ray at x ''' #convert degrees to slope m = slopeOfAngle(angle) #solve for y #(P.y - y)/(P.x - x) = m y = P.y - m * (P.x - x) return Point('', x, y) def onRayAtY(P, angle, y): ''' Accepts point P and angle of line. Returns point along ray at y ''' #convert degrees to slope m = slopeOfAngle(angle) #solve for x #(P.y - y)/(P.x - x) = m x = P.x - (P.y - y)/m return Point('', x, y) def intersectCircles(C1, r1, C2, r2): """ Accepts C1, r1, C2, r2 where C1 & C2 are center points of each circle, and r1 & r2 are the radius of each circle. Returns an array of points of intersection. """ x0, y0 = C1.x, C1.y x1, y1 = C2.x, C2.y d = distance(C1, C2) # distance b/w circle centers dx, dy = (x1-x0), (y1-y0) # negate y b/c canvas increases top to bottom pnts = [] if (d == 0): #intersections = 0 #TODO: better error handling here debug('center of both circles are the same in intersectCircles()') debug('C1 = ', C1.x, C1.y, 'radius1 = ', r1) debug('C2 = ', C2.x, C2.y, 'radius1 = ', r2) return elif (d < abs(r1-r2)): #intersections = 0 #TODO: better error handling here debug('one circle is within the other in intersectCircles()') debug('d = ', d) debug('r1 - r2 = ', (r1-r2)) debug('d < abs(r1 - r2) ?', (d < abs(r1-r2))) debug('C1 = ', C1.x, C1.y, 'radius1 = ', r1) debug('C2 = ', C2.x, C2.y, 'radius1 = ', r2) return elif (d > (r1 + r2)): #intersections = 0 #TODO: better error handling here debug('circles do not intersect in intersectCircles()') debug('d = ', d) debug('r1 + r2 = ', (r1 + r2)) debug('d > abs(r1 + r2) ?', (d > abs(r1 + r2))) debug('C1 = ', C1.x, C1.y, 'radius1 = ', r1) debug('C2 = ', C2.x, C2.y, 'radius1 = ', r2) # TODO:possible kluge -check if this is acceptable using a small margin of error between r1 & r2 (0.5 * CM)?: #r2 = d-r1 return else: #intersections = 2 or intersections = 1 a = ((r1 * r1)-(r2 * r2) + (d * d))/(2.0 * d) x2 = x0 + (dx * a/d) y2 = y0 + (dy * a/d) h = math.sqrt((r1 * r1)-(a * a)) rx = -dy * (h/d) ry = dx * (h/d) X1 = x2 + rx Y1 = y2 + ry X2 = x2-rx Y2 = y2-ry pnts.append(Point("", X1, Y1)) pnts.append(Point("", X2, Y2)) return pnts def onCircleAtX(C, r, x): """ Finds points on circle where p.x=x Accepts C as an object of class Point or xy coords for the center of the circle, r as the radius, and x to find the points on the circle Returns an array P Based on paulbourke.net/geometry/sphereline/sphere_line_intersection.py """ #print 'C =', C.x, C.y #print 'r =', r #print 'x =', x P = [] if abs(x - C.x) > r: print('abs(x - C.x) > r ...', abs(x - C.x), ' > ', r) print('x is outside radius of circle in intersections.onCircleAtX()') else: translated_x = x - C.x # center of translated circle is (0, 0) as translated_x is the difference b/w C.x & x translated_y1 = abs(math.sqrt(r**2 - translated_x**2)) translated_y2 = -(translated_y1) y1 = translated_y1 + C.y # translate back to C.y y2 = translated_y2 + C.y # translate back to C.y P.append(Point('', x, y1)) P.append(Point('', x, y2)) return P def onCircleAtY(C, r, y): """ Finds points one or two points on circle where P.y=y Accepts C of class Point or coords as circle center, r of type float as radius, and y of type float) Returns an array P Based on paulbourke.net/geometry/sphereline/sphere_line_intersection.py """ #print('C =', C.x, C.y) #print('r =', r) #print('x = ', y)) P = [] if abs(y - C.y) > r: print('abs(y - C.y) > r ...', abs(y - C.y), ' > ', r) print('y is outside radius in onCircleAtY() -- no intersection') else: translated_y = y - C.y translated_x1 = abs(math.sqrt(r**2 - translated_y**2)) translated_x2 = -translated_x1 x1 = translated_x1 + C.x x2 = translated_x2 + C.x P.append(Point('', x1, y)) P.append(Point('', x2, y)) return P def intersectLineCircle(P1, P2, C, r): """ Finds intersection of a line segment and a circle. Accepts circle center point object C, radius r, and two line point objects P1 & P2 Returns an array P with up to two coordinate pairs as P.intersections P[0] & P[1] Based on paulbourke.net/geometry/sphereline/sphere_line_intersection.py """ #print('C =', C.x, C.y) #print('P1 =', P1.x, P1.y) #print('P2 =', P2.x, P2.y) #print('r =', r, 'pts', ', ', r / CM, 'cm') p1, p2 = Point('', '', ''), Point('', '', '') P = [] if P1.x == P2.x: #vertical line if abs(P1.x - C.x) > r: print('no intersections for vertical line P1', P1.name, P1.x, P1.y, ', P2', P2.name, P2.x, P2.y, ', and Circle', C.name, C.x, C.y, ', radius', r) return None else: #print('Vertical line') p1.x = P1.x p2.x = P1.x p1.y = C.y + sqrt(r**2 - (P1.x - C.x)**2) p2.y = C.y - sqrt(r**2 - (P1.x - C.x)**2) elif P1.y == P2.y: #horizontal line if abs(P1.y-C.y) > r: print('no intersections for horizontal line P1', P1.name, P1.x, P1.y, ', P2', P2.name, P2.x, P2.y, ', and Circle', C.name, C.x, C.y, ', radius', r) return None else: #print('Horizontal line') p1.y = P1.y p2.y = P1.y p1.x = C.x + sqrt(r**2 - (P1.y - C.y)**2) p2.x = C.x - sqrt(r**2 - (P1.y - C.y)**2) else: a = (P2.x - P1.x)**2 + (P2.y - P1.y)**2 b = 2.0 * ((P2.x - P1.x) * (P1.x - C.x)) + ((P2.y - P1.y) * (P1.y - C.y)) c = C.x**2 + C.y**2 + P1.x**2 + P1.y**2 - (2.0 * (C.x * P1.x + C.y * P1.y)) - r**2 i = b**2 - 4.0 * a * c if i < 0.0: print('no intersections b/w line', P1.name, P1.x, P1.y, '--', P2.name, P2.x, P2.y, 'and Circle', C.name, C.x, C.y, 'with radius', r) return None elif i == 0.0: # one intersection #print('one intersection') mu = -b/(2.0 * a) p1.x, p1.y = P1.x + mu * (P2.x - P1.x), P1.y + mu * (P2.y - P1.y) elif i > 0.0: # two intersections #print('two intersections') # first intersection mu1 = (-b + math.sqrt(i)) / (2.0*a) p1.x, p1.y = P1.x + mu1 * (P2.x - P1.x), P1.y + mu1 * (P2.y - P1.y) # second intersection mu2 = (-b - math.sqrt(i)) / (2.0*a) p2.x, p2.y = P1.x + mu2 * (P2.x - P1.x), P1.y + mu2 * (P2.y - P1.y) P.append(p1) P.append(p2) return P def intersectChordCircle(C, r, P, chord_length): ''' Accepts center of circle, radius of circle, a point on the circle, and chord length. Returns an array of two points on the circle at chord_length distance away from original point''' d = chord_length # point on circle given chordlength & starting point = 2 * asin(d/2r) d_div_2r = d/(2.0 * r) angle = 2 * asin(d_div_2r) pnts = [] pnts.append(polar(C, r, angle)) pnts.append(polar(C, r, - angle)) return pnts def intersectLineCurve(P1, P2, curve, n = 100): ''' Accepts two points of a line P1 & P2, and an array of connected bezier curves [P11, C11, C12, P12, C21, C22, P22, C31, C32, P32, ...] Returns an array points_found[] of point objects where line intersected with the curve, and tangents_found[] of tangent angle at that point ''' # get polar equation for line for P1-P2 # point furthest away from 1st point in curve[] is the fixed point & sets the direction of the angle towards the curve #if distance(P1, curve[0]) > = distance(P2, curve[0] ): # fixed_pnt = P1 # angle = angleOfLine(P1, P2) #else: # fixed_pnt = P2 # angle = angleOfLine(P2, P1) #debug('intersectLineCurve...') #debug('....P1 = ' + P1.id + ' ' + str(P1.x) + ', ' + str(P1.y)) #debug('....P2 = ' + P2.id + ' ' + str(P2.x) + ', ' + str(P2.y)) #for pnt in curve: #debug( '....curve = ' + pnt.id + ' ' + str(pnt.x) + ', ' + str(pnt.y)) fixed_pnt = P1 angle = angleOfLine(P1, P2) intersections = 0 points_found = [] tangents_found = [] pnt = Point() j = 0 while j <= len(curve) -4: # for each bezier curve in curveArray intersection_estimate = intersectLines(P1, P2, curve[j], curve[j + 3]) # is there an intersection? if intersection_estimate != None or intersection_estimate != '': interpolatedPoints = interpolateCurve(curve[j], curve[j + 1], curve[j + 2], curve[j + 3], n) #interpolate this bezier curve, n = 100 k = 0 while k < len(interpolatedPoints)-1: length = distance(fixed_pnt, interpolatedPoints[k]) pnt_on_line = polar(fixed_pnt, length, angle) range = distance(interpolatedPoints[k], interpolatedPoints[k + 1]) # TODO:improve margin of error length = distance(pnt_on_line, interpolatedPoints[k]) #debug(str(k) + 'pntOnCurve = ' + \ # str(interpolatedPoints[k].x) + ', ' + str(interpolatedPoints[k].y) + \ # 'intersectLineAtLength = ' + str(pnt_on_line.x) + ', ' + str( pnt_on_line.y)\ # + 'length = ' + str(length) + 'range = ' + str(range)) if ( length <= range): #debug('its close enough!') if k > 1: if (interpolatedPoints[k-1] not in points_found) and (interpolatedPoints[k-2] not in points_found): points_found.append(interpolatedPoints[k]) tangents_found.append(angleOfLine(interpolatedPoints[k-1], interpolatedPoints[k + 1])) intersections += 1 elif k == 1: if (curve[0] not in intersections): points_found.append(interpolatedPoints[1]) tangents_found.append(angleOfLine(curve[0], interpolatedPoints[2])) intersections += 1 else: intersections.append(curve[0]) tangents_found.append(angleOfLine(curve[0], curve[1])) k += 1 j += 3 # skip j up to P3 of the current curve to be used as P0 start of next curve if intersections == 0: #TODO: better error handling here debug('no intersections found in intersectLineCurve(' + P1.id + ', ' + P2.id + ' and curve') #return points_found, tangents_found return points_found def interpolateCurve(P0, C1, C2, P1, t = 100): ''' Accepts curve points P0, C1, C2, P1 & number of interpolations t Returns array of interpolated points of class Point Adapted from http://www.planetclegg.com/projects/WarpingTextToSplines.htm ''' # calculate coefficients for two knot points P0 & P1 ; C1 & C2 are the controlpoints. # x coefficients A = P1.x-(3 * C2.x) + (3 * C1.x)-P0.x B = (3 * C2.x)-(6 * C1.x) + (3 * P0.x) C = (3 * C1.x)-(3 * P0.x) D = P0.x # y coefficients E = P1.y-(3 * C2.y) + (3 * C1.y)-P0.y F = (3 * C2.y)-(6 * C1.y) + (3 * P0.y) G = (3 * C1.y)-(3 * P0.y) H = P0.y # calculate interpolated points interpolatedPoints = [] maxPoint = float(t) i = 0 while ( i <= t): j = i/maxPoint # j can't be an integer, i/t is an integer..always 0. x = A * (j ** 3) + B * (j ** 2) + C * j + D y = E * (j ** 3) + F * (j ** 2) + G * j + H interpolatedPoints.append(Point('', x, y)) i += 1 return interpolatedPoints #---rotations def slashAndSpread(pivot, angle, *args): """ Accepts pivot point, angle of rotation, and the points to be rotated. Accepts positive & negative angles. """ if (angle == 0.0): print('Angle = 0 -- Slash and Spread not possible') else: list = [] for arg in args: list.append(arg) i = 0 for pnt in list: length = distance(pivot, pnt) rotated_pnt = polar(pivot, length, angleOfLine(pivot, pnt) + angle) # if angle > 0 spread clockwise. if angle < 0 spread counterclockwise updatePoint(pnt, rotated_pnt) return #---darts def foldDart(dart, inside_pnt, seam_allowance): ''' Accepts dart, and the nearest point in the direction dart will be folded Returns dart.m, dart.oc, dart.ic, dart.angle dart.m = middle dart leg at seamline (to be included in seamline path) dart.oc = inside dart leg at cuttingline (to be included in dartline path) dart.oc = outside dart leg at cuttingline (to be included in dartline path) ''' mid_pnt = midPoint(dart.i, dart.o) dart_length = distance(dart, dart.i) i_angle = angleOfLine(dart, dart.i) c_angle = angleOfLine(dart, inside_pnt) dart_angle = abs(angleOfVector(dart.i, dart, dart.o)) dart_half_angle = dart_angle/2.0 #determine which direction the dart will be folded #if ((dart.i.x > dart.x) and (dart.i.y > dart.y)) or ((dart.i.x < dart.x) and (dart.i.y > dart.y)): #x & y vectors not the same sign #dart_half_angle = -dart_half_angle if i_angle > c_angle: dart_half_angle = -dart_half_angle elif dart_angle < c_angle: #dart straddles 0 angle dart_half_angle = -dart_half_angle fold_angle = i_angle + dart_half_angle fold_pnt = intersectLineRay(dart.i, inside_pnt, dart, fold_angle) dart.m = onLineAtLength(dart, mid_pnt, distance(dart, fold_pnt)) #dart midpoint at seamline dart.oc = polar(dart, distance(dart, dart.o) + seam_allowance, angleOfLine(dart, dart.o)) #dart outside leg at cuttingline dart.ic = extendLine(dart, dart.i, seam_allowance) #dart inside leg at cuttingline #create or update dart.angles dart.angle = angleOfVector(dart.i, dart, dart.o) return #---base, pattern & patternpiece groups def base(parent, id): '''Create a base group to contain all patterns, parent should be the document''' newBase = addGroup(parent, id) newBase.set(inkex.addNS('label', 'inkscape'), id) newBase.set(inkex.addNS('groupmode', 'inkscape'), 'layer') return newBase def pattern(parent, id): '''Create a pattern group to hold a single pattern, parent should be the base group''' newPattern = addGroup(parent, id) newPattern.set(inkex.addNS('label', 'inkscape'), id) newPattern.set(inkex.addNS('groupmode', 'inkscape'), 'layer') return newPattern def patternPiece(parent, id, name, fabric = 2, interfacing = 0, lining = 0): '''Create a pattern piece group to hold a single pattern piece, parent should be a pattern group''' newPatternPiece = addGroup(parent, id) newPatternPiece.set(inkex.addNS('label', 'inkscape'), name) newPatternPiece.set(inkex.addNS('groupmode', 'inkscape'), 'layer') return newPatternPiece #---svg def addCircle(parent, id, x, y, radius = 5, fill = 'red', stroke = 'red', stroke_width = '1', reference = 'false'): '''create & write a circle to canvas & set it's attributes''' circ = inkex.etree.SubElement(parent, inkex.addNS('circle', 'svg')) circ.set('id', id) circ.set('cx', str(x)) circ.set('cy', str(y)) circ.set('r', str(radius)) circ.set('fill', fill) circ.set('stroke', stroke) circ.set('stroke-width', stroke_width) if reference == 'true': circ.attrib['reference'] = 'true' return def addSquare(parent, id, w, h, x, y, reference='false'): # create & write a square element, set its attributes square = inkex.etree.SubElement(parent, inkex.addNS('rect', 'svg')) square.set('id', id) square.set('width', str(w)) square.set('height', str(h)) square.set('x', str(x)) square.set('y', str(y)) square.set('stroke', 'none') square.set('fill', '#000000') square.set('stroke-width', '1') if (reference == 'true'): square.attrib['reference'] = 'true' return def addText(parent, id, x, y, text, fontsize = '12', textalign = 'left', textanchor = 'left', reference = 'false'): '''Create a text element, set its attributes, then write to canvas. The text element is different than other elements -- > Set attributes first then write to canvas using append method. There is no inkex.etree.SubElement() method for creating a text element & placing it into the document in one step. Use inkex's etree.Element() method to create an unattached text svg object, then use a document object's append() method to place it on the document canvas''' #create a text element with inkex's Element() txt = inkex.etree.Element(inkex.addNS('text', 'svg')) #set attributes of the text element txt.set('id', id) txt.set('x', str(x)) txt.set('y', str(y)) txt.text = text style = {'text-align':textalign, 'text-anchor':textanchor, 'font-size':fontsize} txt.set('style', formatStyle(style)) if reference == 'true': txt.attrib['reference'] = 'true' #txt.setAttribute('reference', 'true') #alternative syntax #add to canvas in the parent group parent.append(txt) return def addLayer(parent, id): '''Create & write an inkscape group-layer to canvas''' new_layer = inkex.etree.SubElement(parent, 'g') new_layer.set('id', id) new_layer.set(inkex.addNS('groupmode', 'inkscape'), 'layer') new_layer.set(inkex.addNS('label', 'inkscape'), '%s layer' % (id)) return new_layer def addGroup(parent, id): '''Create & write an svg group to canvas''' new_layer = inkex.etree.SubElement(parent, 'g') new_layer.set('id', id) return new_layer def addPath(parent, id, path_str, path_type): '''Accepts parent, id, path string, path type. Creates attribute dictionary. Creates & writes path.''' reference = 'false' if path_type == 'seamline': path_style = {'stroke':'green', 'stroke-width':'4', 'fill':'none', 'opacity':'1', 'stroke-dasharray':'15, 5', 'stroke-dashoffset':'0'} elif path_type == 'cuttingline': path_style = {'stroke':'green', 'stroke-width':'4', 'fill':'none', 'opacity':'1'} elif path_type == 'gridline': path_style = {'stroke':'gray', 'stroke-width':'4', 'fill':'none', 'opacity':'1', 'stroke-dasharray':'6, 6', 'stroke-dashoffset':'0'} reference = 'true' elif path_type == 'dartline': path_style = {'stroke':'gray', 'stroke-width':'4', 'fill':'none', 'opacity':'1'} elif path_type == 'grainline': path_style = {'stroke':'DimGray', 'stroke-width':'3', 'fill':'none', 'opacity':'1', \ 'marker-start':'url(#ArrowStart)', 'marker-end':'url(#ArrowEnd)'} elif path_type == 'slashline': path_style = {'stroke':'green', 'stroke-width':'4', 'fill':'none', 'opacity':'1'} svg_path = inkex.etree.SubElement(parent, inkex.addNS('path', 'svg')) svg_path.set('id', id) svg_path.set('d', path_str) svg_path.set('style', formatStyle(path_style)) if reference == 'true': svg_path.attrib['reference'] = 'true' return svg_path def formatPath( * args): """ Accepts a series of pseudo svg path arguments 'M', 'L', 'C' , and point objects. Returns path_string which is a string formatted for use as the 'd' path attribute in an svg object. """ tokens = [] # initialize an empty array # put all the parameters in * args into the array for arg in args: tokens.append(arg) com = ', ' path_string = '' i = 0 while (i < len(tokens)): cmd = tokens[i] if (cmd == 'M') or (cmd == 'L'): path_string += " %s %g %g" % (cmd, tokens[i + 1].x, tokens[i + 1].y) i = i + 2 elif (cmd == 'C'): path_string += " %s %g %g %g %g %g %g" % (cmd, tokens[i + 1].x, \ tokens[i + 1].y, tokens[i + 2].x, tokens[i + 2].y, tokens[i + 3].x, tokens[i + 3].y) i = i + 4 return path_string def addDefs(doc): '''Add defs group with markers to the document''' defs = inkex.etree.SubElement(doc, inkex.addNS('defs', 'svg')) #add start arrow marker = inkex.etree.SubElement(defs, 'marker', {'id':'ArrowStart', 'orient':'auto', 'refX':'0.0', 'refY':'0.0', 'style':'overflow:visible'}) inkex.etree.SubElement(marker, 'path', {'d':'M 0, 0 L 0, 5 L -20, 0 L 0, -5 z', 'style':'fill:DimGray; stroke:DimGray; stroke-width:0.5'}) #add end arrow marker = inkex.etree.SubElement(defs, 'marker', {'id':'ArrowEnd', 'orient':'auto', 'refX':'0.0', 'refY':'0.0', 'style':'overflow:visible'}) inkex.etree.SubElement(marker, 'path', {'d':'M 0, 0 L 0, 5 L 20, 0 L 0, -5 z', 'style':'fill:DimGray; stroke:DimGray; stroke-width:0.5'})