def test_translation(): pt = PathCanvas() pt.move_to(100, 100) pt.translate(1000, 2000) pt.line_to(10, 20) assert pt.paths == [ Path([Point(100.0, 100.0), Point(1010.0, 2020.0)]), ]
def test_rel_line_to(): pt = PathTiler() pt.translate(1000, 2000) pt.move_to(0, 0) pt.rel_line_to(100, 200) assert pt.paths == [ [Point(1000.0, 2000.0), Point(1100.0, 2200.0)], ]
def test_reflect_xy(): pt = PathCanvas() pt.move_to(100, 100) pt.reflect_xy(1000, 2000) pt.line_to(200, 200) assert pt.paths == [ Path([Point(100.0, 100.0), Point(1800, 3800)]), ]
def test_translation(): pt = PathTiler() pt.move_to(100, 100) pt.translate(1000, 2000) pt.line_to(10, 20) assert pt.paths == [ [Point(100.0, 100.0), Point(1010.0, 2020.0)], ]
def test_reflect_xy(): pt = PathTiler() pt.move_to(100, 100) pt.reflect_xy(1000, 2000) pt.line_to(200, 200) assert pt.paths == [ [Point(100.0, 100.0), Point(1800, 3800)], ]
def test_rel_line_to(): pt = PathCanvas() pt.translate(1000, 2000) pt.move_to(0, 0) pt.rel_line_to(100, 200) assert pt.paths == [ Path([Point(1000.0, 2000.0), Point(1100.0, 2200.0)]), ]
def test_reflect_line(): pt = PathCanvas() pt.move_to(100, 100) pt.reflect_line(Point(50, -50), Point(150, 50)) pt.line_to(100, 50) pt.line_to(100, 100) assert pt.paths == [ Path([Point(100.0, 100.0), Point(150, 0), Point(200, 0)]), ]
def test_reflect_line(): pt = PathTiler() pt.move_to(100, 100) pt.reflect_line(Point(50, -50), Point(150, 50)) pt.line_to(100, 50) pt.line_to(100, 100) assert pt.paths == [ [Point(100.0, 100.0), Point(150, 0), Point(200, 0)], ]
def test_segment_sort_along(p1, p2, tvals): # Get rid of pathological cases. assume(p1.distance(p2) > 0.001) tvals = [t / 100 for t in tvals] fuzz = [1e-10, -1e-10] points = [along_the_way(p1, p2, t) for t in tvals] points = [ Point(x + f, y + f) for (x, y), f in zip(points, itertools.cycle(fuzz)) ] # Calculate the smallest distance between any pair of points. If we get # the wrong answer from sort_along, then the total distance will be off by # at least twice this. min_gap = min(q1.distance(q2) for q1, q2 in all_pairs(points + [p1, p2])) seg = Segment(p1, p2) spoints = seg.sort_along(points) assert len(spoints) == len(points) assert all(pt in points for pt in spoints) original = Point(*p1).distance(Point(*p2)) total = (Point(*p1).distance(Point(*spoints[0])) + sum( Point(*p).distance(Point(*q)) for p, q in adjacent_pairs(spoints)) + Point(*spoints[-1]).distance(Point(*p2))) # The total distance will be wrong by at least 2*min_gap if it is wrong. assert total - original < 2 * min_gap
def test_square_to_paralleogram(pt1, pt2): pt12 = tuple(v1 + v2 for v1, v2 in zip(pt1, pt2)) xform = square_to_parallelogram(pt1, pt2) ins = [(0, 0), (1, 0), (0, 1), (1, 1)] outs = [(0, 0), pt1, pt2, pt12] for i, o in zip(ins, outs): pti = Point(*i) pto = Point(*o) actual = Point(*(xform * pti)) print(pti, pto, actual) assert actual.is_close(pto)
def test_two_segments(): pt = PathTiler() pt.move_to(100, 100) pt.line_to(150, 200) pt.move_to(17, 17) pt.rel_line_to(100, 200) pt.close_path() assert pt.paths == [ [Point(100.0, 100.0), Point(150.0, 200.0)], [Point(17.0, 17.0), Point(117.0, 217.0), Point(17.0, 17.0)], ]
def draw_tile(self, dwg): self.three_points() border_side = Line(self.top, self.bottom) border_shoulder = Line(self.top, self.belly) border_foot = Line(self.belly, self.bottom) offset = self.tilew / 8 side_line = border_side.offset(-offset) shoulder_line = border_shoulder.offset(offset) foot_line = border_foot.offset(offset) with dwg.saved(): dwg.translate(*self.belly) dwg.rotate(30) snip_top = dwg.in_device(0, offset * SQRT2) snip_bottom = dwg.in_device(-offset * SQRT2, 0) snip_top = dwg.in_user(*snip_top) snip_bottom = dwg.in_user(*snip_bottom) snip_line = Line(snip_top, snip_bottom) far_left = Point(-self.tilew * SQRT3 / 2, -self.tilew / 2) shoulder_limit = Line(far_left, self.top) shoulder_limit = shoulder_limit.offset(offset) far_right = Point(self.tilew * SQRT3 / 2, 0) side_limit = Line(self.top, far_right) side_limit = side_limit.offset(offset) side_top = side_line.intersect(side_limit) side_bottom = side_line.intersect(border_foot) shoulder_top = shoulder_line.intersect(shoulder_limit) shoulder_bottom = shoulder_line.intersect(snip_line) foot_top = foot_line.intersect(snip_line) foot_bottom = foot_line.intersect(border_side) dwg.move_to(*side_top) dwg.line_to(*side_bottom) dwg.move_to(*shoulder_top) dwg.line_to(*shoulder_bottom) dwg.line_to(*snip_bottom) dwg.move_to(*snip_top) dwg.line_to(*foot_top) dwg.line_to(*foot_bottom)
def draw_tile(self, dwg): west = Point(0, self.tilew) south = Point(self.tilew, 0) sqw = west.distance(south) southwest = Point(self.tilew-sqw/2, self.tilew-sqw/2) diagonal = Line(west, south) vert = Line(Point(self.tilew-sqw/2, 0), southwest) horz = Line(southwest, Point(0, self.tilew-sqw/2)) wsw = diagonal.intersect(vert) ssw = diagonal.intersect(horz) dwg.move_to(*west) dwg.line_to(*wsw) dwg.line_to(*southwest) dwg.line_to(*ssw) dwg.line_to(*south)
def path_pieces(path, segs_to_points): """Produce a new series of paths, split at intersection points. Yields a series of pieces (paths). The pieces trace the same line as the original path. The endpoints of the pieces are all intersection points in `segs_to_points`, or the endpoints of the original path, if it isn't circular. The pieces are in order along `path`, so consecutive pieces end and begin at the same point. If `path` is closed, then the first piece returned will begin at the first cut, not at the path's first point. """ # If path is circular, then the first piece we collect has to be added to # the last piece, so save it for later. collecting_head = path.closed head = None piece = [] for pt in path: if not piece: piece.append(pt) else: seg = Segment(piece[-1], pt) cuts = segs_to_points.get(seg) if cuts is not None: cuts = seg.sort_along(cuts) for cut in cuts: ptcut = Point(*cut) piece.append(ptcut) if collecting_head: head = piece collecting_head = False else: yield Path(piece) piece = [ptcut] piece.append(pt) piece = Path(piece) if head: piece = piece.join(Path(head)) yield piece
def test_save_restore(): pt = PathTiler() pt.move_to(100, 100) pt.translate(1000, 2000) pt.line_to(10, 20) pt.save() pt.translate(1, 2) pt.move_to(1, 2) pt.line_to(2, 4) pt.restore() pt.move_to(1, 2) pt.line_to(2, 4) assert pt.paths == [ [Point(100.0, 100.0), Point(1010.0, 2020.0)], [Point(1002.0, 2004.0), Point(1003.0, 2006.0)], [Point(1001.0, 2002.0), Point(1002.0, 2004.0)], ]
def debug_world(paths, width, height): dwg = Drawing(paths=paths, name="debug_world", bg=None) # Gray rectangle: the desired visible canvas. with dwg.style(rgb=(.95, .95, .95)): dwg.rectangle(0, 0, width, height) dwg.fill() # Reference grid. llx, lly = dwg.llx, dwg.lly urx = llx + dwg.width ury = lly + dwg.height with dwg.style(rgb=(.5, 1, 1), width=1, dash=[5, 5], dash_offset=7.5): for xmin in tick_range(llx, urx, 20): dwg.move_to(xmin, lly) dwg.line_to(xmin, ury) dwg.stroke() for ymin in tick_range(lly, ury, 20): dwg.move_to(llx, ymin) dwg.line_to(urx, ymin) dwg.stroke() with dwg.style(rgb=(.5, 1, 1), width=1): dwg.circle_points([Point(0, 0)], radius=10) for xmaj in tick_range(llx, urx, 100): dwg.move_to(xmaj, lly) dwg.line_to(xmaj, ury) dwg.stroke() for ymaj in tick_range(lly, ury, 100): dwg.move_to(llx, ymaj) dwg.line_to(urx, ymaj) dwg.stroke() # The paths themselves. dwg.draw_paths(paths, width=1, rgb=(1, 0, 0)) dwg.finish() print("Wrote debug_world.png")
def three_points(self): self.top = Point(0, 0) self.bottom = Point(0, -self.tilew) self.belly = Point(self.tilew * SQRT3 / 4, -self.tilew * .75)
def debug_world(dwg0, paths_styles): """Draw a picture of the entire world. `dwg0` is the Drawing we're really making. `paths_styles` is a list of tuples: (paths, style) for drawing. """ # Get the perimeter of the real drawing. dwg0_path = dwg0.perimeter() # Get the bounds of everything we're going to draw. bounds = EmptyBounds() for paths, styles in paths_styles: bounds |= paths_bounds(paths) bounds |= dwg0_path.bounds() bounds = bounds.expand(percent=2) dwg = Drawing(bounds=bounds, name="debug_world", bg=(.95, .95, .95)) # White rectangle: the desired visible canvas. with dwg.style(rgb=(1, 1, 1)): dwg0_path.draw(dwg) dwg.fill() # Reference grid. llx, lly, urx, ury = dwg.bounds with dwg.style(rgb=(.5, 1, 1), width=1, dash=[5, 5], dash_offset=7.5): for xmin in tick_range(llx, urx, 20): dwg.move_to(xmin, lly) dwg.line_to(xmin, ury) dwg.stroke() for ymin in tick_range(lly, ury, 20): dwg.move_to(llx, ymin) dwg.line_to(urx, ymin) dwg.stroke() with dwg.style(rgb=(.5, 1, 1), width=1): for xmaj in tick_range(llx, urx, 100): dwg.move_to(xmaj, lly) dwg.line_to(xmaj, ury) dwg.stroke() for ymaj in tick_range(lly, ury, 100): dwg.move_to(llx, ymaj) dwg.line_to(urx, ymaj) dwg.stroke() # The origin. with dwg.style(rgb=(0, .75, .75), width=1): dwg.circle_points([Point(0, 0)], radius=10) dwg.move_to(-10, 0) dwg.line_to(10, 0) dwg.move_to(0, -10) dwg.line_to(0, 10) dwg.stroke() # The paths themselves. for paths, styles in paths_styles: dwg.draw_paths(paths, **styles) dwg.finish() print("Wrote debug_world.png")
from zellij.path import Path, combine_paths, equal_path, equal_paths, paths_length from hypothesis import given from hypothesis.strategies import lists, randoms, composite, one_of import pytest from .hypo_helpers import ipoints def P(twodigits): """Helper for compact points: P(34) --> Point(3, 4)""" return Point(*divmod(twodigits, 10)) @pytest.mark.parametrize("compact, result", [ (P(34), Point(3, 4)), (P(20), Point(2, 0)), ]) def test_p(compact, result): assert compact == result @pytest.mark.parametrize( "p1, p2, result", [ # Identical paths. ([P(11), P(22)], [P(11), P(22)], True), # Completely different. ([P(11), P(22)], [P(11), P(33)], False), # Same, but reversed. ([P(11), P(22)], [P(22), P(11)], True),
def test_line_angle(p1, p2, angle): l = Line(Point(*p1), Point(*p2)) assert math.isclose(l.angle(), angle)
def test_intersect(p1, p2, p3, p4, pi): l1 = Line(Point(*p1), Point(*p2)) l2 = Line(Point(*p3), Point(*p4)) assert l1.intersect(l2).is_close(Point(*pi))
def P(twodigits): """Helper for compact points: P(34) --> Point(3, 4)""" return Point(*divmod(twodigits, 10))
[Point(1001.0, 2002.0), Point(1002.0, 2004.0)], ] def test_rel_line_to(): pt = PathTiler() pt.translate(1000, 2000) pt.move_to(0, 0) pt.rel_line_to(100, 200) assert pt.paths == [ [Point(1000.0, 2000.0), Point(1100.0, 2200.0)], ] @pytest.mark.parametrize("p1, p2, result", [ ([Point(0, 0), Point(1, 1)], [ Point(1, 1), Point(2, 0), Point(3, 0) ], [Point(0, 0), Point(1, 1), Point(2, 0), Point(3, 0)]), ([Point(0, 0), Point(1, 1)], [Point(2, 2), Point(3, 3)], None), ([Point(0, 0), Point(1, 1)], [ Point(1, 1), Point(2, 2), Point(3, 0) ], [Point(0, 0), Point(2, 2), Point(3, 0)]), ]) def test_join_paths(p1, p2, result): def same(p1, p2): if p1 is None and p2 is None: return True elif p1 is None or p2 is None: return False else:
def test_no_intersection(p1, p2, p3, p4, err): l1 = Line(Point(*p1), Point(*p2)) l2 = Line(Point(*p3), Point(*p4)) with pytest.raises(err): l1.intersect(l2)
def test_offset(): l1 = Line(Point(10, 10), Point(13, 14)) l2 = l1.offset(10) assert l2.p1 == Point(18, 4) assert l2.p2 == Point(21, 8)
def test_point_equality(p1, p2, result): assert (Point(*p1) == Point(*p2)) == result
def test_point_is_close(p1, p2, result): assert Point(*p1).is_close(Point(*p2)) == result
def test_point_distance(p1, p2, result): assert math.isclose(Point(*p1).distance(Point(*p2)), result)
def test_points_collinear(p1, p2, p3, result): assert collinear(Point(*p1), Point(*p2), Point(*p3)) == result