def test_segment_maths(self): """Segments have calculations""" self.assertEqual(DirectedLineSegment((0, 0), (10, 0)).angle, 0) self.assertAlmostEqual(DirectedLineSegment((0, 0), (0.5 * sqrt(3), 0.5)).angle, pi / 6, delta=1e-6)
def transform_point(tbox, bbox, x, y): """Transform algorithm thanks to Jose Hevia (freon)""" vector = (x, y) - bbox.minimum xratio, yratio = map(truediv, vector, (bbox.width, bbox.height)) horz = DirectedLineSegment(tbox[0].point_at_ratio(xratio), tbox[2].point_at_ratio(xratio)) vert = DirectedLineSegment(tbox[3].point_at_ratio(yratio), tbox[1].point_at_ratio(yratio)) denom = horz.vector.cross(vert.vector) if denom == 0.0: # Degenerate cases of intersecting envelope edges if horz.length == 0.0: return horz.start if vert.length == 0.0: return vert.start # Here we should know that the lines share a start or end point. if horz.vector.dot(vert.vector) < 0: # Segments point opposite directions if (horz.start - vert.start).length <= 1e-8: return horz.start return horz.end # Otherwise they are pointing the same direction if (horz.start - vert.end).length < 1e-8: return horz.start return horz.end # If we didn't hit a degenerate case we can treat the segments as infinite lines intersect_ratio = (vert.start - horz.start).cross(vert.vector) / denom return horz.point_at_ratio(intersect_ratio)
def envelope_box_from_path(self, envelope_path): if len(envelope_path) < 1 or len(envelope_path[0]) < 4: raise inkex.AbortExtension(_("Second selected path is too short. Must be four or more nodes.")) trafo = [[(csp[1][0], csp[1][1]) for csp in subs] for subs in envelope_path][0][:4] #vectors pointing away from the trafo origin tbox = [ DirectedLineSegment(trafo[0], trafo[1]), DirectedLineSegment(trafo[1], trafo[2]), DirectedLineSegment(trafo[3], trafo[2]), DirectedLineSegment(trafo[0], trafo[3]), ] vects = [segment.vector for segment in tbox] if 0.0 == vects[0].cross(vects[1]) == vects[1].cross(vects[2]) == vects[2].cross(vects[3]): raise inkex.AbortExtension(_("The points for the selected envelope must not all be in a line.")) return tbox
def test_segment_dy(self): """Test segment dy calculation""" self.assertEqual(DirectedLineSegment((0, 0), (0, 0)).dy, 0) self.assertEqual(DirectedLineSegment((0, 0), (3, 0)).dy, 0) self.assertEqual(DirectedLineSegment((0, 0), (0, 3)).dy, 3) self.assertEqual(DirectedLineSegment((0, 0), (0, -3)).dy, -3) self.assertEqual(DirectedLineSegment((0, 5), (0, 1)).dy, -4) self.assertEqual(DirectedLineSegment((0, -3), (0, 1)).dy, 4)
def test_segment_dx(self): """Test segment dx calculation""" self.assertEqual(DirectedLineSegment((0, 0), (0, 0)).dx, 0) self.assertEqual(DirectedLineSegment((0, 0), (0, 3)).dx, 0) self.assertEqual(DirectedLineSegment((0, 0), (3, 0)).dx, 3) self.assertEqual(DirectedLineSegment((0, 0), (-3, 0)).dx, -3) self.assertEqual(DirectedLineSegment((5, 0), (1, 0)).dx, -4) self.assertEqual(DirectedLineSegment((-3, 0), (1, 0)).dx, 4)
def test_segment_angle(self): """Test segment angle calculation""" self.assertEqual(DirectedLineSegment((0, 0), (3, 0)).angle, 0) self.assertEqual(DirectedLineSegment((0, 0), (-3, 0)).angle, pi) self.assertEqual(DirectedLineSegment((0, 0), (0, 5)).angle, pi / 2) self.assertEqual(DirectedLineSegment((0, 0), (0, -5)).angle, -pi / 2) self.assertEqual(DirectedLineSegment((2, 0), (0, 0)).angle, pi) self.assertEqual(DirectedLineSegment((-2, 0), (0, 0)).angle, 0) self.assertEqual(DirectedLineSegment((0, 4), (0, 0)).angle, -pi / 2) self.assertEqual(DirectedLineSegment((0, -4), (0, 0)).angle, pi / 2) self.assertEqual(DirectedLineSegment((0, 0), (1, 1)).angle, pi / 4) self.assertEqual( DirectedLineSegment((0, 0), (-1, 1)).angle, 3 * pi / 4) self.assertEqual( DirectedLineSegment((0, 0), (-1, -1)).angle, -3 * pi / 4) self.assertEqual(DirectedLineSegment((0, 0), (1, -1)).angle, -pi / 4)
def test_segment_length(self): """Test segment length calculation""" self.assertEqual(DirectedLineSegment((0, 0), (0, 0)).length, 0) self.assertEqual(DirectedLineSegment((0, 0), (3, 0)).length, 3) self.assertEqual(DirectedLineSegment((0, 0), (-3, 0)).length, 3) self.assertEqual(DirectedLineSegment((0, 0), (0, 5)).length, 5) self.assertEqual(DirectedLineSegment((0, 0), (0, -5)).length, 5) self.assertEqual(DirectedLineSegment((2, 0), (0, 0)).length, 2) self.assertEqual(DirectedLineSegment((-2, 0), (0, 0)).length, 2) self.assertEqual(DirectedLineSegment((0, 4), (0, 0)).length, 4) self.assertEqual(DirectedLineSegment((0, -4), (0, 0)).length, 4) self.assertEqual(DirectedLineSegment((0, 0), (3, 4)).length, 5) self.assertEqual(DirectedLineSegment((-3, -4), (0, 0)).length, 5)
def test_segment_vector(self): """Test segment delta vector""" self.assertEqual( DirectedLineSegment((0, 0), (2, 3)).vector.to_tuple(), (2, 3)) self.assertEqual( DirectedLineSegment((-2, -3), (2, 3)).vector.to_tuple(), (4, 6))
def test_segment_creation(self): """Test segments""" self.assertEqual(DirectedLineSegment((1, 2), (3, 4)), (1, 3, 2, 4)) self.assertEqual(repr(DirectedLineSegment((1, 2), (3, 4))), 'DirectedLineSegment((1, 2), (3, 4))')
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 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))