def test_to_curves(self): """Segments can become curves""" self.assertRaises(ValueError, Move(0, 0).to_curve, None) self.assertEqual( Line(10, 10).to_curve(Vector2d(10, 5)), (10, 5, 10, 10, 10, 10)) self.assertEqual( Horz(10).to_curve(Vector2d(10, 5)), (10, 5, 10, 5, 10, 5)) self.assertEqual( Vert(10).to_curve(Vector2d(5, 10)), (5, 10, 5, 10, 5, 10)) self.assertEqual( Curve(5, 5, 10, 10, 4, 4).to_curve(Vector2d(0, 0)), (5, 5, 10, 10, 4, 4)) self.assertEqual( Smooth(10, 10, 4, 4).to_curve(Vector2d(4, 4), Vector2d(10, 10)), (-2, -2, 10, 10, 4, 4), ) self.assertAlmostTuple( Quadratic(10, 10, 4, 4).to_curve(Vector2d(0, 0)).args, (6.666666666666666, 6.666666666666666, 8, 8, 4, 4), ) self.assertAlmostTuple( TepidQuadratic(4, 4).to_curve(Vector2d(14, 19), Vector2d(11, 12)).args, # (20.666666666666664, 30, 17.333333333333332, 25, 4, 4), (15.999999999999998, 23.666666666666664, 12.666666666666666, 18.666666666666664, 4, 4), ) curves = list(Arc(50, 50, 0, 0, 1, 85, 85).to_curves(Vector2d(0, 0))) self.assertEqual(len(curves), 3) self.assertAlmostTuple( curves[0].args, (19.77590700610636, -5.4865851247611115, 38.18634924829132, -10.4196482558544, 55.44095225512604, -5.796291314453416)) self.assertAlmostTuple( curves[1].args, (72.69555526196076, -1.172934373052433, 86.17293437305243, 12.30444473803924, 90.79629131445341, 29.559047744873958)) self.assertAlmostTuple( curves[2].args, (95.41964825585441, 46.81365075170867, 90.4865851247611, 65.22409299389365, 77.85533905932738, 77.85533905932738)) def apply_to_curve(obj): obj.to_curve(Vector2d()) def apply_to_curves(obj): obj.to_curve(Vector2d()) self.assertRaises(ValueError, apply_to_curve, ZoneClose()) self.assertRaises(ValueError, apply_to_curves, zoneClose()) self.assertRaises(ValueError, apply_to_curve, Move(0, 0)) self.assertRaises(ValueError, apply_to_curves, move(0, 0))
def test_transformation(self): t = Transform(matrix=((1, 2, 3), (4, 5, 6))) first = Vector2d() prev = Vector2d(31, 97) prev_prev = Vector2d(5, 7) for Cmd in (Line, Move, Curve, Smooth, Quadratic, TepidQuadratic, Arc): random_seg = self.get_random_cmd(Cmd) self.assertTrue(random_seg.transform(t) is not random_seg) # transform returns copy self.assertEqual( random_seg.transform(t).name, Cmd.name) # transform does not change Command type T = Transform() T.add_translate(10, 20) A = [ T.apply_to_point(p) for p in random_seg.control_points(first, prev, prev_prev) ] first2, prev2, prev_prev2 = (T.apply_to_point(p) for p in (first, prev, prev_prev)) B = list( random_seg.translate(Vector2d(10, 20)).control_points( first2, prev2, prev_prev2)) self.assertAlmostTuple(A, B) T = Transform() T.add_scale(10, 20) A = [ T.apply_to_point(p) for p in random_seg.control_points(first, prev, prev_prev) ] first2, prev2, prev_prev2 = (T.apply_to_point(p) for p in (first, prev, prev_prev)) B = list( random_seg.scale( (10, 20)).control_points(first2, prev2, prev_prev2)) self.assertAlmostTuple(A, B) T = Transform() T.add_rotate(35, 15, 28) A = [ T.apply_to_point(p) for p in random_seg.control_points(first, prev, prev_prev) ] first2, prev2, prev_prev2 = (T.apply_to_point(p) for p in (first, prev, prev_prev)) B = list( random_seg.rotate(35, Vector2d(15, 28)).control_points( first2, prev2, prev_prev2)) self.assertAlmostTuple(A, B)
def process_path(self, node, transform): """Process the given element into a plotter path""" pen = self.get_pen_number(node) speed = self.get_pen_speed(node) force = self.get_pen_force(node) path = node.path.to_absolute()\ .transform(node.composed_transform())\ .transform(transform)\ .to_superpath() if path: cspsubdiv(path, self.flat) # path to HPGL commands oldPosX = 0.0 oldPosY = 0.0 for singlePath in path: cmd = 'PU' for singlePathPoint in singlePath: posX, posY = singlePathPoint[1] # check if point is repeating, if so, ignore if int(round(posX)) != int(round(oldPosX)) \ or int(round(posY)) != int(round(oldPosY)): self.processOffset(cmd, Vector2d(posX, posY), pen, speed, force) cmd = 'PD' oldPosX = posX oldPosY = posY # perform overcut if self.overcut > 0.0 and not self.dryRun: # check if last and first points are the same, otherwise the path # is not closed and no overcut can be performed if int(round(oldPosX)) == int(round(singlePath[0][1][0])) and\ int(round(oldPosY)) == int(round(singlePath[0][1][1])): overcutLength = 0 for singlePathPoint in singlePath: posX, posY = singlePathPoint[1] # check if point is repeating, if so, ignore if int(round(posX)) != int(round(oldPosX)) or int(round(posY))\ != int(round(oldPosY)): overcutLength += (Vector2d(posX, posY) - (oldPosX, oldPosY)).length if overcutLength >= self.overcut: newEndPoint = self.changeLength(Vector2d(oldPosX, oldPosY),\ Vector2d(posX, posY), - (overcutLength - self.overcut)) self.processOffset(cmd, newEndPoint, pen, speed, force) break self.processOffset(cmd, Vector2d(posX, posY), pen, speed, force) oldPosX = posX oldPosY = posY
def test_binary_operators(self): """Test binary operators for vector2d""" vec1 = Vector2d(15, 22) vec2 = Vector2d(5, 3) self.assertTrue((vec1 - vec2).is_close((10, 19))) self.assertTrue((vec1 - (5, 3)).is_close((10, 19))) self.assertTrue(((15, 22) - vec2).is_close((10, 19))) self.assertTrue((vec1 + vec2).is_close((20, 25))) self.assertTrue((vec1 + (5, 3)).is_close((20, 25))) self.assertTrue(((15, 22) + vec2).is_close((20, 25))) self.assertTrue((vec1 * 2).is_close((30, 44))) self.assertTrue((2 * vec1).is_close((30, 44))) self.assertTrue((vec1 / 2).is_close((7.5, 11))) self.assertTrue((vec1.__div__(2)).is_close((7.5, 11))) self.assertTrue((vec1 // 2).is_close((7.5, 11)))
def test_assign(self): """Test vector2d assignement""" vec = Vector2d(10, 20) vec.assign(5, 10) self.assertAlmostTuple(vec, (5, 10)) vec.assign((7, 11)) self.assertAlmostTuple(vec, (7, 11))
def test_polar_operations(self): """Test polar coordinates operations""" # x y r pi equivilents = [(0, 0, 0, 0), (0, 0, 0, 1), (0, 0, 0, -1), (0, 0, 0, 0.5), (1, 0, 1, 0), (0, 1, 1, 0.5), (0, -1, 1, -0.5), (3, 0, 3, 0), (0, 3, 3, 0.5), (0, -3, 3, -0.5), (sqrt(2), sqrt(2), 2, 0.25), (-sqrt(2), sqrt(2), 2, 0.75), (sqrt(2), -sqrt(2), 2, -0.25), (-sqrt(2), -sqrt(2), 2, -0.75)] for x, y, r, t in equivilents: theta = t * pi if r != 0 else None for ts in [0, 2, -2]: ctx_msg = 'Test values are x: {} y: {} r: {} θ: {} * pi'.format( x, y, r, t + ts) polar = Vector2d.from_polar(r, (t + ts) * pi) cart = Vector2d(x, y) self.assertEqual(cart.length, r, msg=ctx_msg) self.assertEqual(polar.length, r, msg=ctx_msg) self.assertAlmostEqual(cart.angle, theta, msg=ctx_msg, delta=1e-12) self.assertAlmostEqual(polar.angle, theta, msg=ctx_msg, delta=1e-12) self.assertEqual(cart.to_polar_tuple(), (r, cart.angle), msg=ctx_msg) self.assertEqual(polar.to_polar_tuple(), (r, polar.angle), msg=ctx_msg) self.assertEqual(cart.to_tuple(), (x, y), msg=ctx_msg) self.assertAlmostEqual(polar.to_tuple()[0], x, msg=ctx_msg, delta=1e-12) self.assertAlmostEqual(polar.to_tuple()[1], y, msg=ctx_msg, delta=1e-12) # Test special handling of from_polar with None theta self.assertEqual(Vector2d.from_polar(0, None).to_tuple(), (0.0, 0.0)) self.assertIsNone(Vector2d.from_polar(4, None))
def test_vector_creation(self): """Test Vector2D creation""" vec0 = Vector2d(15, 22) self.assertEqual(vec0.x, 15) self.assertEqual(vec0.y, 22) vec1 = Vector2d() self.assertEqual(vec1.x, 0) self.assertEqual(vec1.y, 0) vec2 = Vector2d((17, 32)) self.assertEqual(vec2.x, 17) self.assertEqual(vec2.y, 32) vec3 = Vector2d(vec0) self.assertEqual(vec3.x, 15) self.assertEqual(vec3.y, 22) self.assertRaises(ValueError, Vector2d, (1)) self.assertRaises(ValueError, Vector2d, (1, 2, 3))
def _path_points(self, elt): 'Return a list of all points on a path (endpoints, not control points).' pts = set() first = None prev = Vector2d() for cmd in elt.path.to_absolute(): if first is None: first = cmd.end_point(first, prev) ep = cmd.end_point(first, prev) pts.add((ep.x, ep.y)) prev = ep return pts
def test_ioperators(self): """Test operators for vector2d""" vec0 = vec = Vector2d(15, 22) vec += (1, 1) self.assertTrue(vec.is_close((16, 23))) vec -= (10, 20) self.assertTrue(vec.is_close((6, 3))) vec *= 5 self.assertTrue(vec.is_close((30, 15))) vec /= 90 self.assertTrue(vec.is_close((1.0 / 3, 1.0 / 6))) vec //= 1.0 / 3 self.assertTrue(vec.is_close((1, 0.5))) self.assertFalse(vec0.is_close((15, 22))) self.assertTrue(vec0.is_close(vec))
def test_absolute_relative(self): absolutes = Line, Move, Curve, Smooth, Quadratic, TepidQuadratic, Arc, Vert, Horz, ZoneClose relatives = line, move, curve, smooth, quadratic, tepidQuadratic, arc, vert, horz, zoneClose zero = Vector2d() for R, A in zip(relatives, absolutes): rel = self.get_random_cmd(R) ab = self.get_random_cmd(A) self.assertTrue(rel.is_relative) self.assertTrue(ab.is_absolute) self.assertFalse(rel.is_absolute) self.assertFalse(ab.is_relative) self.assertEqual(type(rel.to_absolute(zero)), A) self.assertEqual(type(ab.to_relative(zero)), R) self.assertTrue(rel.to_relative(zero) is not rel) self.assertTrue(ab.to_absolute(zero) is not ab)
def effect(self): for node in self.svg.selection.filter(inkex.PathElement): result = Path() prev = Vector2d() start = None for seg in node.path.to_absolute(): if start is None: start = seg.end_point(start, prev) if isinstance(seg, Curve): result += [ Move(seg.x2, seg.y2), Line(prev.x, prev.y), Move(seg.x3, seg.y3), Line(seg.x4, seg.y4), ] elif isinstance(seg, Quadratic): result += [ Move(seg.x2, seg.y2), Line(prev.x, prev.y), Move(seg.x2, seg.y2), Line(seg.x3, seg.y3) ] prev = seg.end_point(start, prev) if not result: continue elem = node.getparent().add(inkex.PathElement()) elem.path = result.transform(node.transform) elem.style = { 'stroke-linejoin': 'miter', 'stroke-width': '1.0px', 'stroke-opacity': '1.0', 'fill-opacity': '1.0', 'stroke': '#000000', 'stroke-linecap': 'butt', 'fill': 'none' }
def makeface(last, segment, facegroup, delx, dely): """translate path segment along vector""" elem = facegroup.add(inkex.PathElement()) npt = segment.translate([delx, dely]) # reverse direction of path segment if isinstance(segment, Curve): rev = Curve(npt.x3, npt.y3, npt.x2, npt.y2, last[0] + delx, last[1] + dely) elif isinstance(segment, Line): rev = Line(last[0] + delx, last[1] + dely) else: raise RuntimeError("Unexpected segment type {}".format( type(segment))) elem.path = inkex.Path([ Move(last[0], last[1]), segment, npt.to_line(Vector2d()), rev, ZoneClose(), ])
def getHpgl(self): """Return the HPGL instructions""" # dryRun to find edges transform = Transform( [[self.mirrorX * self.scaleX * self.viewBoxTransformX, 0.0, 0.0], [0.0, self.mirrorY * self.scaleY * self.viewBoxTransformY, 0.0]]) transform.add_rotate(int(self.options.orientation)) self.vData = [['', 'False', 0], ['', 'False', 0], ['', 'False', 0], ['', 'False', 0]] self.process_group(self.doc, transform) if self.divergenceX == 'False' or self.divergenceY == 'False' or self.sizeX == 'False' or self.sizeY == 'False': raise NoPathError("No paths found") # live run self.dryRun = False # move drawing according to various modifiers if self.options.autoAlign: if self.options.center: self.offsetX -= (self.sizeX - self.divergenceX) / 2 self.offsetY -= (self.sizeY - self.divergenceY) / 2 else: self.divergenceX = 0.0 self.divergenceY = 0.0 if self.options.center: if self.options.orientation == '0': self.offsetX -= (self.docWidth * self.scaleX) / 2 self.offsetY += (self.docHeight * self.scaleY) / 2 if self.options.orientation == '90': self.offsetY += (self.docWidth * self.scaleX) / 2 self.offsetX += (self.docHeight * self.scaleY) / 2 if self.options.orientation == '180': self.offsetX += (self.docWidth * self.scaleX) / 2 self.offsetY -= (self.docHeight * self.scaleY) / 2 if self.options.orientation == '270': self.offsetY -= (self.docWidth * self.scaleX) / 2 self.offsetX -= (self.docHeight * self.scaleY) / 2 else: if self.options.orientation == '0': self.offsetY += self.docHeight * self.scaleY if self.options.orientation == '90': self.offsetY += self.docWidth * self.scaleX self.offsetX += self.docHeight * self.scaleY if self.options.orientation == '180': self.offsetX += self.docWidth * self.scaleX if not self.options.center and self.toolOffset > 0.0: self.offsetX += self.toolOffset self.offsetY += self.toolOffset # initialize transformation matrix and cache transform = Transform( [[ self.mirrorX * self.scaleX * self.viewBoxTransformX, 0.0, -float(self.divergenceX) + self.offsetX ], [ 0.0, self.mirrorY * self.scaleY * self.viewBoxTransformY, -float(self.divergenceY) + self.offsetY ]]) transform.add_rotate(int(self.options.orientation)) self.vData = [['', 'False', 0], ['', 'False', 0], ['', 'False', 0], ['', 'False', 0]] # add move to zero point and precut if self.toolOffset > 0.0 and self.options.precut: if self.options.center: # position precut outside of drawing plus one time the tooloffset if self.offsetX >= 0.0: precutX = self.offsetX + self.toolOffset else: precutX = self.offsetX - self.toolOffset if self.offsetY >= 0.0: precutY = self.offsetY + self.toolOffset else: precutY = self.offsetY - self.toolOffset self.processOffset('PU', Vector2d(precutX, precutY), self.options.pen, self.options.speed, self.options.force) self.processOffset( 'PD', Vector2d(precutX, precutY + self.toolOffset * 8), self.options.pen, self.options.speed, self.options.force) else: self.processOffset('PU', Vector2d(0, 0), self.options.pen, self.options.speed, self.options.force) self.processOffset('PD', Vector2d(0, self.toolOffset * 8), self.options.pen, self.options.speed, self.options.force) # start conversion self.process_group(self.doc, transform) # shift an empty node in in order to process last node in cache if self.toolOffset > 0.0 and not self.dryRun: self.processOffset('PU', Vector2d(0, 0), 0, 0, 0) return self.hpgl
def test_getitem(self): """Test getitem for Vector2D""" vec = Vector2d(10, 20) self.assertEqual(len(vec), 2) self.assertEqual(vec[0], 10) self.assertEqual(vec[1], 20)
def changeLength(self, p1, p2, offset): """change length of line""" if p1.x == p2.x and p1.y == p2.y: # abort if points are the same return p1 return Vector2d(DirectedLineSegment(p2, p1).point_at_length(-offset))
def test_representations(self): """Test Vector2D Repr""" self.assertEqual(str(Vector2d(1, 2)), "1, 2") self.assertEqual(repr(Vector2d(1, 2)), "Vector2d(1, 2)") self.assertEqual(Vector2d(1, 2).to_tuple(), (1, 2))
def processOffset(self, cmd, point, pen, speed, force): """ Calculate offset correction """ if self.toolOffset == 0.0 or self.dryRun: self.storePoint(cmd, point, pen, speed, force) else: # insert data into cache self.vData.pop(0) self.vData.insert(3, [cmd, point, pen, speed, force]) # decide if enough data is available if self.vData[2][1] != 'False': if self.vData[1][1] == 'False': self.storePoint(self.vData[2][0], self.vData[2][1], self.vData[2][2], self.vData[2][3], self.vData[2][4]) else: # perform tool offset correction (It's a *tad* complicated, if you want # to understand it draw the data as lines on paper) if self.vData[2][0] == 'PD': # If the 3rd entry in the cache is a pen down command, # make the line longer by the tool offset pointThree = self.changeLength(self.vData[1][1], self.vData[2][1], self.toolOffset) self.storePoint('PD', pointThree, self.vData[2][2], self.vData[2][3], self.vData[2][4]) elif self.vData[0][1] != 'False': # Elif the 1st entry in the cache is filled with data and the 3rd entry # is a pen up command shift the 3rd entry by the current tool offset # position according to the 2nd command pointThree = self.changeLength(self.vData[0][1], self.vData[1][1], self.toolOffset) pointThree = self.vData[2][1] - (self.vData[1][1] - pointThree) self.storePoint('PU', pointThree, self.vData[2][2], self.vData[2][3], self.vData[2][4]) else: # Else just write the 3rd entry pointThree = self.vData[2][1] self.storePoint('PU', pointThree, self.vData[2][2], self.vData[2][3], self.vData[2][4]) if self.vData[3][0] == 'PD': # If the 4th entry in the cache is a pen down command guide tool to next # line with a circle between the prolonged 3rd and 4th entry originalSegment = DirectedLineSegment( self.vData[2][1], self.vData[3][1]) if originalSegment.length >= self.toolOffset: pointFour = self.changeLength(originalSegment.end,\ originalSegment.start, - self.toolOffset) else: pointFour = self.changeLength(originalSegment.start,\ originalSegment.end, self.toolOffset - originalSegment.length) # get angle start and angle vector angleStart = DirectedLineSegment( self.vData[2][1], pointThree).angle angleVector = DirectedLineSegment(self.vData[2][1], pointFour).angle\ - angleStart # switch direction when arc is bigger than 180° if angleVector > math.pi: angleVector -= math.pi * 2 elif angleVector < -math.pi: angleVector += math.pi * 2 # draw arc if angleVector >= 0: angle = angleStart + self.toolOffsetFlat while angle < angleStart + angleVector: self.storePoint('PD', self.vData[2][1] + self.toolOffset *\ Vector2d(math.cos(angle), math.sin(angle)), self.vData[2][2], self.vData[2][3], self.vData[2][4]) angle += self.toolOffsetFlat else: angle = angleStart - self.toolOffsetFlat while angle > angleStart + angleVector: self.storePoint('PD', self.vData[2][1] + self.toolOffset *\ Vector2d(math.cos(angle), math.sin(angle)), self.vData[2][2], self.vData[2][3], self.vData[2][4]) angle -= self.toolOffsetFlat self.storePoint('PD', pointFour, self.vData[3][2], self.vData[2][3], self.vData[2][4])
def test_unary_operators(self): """Test unary operators""" vec = Vector2d(1, 2) self.assertTrue((-vec).is_close((-1, -2))) self.assertTrue((+vec).is_close(vec)) self.assertTrue(+vec is not vec) # returned value is a copy
def test_svg_center_position(self): """SVG with namedview has a center position""" doc = svg_file(self.data_file('svg', 'multilayered-test.svg')) self.assertTrue(doc.namedview.center.is_close((30.714286, 520.0))) self.assertTrue(svg().namedview.center.is_close(Vector2d()))
def test_to_line(self): self.assertEqual(Vert(3).to_line(Vector2d(5, 11)), Line(5, 3)) self.assertEqual(Horz(3).to_line(Vector2d(5, 11)), Line(3, 11)) self.assertEqual(vert(3).to_line(Vector2d(5, 11)), Line(5, 14)) self.assertEqual(horz(3).to_line(Vector2d(5, 11)), Line(8, 11))
def apply_to_curves(obj): obj.to_curve(Vector2d())
def effect(self): path_num = 0 elems = [] npaths = [] sstr = None for selem in self.svg.selection.filter(PathElement): elems.append(copy.deepcopy(selem)) if len(elems) == 0: raise inkex.AbortExtension("Nothing selected") for elem in elems: escale = 1.0 npaths.clear() #inkex.utils.debug(elem.attrib) if 'style' in elem.attrib: sstr = elem.attrib['style'] if 'transform' in elem.attrib: transforms = elem.attrib['transform'].split() for tf in transforms: if tf.startswith('scale'): escale = float(tf.split('(')[1].split(')')[0]) if sstr != None: lsstr = sstr.split(';') for stoken in range(len(lsstr)): if lsstr[stoken].startswith('stroke-width'): swt = lsstr[stoken].split(':')[1] if not swt[2:].isalpha( ): # is value expressed in units (e.g. px)? swf = str(float(swt) * escale) # no. scale it lsstr[stoken] = lsstr[stoken].replace(swt, swf) if lsstr[stoken].startswith('stroke-miterlimit'): swt = lsstr[stoken].split(':')[1] if not swt[2:].isalpha( ): # is value expressed in units (e.g. px)? swf = str(float(swt) * escale) # no. scale it lsstr[stoken] = lsstr[stoken].replace(swt, swf) sstr = ";".join(lsstr) else: sstr = None elem.apply_transform() xbound, ybound = elem.bounding_box() # Get bounds of this element xmin, xmax = xbound ymin, ymax = ybound ntotal = len(elem.path) nodecnt = 0 startx = 0 starty = ymax + 10 * escale endx = 0 endy = starty xoffset = 0 orig_sx = 0 orig_sy = 0 orig_ex = 0 orig_ey = 0 sx1 = 0 sy1 = 0 orig_length = 0 prev = Vector2d() for ptoken in elem.path.to_absolute( ): # For each point in the path startx = xmin + xoffset if ptoken.letter == 'M': # Starting a new line orig_sx = ptoken.x orig_sy = ptoken.y cd = Path() cd.append(Move(startx, starty)) sx1 = orig_sx sy1 = orig_sy else: if last_letter != 'M': orig_sx = orig_ex orig_sy = orig_ey if ptoken.letter == 'L': orig_ex = ptoken.x orig_ey = ptoken.y orig_length = math.sqrt((orig_sx - orig_ex)**2 + (orig_sy - orig_ey)**2) endx = startx + orig_length cd.append(Line(endx, endy)) elif ptoken.letter == 'H': orig_ey = ptoken.to_line(prev).y orig_length = abs(orig_sx - ptoken.x) orig_ex = ptoken.x endx = startx + orig_length cd.append(Line(endx, endy)) elif ptoken.letter == 'V': orig_ex = ptoken.to_line(prev).x orig_length = abs(orig_sy - ptoken.y) orig_ey = ptoken.y endx = startx + orig_length cd.append(Line(endx, endy)) elif ptoken.letter == 'Z': orig_ex = sx1 orig_ey = sy1 orig_length = math.sqrt((orig_sx - orig_ex)**2 + (orig_sy - orig_ey)**2) endx = startx + orig_length cd.append(Line(endx, endy)) elif ptoken.letter == 'C': bez = ptoken.to_bez() # [[x0,y0][x1,y1][x2,y2][x3,y3]] bez.insert(0, [prev.x, prev.y]) orig_ex = bez[3][0] #x3 orig_ey = bez[3][1] #y3 orig_length = inkex.bezier.bezierlength(bez) endx = startx + orig_length cd.append(Line(endx, endy)) else: raise inkex.AbortExtension( "Unknown letter - {0}".format(ptoken.letter)) nodecnt = nodecnt + 1 if ptoken.letter != 'M': if nodecnt == ntotal: self.drawline(str(cd), "hline{0}".format(path_num), self.svg.get_current_layer(), sstr) path_num = path_num + 1 xoffset = xoffset + orig_length prev.x = orig_ex prev.y = orig_ey else: prev.x = orig_sx prev.y = orig_sy last_letter = ptoken.letter