From 9f9af7589dce72fbefd934332a12390a8af856ab Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Tue, 26 Mar 2024 21:22:07 +1000 Subject: [PATCH 01/45] Add reportlab to requirements --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index ee05403b7..3bf0616d8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,4 +12,5 @@ pytest >= 8.0.0 pytest-console-scripts pytest-randomly pytest-subtests +reportlab >= 4.1.0 ruff >= 0.3.0 From a3478df42262b5f2e7c3fbe2ac62df896f992c14 Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Tue, 26 Mar 2024 21:52:39 +1000 Subject: [PATCH 02/45] Rename PDFSectorMap to PDFHexMap --- PyRoute/Outputs/{PDFSectorMap.py => PDFHexMap.py} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename PyRoute/Outputs/{PDFSectorMap.py => PDFHexMap.py} (98%) diff --git a/PyRoute/Outputs/PDFSectorMap.py b/PyRoute/Outputs/PDFHexMap.py similarity index 98% rename from PyRoute/Outputs/PDFSectorMap.py rename to PyRoute/Outputs/PDFHexMap.py index a2d80e871..1aa5bbe58 100644 --- a/PyRoute/Outputs/PDFSectorMap.py +++ b/PyRoute/Outputs/PDFHexMap.py @@ -14,9 +14,9 @@ from PyRoute.StatCalculation import StatCalculation -class PDFSectorMap(Map): +class PDFHexMap(Map): def __init__(self, galaxy, routes): - super(PDFSectorMap, self).__init__(galaxy, routes) + super(PDFHexMap, self).__init__(galaxy, routes) self.lineStart = PDFCursor(0, 0) self.lineEnd = PDFCursor(0, 0) From 43434d3841fe289649dfb11e9cf7e2559b6107c5 Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Tue, 26 Mar 2024 22:05:10 +1000 Subject: [PATCH 03/45] Add min_btn field to PDFHexMap --- PyRoute/Outputs/Map.py | 7 +++++++ PyRoute/Outputs/PDFHexMap.py | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/PyRoute/Outputs/Map.py b/PyRoute/Outputs/Map.py index 53fecd88d..92bef4583 100644 --- a/PyRoute/Outputs/Map.py +++ b/PyRoute/Outputs/Map.py @@ -75,6 +75,13 @@ def get_line(self, doc, start, end, colorname, width): # color.set_color_by_name(colorname) # hline = PDFLine(pdf.session, pdf.page, hlineStart, hlineEnd, stroke='solid', color=color, size=width) + def trade_line(self, pdf, edge, data): + raise NotImplementedError("Base Class") + + @staticmethod + def string_width(font, string): + raise NotImplementedError("Base Class") + def place_system(self, doc, star): """ Write a single world information into the map diff --git a/PyRoute/Outputs/PDFHexMap.py b/PyRoute/Outputs/PDFHexMap.py index 1aa5bbe58..0111f49e8 100644 --- a/PyRoute/Outputs/PDFHexMap.py +++ b/PyRoute/Outputs/PDFHexMap.py @@ -15,10 +15,11 @@ class PDFHexMap(Map): - def __init__(self, galaxy, routes): + def __init__(self, galaxy, routes, min_btn=8): super(PDFHexMap, self).__init__(galaxy, routes) self.lineStart = PDFCursor(0, 0) self.lineEnd = PDFCursor(0, 0) + self.min_btn = min_btn def document(self, sector): """ From 5a1a11d28b6023d72b10bdb6d3f85171509d77ec Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Tue, 26 Mar 2024 22:12:55 +1000 Subject: [PATCH 04/45] Clone existing map tests for PDFHexMap This is ultimately laying groundwork for the switch to reportLab. By using a new class, I'm aiming ultimately to generate hypothesis-powered regression tests for the maps, which, from experience, will massively constrain where mismatches can lurk. --- PyRoute/Outputs/PDFHexMap.py | 5 +- Tests/Outputs/testHexMap.py | 205 +++++++++++++++++++++++++++++++++++ 2 files changed, 209 insertions(+), 1 deletion(-) diff --git a/PyRoute/Outputs/PDFHexMap.py b/PyRoute/Outputs/PDFHexMap.py index 0111f49e8..1bd922fd9 100644 --- a/PyRoute/Outputs/PDFHexMap.py +++ b/PyRoute/Outputs/PDFHexMap.py @@ -21,11 +21,13 @@ def __init__(self, galaxy, routes, min_btn=8): self.lineEnd = PDFCursor(0, 0) self.min_btn = min_btn - def document(self, sector): + def document(self, sector, is_live=True): """ Generated by the type of document """ path = os.path.join(self.galaxy.output_path, sector.sector_name() + " Sector.pdf") + if not is_live: + path = "string" self.writer = PDFLite(path) title = "Sector %s" % sector @@ -34,6 +36,7 @@ def document(self, sector): keywords = None creator = "PyPDFLite" self.writer.set_information(title, subject, author, keywords, creator) + self.writer.set_compression(is_live) document = self.writer.get_document() document.set_margins(4) return document diff --git a/Tests/Outputs/testHexMap.py b/Tests/Outputs/testHexMap.py index 8932ddfdd..30cedc320 100644 --- a/Tests/Outputs/testHexMap.py +++ b/Tests/Outputs/testHexMap.py @@ -9,6 +9,7 @@ from PyRoute.DeltaDebug.DeltaDictionary import DeltaDictionary, SectorDictionary from PyRoute.DeltaDebug.DeltaGalaxy import DeltaGalaxy from PyRoute.Outputs.HexMap import HexMap +from PyRoute.Outputs.PDFHexMap import PDFHexMap from Tests.baseTest import baseTest @@ -59,6 +60,47 @@ def test_document_object(self): self.assertEqual('PyPDFLite', hexmap.writer.creator) self.assertEqual(expected_path, hexmap.writer.filepath) + def test_document_object_pdf(self): + sourcefile = self.unpack_filename('DeltaFiles/no_subsectors_named/Zao Kfeng Ig Grilokh empty.sec') + + args = self._make_args() + args.interestingline = None + args.interestingtype = None + args.maps = True + args.subsectors = True + + delta = DeltaDictionary() + sector = SectorDictionary.load_traveller_map_file(sourcefile) + delta[sector.name] = sector + + galaxy = DeltaGalaxy(args.btn, args.max_jump) + galaxy.read_sectors(delta, args.pop_code, args.ru_calc, + args.route_reuse, args.routes, args.route_btn, args.mp_threads, args.debug_flag) + galaxy.output_path = args.output + + secname = 'Zao Kfeng Ig Grilokh' + + hexmap = PDFHexMap(galaxy, 'trade') + + blurb = [ + ("Live map", True, os.path.abspath(args.output + '/Zao Kfeng Ig Grilokh Sector.pdf')), + ("Regression map", False, "string") + ] + + for msg, is_live, expected_path in blurb: + with self.subTest(msg): + document = hexmap.document(galaxy.sectors[secname], is_live=is_live) + self.assertEqual(4, document.margins.left, 'Unexpected margins value') + # check writer properties + if is_live: + self.assertTrue(hexmap.writer.session.compression, 'PDF writer compression not set') + else: + self.assertFalse(hexmap.writer.session.compression, 'PDF writer compression set') + self.assertEqual('Sector Zao Kfeng Ig Grilokh (-2,4)', hexmap.writer.title) + self.assertEqual('Trade route map generated by PyRoute for Traveller', hexmap.writer.subject) + self.assertEqual('PyPDFLite', hexmap.writer.creator) + self.assertEqual(expected_path, hexmap.writer.filepath) + def test_verify_empty_sector_write(self): sourcefile = self.unpack_filename('DeltaFiles/no_subsectors_named/Zao Kfeng Ig Grilokh empty.sec') @@ -103,6 +145,50 @@ def test_verify_empty_sector_write(self): result = self.md5line.sub(oldmd5, result) self.assertEqual(expected_result, result) + def test_verify_empty_sector_write_pdf(self): + sourcefile = self.unpack_filename('DeltaFiles/no_subsectors_named/Zao Kfeng Ig Grilokh empty.sec') + + outfile = self.unpack_filename('OutputFiles/verify_empty_sector_write/Zao Kfeng Ig Grilokh empty.txt') + + args = self._make_args() + args.interestingline = None + args.interestingtype = None + args.maps = True + args.subsectors = True + + delta = DeltaDictionary() + sector = SectorDictionary.load_traveller_map_file(sourcefile) + delta[sector.name] = sector + + galaxy = DeltaGalaxy(args.btn, args.max_jump) + galaxy.read_sectors(delta, args.pop_code, args.ru_calc, + args.route_reuse, args.routes, args.route_btn, args.mp_threads, args.debug_flag) + + galaxy.output_path = args.output + + secname = 'Zao Kfeng Ig Grilokh' + + hexmap = PDFHexMap(galaxy, 'trade') + + oldtime = b'20230911163653' + oldmd5 = b'8419949643701e6b438d6f3f93239cf7' + + with open(outfile, 'rb') as file: + expected_result = file.read() + + result = hexmap.write_sector_pdf_map(galaxy.sectors[secname], is_live=False) + self.assertIsNotNone(result) + # rather than try to mock datetime.now(), patch the output result. + # this also lets us check that there's only a single match + matches = self.timeline.search(result) + self.assertEqual(1, len(matches.groups()), 'Should be exactly one create-date match') + result = self.timeline.sub(oldtime, result) + # likewise patch md5 outout + matches = self.md5line.findall(result) + self.assertEqual(2, len(matches), 'Should be exactly two MD5 matches') + result = self.md5line.sub(oldmd5, result) + self.assertEqual(expected_result, result) + @pytest.mark.xfail(reason='Flaky on ubuntu') def test_verify_subsector_trade_write(self): sourcefile = self.unpack_filename('DeltaFiles/no_subsectors_named/Zao Kfeng Ig Grilokh - subsector P.sec') @@ -163,6 +249,65 @@ def test_verify_subsector_trade_write(self): result = self.md5line.sub(oldmd5, result) self.assertEqual(expected_result, result) + def test_verify_subsector_trade_write_pdf(self): + sourcefile = self.unpack_filename('DeltaFiles/no_subsectors_named/Zao Kfeng Ig Grilokh - subsector P.sec') + outfile = self.unpack_filename('OutputFiles/verify_subsector_trade_write/Zao Kfeng Ig Grilokh - subsector P - trade.txt') + + starsfile = self.unpack_filename('OutputFiles/verify_subsector_trade_write/trade stars.txt') + rangesfile = self.unpack_filename('OutputFiles/verify_subsector_trade_write/trade ranges.txt') + + args = self._make_args() + args.interestingline = None + args.interestingtype = None + args.maps = True + args.subsectors = True + args.routes = 'trade' + + delta = DeltaDictionary() + sector = SectorDictionary.load_traveller_map_file(sourcefile) + delta[sector.name] = sector + + galaxy = DeltaGalaxy(args.btn, args.max_jump) + galaxy.read_sectors(delta, args.pop_code, args.ru_calc, + args.route_reuse, args.routes, args.route_btn, args.mp_threads, args.debug_flag) + galaxy.output_path = args.output + + galaxy.generate_routes() + + with open(starsfile, 'rb') as file: + galaxy.stars = nx.read_edgelist(file, nodetype=int) + self.assertEqual(26, len(galaxy.stars.nodes()), "Unexpected number of stars nodes") + self.assertEqual(53, len(galaxy.stars.edges), "Unexpected number of stars edges") + for item in galaxy.stars.edges(data=True): + self.assertIn('trade', item[2], 'Trade value not set during edgelist read') + + self._load_ranges(galaxy, rangesfile) + self.assertEqual(27, len(galaxy.ranges.nodes()), "Unexpected number of ranges nodes") + self.assertEqual(44, len(galaxy.ranges.edges), "Unexpected number of ranges edges") + + secname = 'Zao Kfeng Ig Grilokh' + + hexmap = PDFHexMap(galaxy, 'trade') + + oldtime = b'20230912001440' + oldmd5 = b'b1f97f6ac37340ab332a9a0568711ec0' + + with open(outfile, 'rb') as file: + expected_result = file.read() + + result = hexmap.write_sector_pdf_map(galaxy.sectors[secname], is_live=False) + self.assertIsNotNone(result) + # rather than try to mock datetime.now(), patch the output result. + # this also lets us check that there's only a single match + matches = self.timeline.search(result) + self.assertEqual(1, len(matches.groups()), 'Should be exactly one create-date match') + result = self.timeline.sub(oldtime, result) + # likewise patch md5 output + matches = self.md5line.findall(result) + self.assertEqual(2, len(matches), 'Should be exactly two MD5 matches') + result = self.md5line.sub(oldmd5, result) + self.assertEqual(expected_result, result) + def test_verify_subsector_comm_write(self): sourcefile = self.unpack_filename('DeltaFiles/no_subsectors_named/Zao Kfeng Ig Grilokh - subsector P.sec') outfile = self.unpack_filename('OutputFiles/verify_subsector_comm_write/Zao Kfeng Ig Grilokh - subsector P - comm.txt') @@ -223,6 +368,66 @@ def test_verify_subsector_comm_write(self): result = self.md5line.sub(oldmd5, result) self.assertEqual(expected_result, result) + def test_verify_subsector_comm_write_pdf(self): + sourcefile = self.unpack_filename('DeltaFiles/no_subsectors_named/Zao Kfeng Ig Grilokh - subsector P.sec') + outfile = self.unpack_filename('OutputFiles/verify_subsector_comm_write/Zao Kfeng Ig Grilokh - subsector P - comm.txt') + + starsfile = self.unpack_filename('OutputFiles/verify_subsector_comm_write/comm stars.txt') + + rangesfile = self.unpack_filename('OutputFiles/verify_subsector_comm_write/comm ranges.txt') + + args = self._make_args() + args.interestingline = None + args.interestingtype = None + args.maps = True + args.routes = 'comm' + args.subsectors = True + + delta = DeltaDictionary() + sector = SectorDictionary.load_traveller_map_file(sourcefile) + delta[sector.name] = sector + + galaxy = DeltaGalaxy(args.btn, args.max_jump) + galaxy.read_sectors(delta, args.pop_code, args.ru_calc, + args.route_reuse, args.routes, args.route_btn, args.mp_threads, args.debug_flag) + galaxy.output_path = args.output + + galaxy.generate_routes() + + with open(starsfile, 'rb') as file: + galaxy.stars = nx.read_edgelist(file, nodetype=int) + self.assertEqual(5, len(galaxy.stars.nodes()), "Unexpected number of stars nodes") + self.assertEqual(4, len(galaxy.stars.edges), "Unexpected number of stars edges") + for item in galaxy.stars.edges(data=True): + self.assertIn('trade', item[2], 'Trade value not set during edgelist read') + + self._load_ranges(galaxy, rangesfile) + self.assertEqual(27, len(galaxy.ranges.nodes()), "Unexpected number of ranges nodes") + self.assertEqual(28, len(galaxy.ranges.edges), "Unexpected number of ranges edges") + + secname = 'Zao Kfeng Ig Grilokh' + + hexmap = PDFHexMap(galaxy, 'trade') + + oldtime = b'20230912013953' + oldmd5 = b'ff091edb9d8ca0abacea39e5791a9843' + + with open(outfile, 'rb') as file: + expected_result = file.read() + + result = hexmap.write_sector_pdf_map(galaxy.sectors[secname], is_live=False) + self.assertIsNotNone(result) + # rather than try to mock datetime.now(), patch the output result. + # this also lets us check that there's only a single match + matches = self.timeline.search(result) + self.assertEqual(1, len(matches.groups()), 'Should be exactly one create-date match') + result = self.timeline.sub(oldtime, result) + # likewise patch md5 output + matches = self.md5line.findall(result) + self.assertEqual(2, len(matches), 'Should be exactly two MD5 matches') + result = self.md5line.sub(oldmd5, result) + self.assertEqual(expected_result, result) + def _load_ranges(self, galaxy, sourcefile): with open(sourcefile, "rb") as f: lines = f.readlines() From 33968868fe5f85081f3d41c60833b70ec2839366 Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Tue, 26 Mar 2024 22:56:11 +1000 Subject: [PATCH 05/45] Add sector tracking to Map base class --- PyRoute/Outputs/Map.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/PyRoute/Outputs/Map.py b/PyRoute/Outputs/Map.py index 92bef4583..baf8f4034 100644 --- a/PyRoute/Outputs/Map.py +++ b/PyRoute/Outputs/Map.py @@ -88,6 +88,9 @@ def place_system(self, doc, star): """ raise NotImplementedError("Base Class") + def write_sector_pdf_map(self, gal_sector, is_live=True): + raise NotImplementedError("Base Class") + def write_maps(self): """ Starting point for writing PDF files. @@ -219,6 +222,7 @@ def _draw_borders(self, x, y, hline, lline, rline): lline._draw() def draw_borders(self, pdf, sector): + self.sector = sector self.hex_grid(pdf, self._draw_borders, 1.5, 'salmon') @staticmethod From d8b2589f0f2c3f85938d198a0d9fde86d78f41bb Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Tue, 26 Mar 2024 23:23:52 +1000 Subject: [PATCH 06/45] Fix missing-method complaints --- PyRoute/Outputs/Map.py | 3 + PyRoute/Outputs/PDFHexMap.py | 255 +++++++++++++++++++++++++++++++---- 2 files changed, 233 insertions(+), 25 deletions(-) diff --git a/PyRoute/Outputs/Map.py b/PyRoute/Outputs/Map.py index baf8f4034..fbde3d38c 100644 --- a/PyRoute/Outputs/Map.py +++ b/PyRoute/Outputs/Map.py @@ -91,6 +91,9 @@ def place_system(self, doc, star): def write_sector_pdf_map(self, gal_sector, is_live=True): raise NotImplementedError("Base Class") + def system(self, pdf, star): + raise NotImplementedError("Base Class") + def write_maps(self): """ Starting point for writing PDF files. diff --git a/PyRoute/Outputs/PDFHexMap.py b/PyRoute/Outputs/PDFHexMap.py index 1bd922fd9..ec5148b68 100644 --- a/PyRoute/Outputs/PDFHexMap.py +++ b/PyRoute/Outputs/PDFHexMap.py @@ -4,6 +4,7 @@ @author: CyberiaResurrection """ import os +import logging from pypdflite import PDFCursor, PDFLite from pypdflite.pdfobjects.pdfellipse import PDFEllipse @@ -21,31 +22,50 @@ def __init__(self, galaxy, routes, min_btn=8): self.lineEnd = PDFCursor(0, 0) self.min_btn = min_btn - def document(self, sector, is_live=True): - """ - Generated by the type of document - """ - path = os.path.join(self.galaxy.output_path, sector.sector_name() + " Sector.pdf") - if not is_live: - path = "string" - self.writer = PDFLite(path) - - title = "Sector %s" % sector - subject = "Trade route map generated by PyRoute for Traveller" - author = None - keywords = None - creator = "PyPDFLite" - self.writer.set_information(title, subject, author, keywords, creator) - self.writer.set_compression(is_live) - document = self.writer.get_document() - document.set_margins(4) - return document - - def close(self): - self.writer.close() - - def cursor(self, x=0, y=0): - return PDFCursor(x, y) + def write_sector_pdf_map(self, gal_sector, is_live=True): + pdf_doc = self.document(gal_sector, is_live) + self.write_base_map(pdf_doc, gal_sector) + self.draw_borders(pdf_doc, gal_sector) + worlds = [item.index for item in gal_sector.worlds] + comm_routes = [star for star in self.galaxy.stars.edges(worlds, True) + if star[2].get('xboat', False) or star[2].get('comm', False)] + for (star, neighbor, data) in comm_routes: + srcstar = self.galaxy.star_mapping[star] + trgstar = self.galaxy.star_mapping[neighbor] + self.comm_line(pdf_doc, [srcstar, trgstar]) + sector_trade = [star for star in self.galaxy.stars.edges(worlds, True) + if star[2]['trade'] > 0 and StatCalculation.trade_to_btn(star[2]['trade']) >= self.min_btn] + logging.getLogger('PyRoute.HexMap').debug("Worlds with trade: {}".format(len(sector_trade))) + sector_trade.sort(key=lambda line: line[2]['trade']) + for (star, neighbor, data) in sector_trade: + self.galaxy.stars[star][neighbor]['trade btn'] = StatCalculation.trade_to_btn(data['trade']) + srcstar = self.galaxy.star_mapping[star] + trgstar = self.galaxy.star_mapping[neighbor] + self.trade_line(pdf_doc, [srcstar, trgstar], data) + # Get all the worlds in this sector + # for (star, neighbor, data) in self.galaxy.stars.edges(sector.worlds, True): + # if star.sector != sector: + # continue# + # if data['trade'] > 0 and self.trade_to_btn(data['trade']) >= self.min_btn: + # self.galaxy.stars[star][neighbor]['trade btn'] = self.trade_to_btn(data['trade']) + # self.trade_line(pdf, [star, neighbor], data) + # elif star.sector != neighbor.sector: + # data = self.galaxy.stars.get_edge_data(neighbor, star) + # if data is not None and \ + # data['trade'] > 0 and \ + # self.trade_to_btn(data['trade']) >= self.min_btn: + # self.trade_line(pdf, [star, neighbor], data) + for star in gal_sector.worlds: + self.system(pdf_doc, star) + if gal_sector.coreward: + self.coreward_sector(pdf_doc, gal_sector.coreward.name) + if gal_sector.rimward: + self.rimward_sector(pdf_doc, gal_sector.rimward.name) + if gal_sector.spinward: + self.spinward_sector(pdf_doc, gal_sector.spinward.name) + if gal_sector.trailing: + self.trailing_sector(pdf_doc, gal_sector.trailing.name) + return self.writer.close() def sector_name(self, doc, name): """ @@ -59,6 +79,7 @@ def sector_name(self, doc, name): doc.add_text(name, cursor) doc.set_font(font=def_font) + def coreward_sector(self, pdf, name): cursor = PDFCursor(5, self.y_start - 15, True) def_font = pdf.get_font() @@ -96,6 +117,183 @@ def trailing_sector(self, pdf, name): text._text(name) pdf.set_font(font=def_font) + + def system(self, pdf, star): + def_font = pdf.get_font() + pdf.set_font('times', size=4) + + col = (self.xm * 3 * (star.col)) + if (star.col & 1): + row = (self.y_start - self.ym * 2) + (star.row * self.ym * 2) + else: + row = (self.y_start - self.ym) + (star.row * self.ym * 2) + + point = PDFCursor(col, row) + self.zone(pdf, star, point.copy()) + + width = self.string_width(pdf.get_font(), str(star.uwp)) + point.y_plus(7) + point.x_plus(self.ym - (width // 2)) + pdf.add_text(str(star.uwp), point) + + if len(star.name) > 0: + for chars in range(len(star.name), 0, -1): + width = self.string_width(pdf.get_font(), star.name[:chars]) + if width <= self.xm * 3.5: + break + point.y_plus(3.5) + point.x = col + point.x_plus(self.ym - (width // 2)) + pdf.add_text(star.name[:chars], point) + + added = star.alg_code + if star.tradeCode.subsector_capital: + added += '+' + elif star.tradeCode.sector_capital or star.tradeCode.other_capital: + added += '*' + else: + added += ' ' + + added += '{:d}'.format(star.ggCount) + point.y_plus(3.5) + point.x = col + width = pdf.get_font()._string_width(added) + point.x_plus(self.ym - (width // 2)) + pdf.add_text(added, point) + + added = '' + tradeIn = StatCalculation.trade_to_btn(star.tradeIn) + tradeThrough = StatCalculation.trade_to_btn(star.tradeIn + star.tradeOver) + + if self.routes == 'trade': + added += "{:X}{:X}{:X}{:d}".format(star.wtn, tradeIn, tradeThrough, star.starportSize) + elif self.routes == 'comm': + added += "{}{} {}".format(star.baseCode, star.ggCount, star.importance) + elif self.routes == 'xroute': + added += " {}".format(star.importance) + width = pdf.get_font()._string_width(added) + point.y_plus(3.5) + point.x = col + point.x_plus(self.ym - (width // 2)) + pdf.add_text(added, point) + + pdf.set_font(def_font) + + + def trade_line(self, pdf, edge, data): + + tradeColors = [(255, 0, 0), # Red + (224, 224, 16), # yellow - darker + (0, 255, 0), # green + (0, 255, 255), # Cyan + (96, 96, 255), # blue - lighter + (128, 0, 128), # purple + (148, 0, 211), # violet + ] + + start = edge[0] + end = edge[1] + + trade = StatCalculation.trade_to_btn(data['trade']) - self.min_btn + if trade < 0: + return + if trade > 6: + logging.getLogger('PyRoute.HexMap').warn("trade calculated over %d" % self.min_btn + 6) + trade = 6 + + tradeColor = tradeColors[trade] + color = pdf.get_color() + color.set_color_by_number(tradeColor[0], tradeColor[1], tradeColor[2]) + + endCircle = end.sector == start.sector + endx, endy, startx, starty = self._get_line_endpoints(end, start) + + lineStart = PDFCursor(startx, starty) + lineEnd = PDFCursor(endx, endy) + + line = PDFLine(pdf.session, pdf.page, lineStart, lineEnd, stroke='solid', color=color, size=1) + line._draw() + + radius = PDFCursor(2, 2) + circle = PDFEllipse(pdf.session, pdf.page, lineStart, radius, color, size=3) + circle._draw() + + if endCircle: + circle = PDFEllipse(pdf.session, pdf.page, lineEnd, radius, color, size=3) + circle._draw() + + def comm_line(self, pdf, edge): + start = edge[0] + end = edge[1] + color = pdf.get_color() + color.set_color_by_number(102, 178, 102) + + endx, endy, startx, starty = self._get_line_endpoints(end, start) + + lineStart = PDFCursor(startx, starty) + lineEnd = PDFCursor(endx, endy) + + line = PDFLine(pdf.session, pdf.page, lineStart, lineEnd, stroke='solid', color=color, size=3) + line._draw() + + def _get_line_endpoints(self, end, start): + starty = self.y_start + (self.ym * 2 * (start.row)) - (self.ym * (1 if start.col & 1 else 0)) + startx = (self.xm * 3 * (start.col)) + self.ym + endRow = end.row + endCol = end.col + if (end.sector != start.sector): + up = False + down = False + if end.sector.x < start.sector.x: + endCol -= 32 + if end.sector.x > start.sector.x: + endCol += 32 + if end.sector.y > start.sector.y: + endRow -= 40 + up = True + if end.sector.y < start.sector.y: + endRow += 40 + down = True + endy = self.y_start + (self.ym * 2 * (endRow)) - (self.ym * (1 if endCol & 1 else 0)) + endx = (self.xm * 3 * endCol) + self.ym + + (startx, starty), (endx, endy) = self.clipping(startx, starty, endx, endy) + if up: + assert starty >= endy, "Misaligned to-coreward trade segment between " + str(start) + " and " + str(end) + if down: + assert starty <= endy, "Misaligned to-rimward trade segment between " + str(start) + " and " + str(end) + + else: + endy = self.y_start + (self.ym * 2 * (endRow)) - (self.ym * (1 if endCol & 1 else 0)) + endx = (self.xm * 3 * endCol) + self.ym + return endx, endy, startx, starty + + def document(self, sector, is_live=True): + """ + Generated by the type of document + """ + path = os.path.join(self.galaxy.output_path, sector.sector_name() + " Sector.pdf") + if not is_live: + path = "string" + self.writer = PDFLite(path) + + title = "Sector %s" % sector + subject = "Trade route map generated by PyRoute for Traveller" + author = None + keywords = None + creator = "PyPDFLite" + self.writer.set_information(title, subject, author, keywords, creator) + self.writer.set_compression(is_live) + document = self.writer.get_document() + document.set_margins(4) + return document + + def close(self): + self.writer.close() + + def cursor(self, x=0, y=0): + return PDFCursor(x, y) + def add_line(self, pdf, start, end, colorname): """ Add a line to the document, from start to end, in color @@ -180,3 +378,10 @@ def place_system(self, pdf, star): pdf.add_text(added, point) pdf.set_font(def_font) + + @staticmethod + def string_width(font, string): + w = 0 + for i in string: + w += font.character_widths[i] if i in font.character_widths else 600 + return w * font.font_size / 1000.0 From ccffe744de5504b7c9e38c2e7ba8eedee02518e0 Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Wed, 27 Mar 2024 00:19:28 +1000 Subject: [PATCH 07/45] Get cloned tests passing --- PyRoute/Outputs/PDFHexMap.py | 100 +++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/PyRoute/Outputs/PDFHexMap.py b/PyRoute/Outputs/PDFHexMap.py index ec5148b68..10fa1c317 100644 --- a/PyRoute/Outputs/PDFHexMap.py +++ b/PyRoute/Outputs/PDFHexMap.py @@ -67,6 +67,11 @@ def write_sector_pdf_map(self, gal_sector, is_live=True): self.trailing_sector(pdf_doc, gal_sector.trailing.name) return self.writer.close() + def write_base_map(self, pdf, sector): + self.sector_name(pdf, sector.name) + self.subsector_grid(pdf) + self.hex_grid(pdf, self._draw_all, 0.5) + def sector_name(self, doc, name): """ Write name at the top of the document @@ -117,6 +122,101 @@ def trailing_sector(self, pdf, name): text._text(name) pdf.set_font(font=def_font) + def subsector_grid(self, pdf): + color = pdf.get_color() + color.set_color_by_name('lightgray') + pdf.set_draw_color(color) + vlineStart = PDFCursor(0, self.y_start + self.xm) + vlineEnd = PDFCursor(0, self.y_start + self.xm + (180 * 4)) + for x in range(self.x_start, 595, 144): + vlineStart.x = x + vlineEnd.x = x + pdf.add_line(cursor1=vlineStart, cursor2=vlineEnd) + + hlineStart = PDFCursor(self.x_start, 0) + hlineEnd = PDFCursor(591, 0) + for y in range(self.y_start + self.xm, 780, 180): + hlineStart.y = y + hlineEnd.y = y + pdf.add_line(cursor1=hlineStart, cursor2=hlineEnd) + + def _hline(self, pdf, width, colorname): + hlineStart = PDFCursor(0, 0) + hlineStart.x = 3 + hlineStart.y = self.y_start - self.ym + hlineStart.dx = self.xm * 3 + hlineStart.dy = self.ym * 2 + + hlineEnd = PDFCursor(0, 0) + hlineEnd.x = self.xm * 2.5 + hlineEnd.y = self.y_start - self.ym + hlineEnd.dx = self.xm * 3 + hlineEnd.dy = self.ym * 2 + + color = pdf.get_color() + color.set_color_by_name(colorname) + + hline = PDFLine(pdf.session, pdf.page, hlineStart, hlineEnd, stroke='solid', color=color, size=width) + + return (hlineStart, hlineEnd, hline) + + def _hline_restart_y(self, x, hlineStart, hlineEnd): + if (x & 1): + hlineStart.y = self.y_start - self.ym + hlineEnd.y = self.y_start - self.ym + else: + hlineStart.y = self.y_start - 2 * self.ym + hlineEnd.y = self.y_start - 2 * self.ym + + def _lline(self, pdf, width, colorname): + llineStart = PDFCursor(-10, 0) + llineStart.x = self.x_start + llineStart.dx = self.xm * 3 + llineStart.dy = self.ym * 2 + + llineEnd = PDFCursor(-10, 0) + llineEnd.x = self.x_start + self.xm + llineEnd.dx = self.xm * 3 + llineEnd.dy = self.ym * 2 + + color = pdf.get_color() + color.set_color_by_name(colorname) + + lline = PDFLine(pdf.session, pdf.page, llineStart, llineEnd, stroke='solid', color=color, size=width) + + return (llineStart, llineEnd, lline) + + def _lline_restart_y(self, x, llineStart, llineEnd): + if (x & 1): + llineStart.y = self.y_start - 2 * self.ym + llineEnd.y = self.y_start - self.ym + else: + llineStart.y = self.y_start - self.ym + llineEnd.y = self.y_start - 2 * self.ym + + def _rline(self, pdf, width, colorname): + rlineStart = PDFCursor(0, 0) + rlineStart.x = self.x_start + self.xm + rlineStart.dx = self.xm * 3 + rlineStart.dy = self.ym * 2 + rlineEnd = PDFCursor(0, 0) + rlineEnd.x = self.x_start + rlineEnd.dx = self.xm * 3 + rlineEnd.dy = self.ym * 2 + + color = pdf.get_color() + color.set_color_by_name(colorname) + rline = PDFLine(pdf.session, pdf.page, rlineStart, rlineEnd, stroke='solid', color=color, size=width) + + return (rlineStart, rlineEnd, rline) + + def _rline_restart_y(self, x, rlineStart, rlineEnd): + if (x & 1): + rlineStart.y = self.y_start - 3 * self.ym + rlineEnd.y = self.y_start - 2 * self.ym + else: + rlineStart.y = self.y_start - 2 * self.ym + rlineEnd.y = self.y_start - 3 * self.ym def system(self, pdf, star): def_font = pdf.get_font() From d313b7aee7d03fcb3bcff353093c21d57d900d03 Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Wed, 27 Mar 2024 00:26:03 +1000 Subject: [PATCH 08/45] Rip out obsolete code in PDFHexMap --- PyRoute/Outputs/PDFHexMap.py | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/PyRoute/Outputs/PDFHexMap.py b/PyRoute/Outputs/PDFHexMap.py index 10fa1c317..f84e4ab4c 100644 --- a/PyRoute/Outputs/PDFHexMap.py +++ b/PyRoute/Outputs/PDFHexMap.py @@ -42,19 +42,7 @@ def write_sector_pdf_map(self, gal_sector, is_live=True): srcstar = self.galaxy.star_mapping[star] trgstar = self.galaxy.star_mapping[neighbor] self.trade_line(pdf_doc, [srcstar, trgstar], data) - # Get all the worlds in this sector - # for (star, neighbor, data) in self.galaxy.stars.edges(sector.worlds, True): - # if star.sector != sector: - # continue# - # if data['trade'] > 0 and self.trade_to_btn(data['trade']) >= self.min_btn: - # self.galaxy.stars[star][neighbor]['trade btn'] = self.trade_to_btn(data['trade']) - # self.trade_line(pdf, [star, neighbor], data) - # elif star.sector != neighbor.sector: - # data = self.galaxy.stars.get_edge_data(neighbor, star) - # if data is not None and \ - # data['trade'] > 0 and \ - # self.trade_to_btn(data['trade']) >= self.min_btn: - # self.trade_line(pdf, [star, neighbor], data) + for star in gal_sector.worlds: self.system(pdf_doc, star) if gal_sector.coreward: @@ -84,7 +72,6 @@ def sector_name(self, doc, name): doc.add_text(name, cursor) doc.set_font(font=def_font) - def coreward_sector(self, pdf, name): cursor = PDFCursor(5, self.y_start - 15, True) def_font = pdf.get_font() @@ -279,7 +266,6 @@ def system(self, pdf, star): pdf.set_font(def_font) - def trade_line(self, pdf, edge, data): tradeColors = [(255, 0, 0), # Red From a382280222eb7515f2843706f64577e20d500547 Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Wed, 27 Mar 2024 00:27:11 +1000 Subject: [PATCH 09/45] Clean up redundant brackets --- PyRoute/Outputs/PDFHexMap.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/PyRoute/Outputs/PDFHexMap.py b/PyRoute/Outputs/PDFHexMap.py index f84e4ab4c..e90125387 100644 --- a/PyRoute/Outputs/PDFHexMap.py +++ b/PyRoute/Outputs/PDFHexMap.py @@ -145,10 +145,10 @@ def _hline(self, pdf, width, colorname): hline = PDFLine(pdf.session, pdf.page, hlineStart, hlineEnd, stroke='solid', color=color, size=width) - return (hlineStart, hlineEnd, hline) + return hlineStart, hlineEnd, hline def _hline_restart_y(self, x, hlineStart, hlineEnd): - if (x & 1): + if x & 1: hlineStart.y = self.y_start - self.ym hlineEnd.y = self.y_start - self.ym else: @@ -171,10 +171,10 @@ def _lline(self, pdf, width, colorname): lline = PDFLine(pdf.session, pdf.page, llineStart, llineEnd, stroke='solid', color=color, size=width) - return (llineStart, llineEnd, lline) + return llineStart, llineEnd, lline def _lline_restart_y(self, x, llineStart, llineEnd): - if (x & 1): + if x & 1: llineStart.y = self.y_start - 2 * self.ym llineEnd.y = self.y_start - self.ym else: @@ -195,10 +195,10 @@ def _rline(self, pdf, width, colorname): color.set_color_by_name(colorname) rline = PDFLine(pdf.session, pdf.page, rlineStart, rlineEnd, stroke='solid', color=color, size=width) - return (rlineStart, rlineEnd, rline) + return rlineStart, rlineEnd, rline def _rline_restart_y(self, x, rlineStart, rlineEnd): - if (x & 1): + if x & 1: rlineStart.y = self.y_start - 3 * self.ym rlineEnd.y = self.y_start - 2 * self.ym else: @@ -209,8 +209,8 @@ def system(self, pdf, star): def_font = pdf.get_font() pdf.set_font('times', size=4) - col = (self.xm * 3 * (star.col)) - if (star.col & 1): + col = (self.xm * 3 * star.col) + if star.col & 1: row = (self.y_start - self.ym * 2) + (star.row * self.ym * 2) else: row = (self.y_start - self.ym) + (star.row * self.ym * 2) @@ -323,11 +323,11 @@ def comm_line(self, pdf, edge): line._draw() def _get_line_endpoints(self, end, start): - starty = self.y_start + (self.ym * 2 * (start.row)) - (self.ym * (1 if start.col & 1 else 0)) - startx = (self.xm * 3 * (start.col)) + self.ym + starty = self.y_start + (self.ym * 2 * start.row) - (self.ym * (1 if start.col & 1 else 0)) + startx = (self.xm * 3 * start.col) + self.ym endRow = end.row endCol = end.col - if (end.sector != start.sector): + if end.sector != start.sector: up = False down = False if end.sector.x < start.sector.x: @@ -340,7 +340,7 @@ def _get_line_endpoints(self, end, start): if end.sector.y < start.sector.y: endRow += 40 down = True - endy = self.y_start + (self.ym * 2 * (endRow)) - (self.ym * (1 if endCol & 1 else 0)) + endy = self.y_start + (self.ym * 2 * endRow) - (self.ym * (1 if endCol & 1 else 0)) endx = (self.xm * 3 * endCol) + self.ym (startx, starty), (endx, endy) = self.clipping(startx, starty, endx, endy) @@ -350,7 +350,7 @@ def _get_line_endpoints(self, end, start): assert starty <= endy, "Misaligned to-rimward trade segment between " + str(start) + " and " + str(end) else: - endy = self.y_start + (self.ym * 2 * (endRow)) - (self.ym * (1 if endCol & 1 else 0)) + endy = self.y_start + (self.ym * 2 * endRow) - (self.ym * (1 if endCol & 1 else 0)) endx = (self.xm * 3 * endCol) + self.ym return endx, endy, startx, starty @@ -408,8 +408,8 @@ def place_system(self, pdf, star): def_font = pdf.get_font() pdf.set_font('times', size=4) - col = (self.xm * 3 * (star.col)) - if (star.col & 1): + col = (self.xm * 3 * star.col) + if star.col & 1: row = (self.y_start - self.ym * 2) + (star.row * self.ym * 2) else: row = (self.y_start - self.ym) + (star.row * self.ym * 2) From a3fbc2610a8a4ae771223c33aa92e99abb16edbf Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Wed, 27 Mar 2024 03:26:57 +1000 Subject: [PATCH 10/45] Encaps compression setting --- PyRoute/Outputs/HexMap.py | 3 +++ PyRoute/Outputs/PDFHexMap.py | 4 ++++ Tests/Outputs/testHexMap.py | 8 ++++---- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/PyRoute/Outputs/HexMap.py b/PyRoute/Outputs/HexMap.py index 8ca3f9a00..19a45cdad 100644 --- a/PyRoute/Outputs/HexMap.py +++ b/PyRoute/Outputs/HexMap.py @@ -514,6 +514,9 @@ def clipping(self, startx, starty, endx, endy): logging.getLogger("PyRoute.HexMap").debug(result) return (result[0], result[1]), (result[2], result[3]) + @property + def compression(self): + return self.writer.session.compression if __name__ == '__main__': sector = Sector('# Core', '# 0,0') diff --git a/PyRoute/Outputs/PDFHexMap.py b/PyRoute/Outputs/PDFHexMap.py index e90125387..fe7afd5dc 100644 --- a/PyRoute/Outputs/PDFHexMap.py +++ b/PyRoute/Outputs/PDFHexMap.py @@ -471,3 +471,7 @@ def string_width(font, string): for i in string: w += font.character_widths[i] if i in font.character_widths else 600 return w * font.font_size / 1000.0 + + @property + def compression(self): + return self.writer.session.compression diff --git a/Tests/Outputs/testHexMap.py b/Tests/Outputs/testHexMap.py index 30cedc320..fd8194a84 100644 --- a/Tests/Outputs/testHexMap.py +++ b/Tests/Outputs/testHexMap.py @@ -52,9 +52,9 @@ def test_document_object(self): self.assertEqual(4, document.margins.left, 'Unexpected margins value') # check writer properties if is_live: - self.assertTrue(hexmap.writer.session.compression, 'PDF writer compression not set') + self.assertTrue(hexmap.compression, 'PDF writer compression not set') else: - self.assertFalse(hexmap.writer.session.compression, 'PDF writer compression set') + self.assertFalse(hexmap.compression, 'PDF writer compression set') self.assertEqual('Sector Zao Kfeng Ig Grilokh (-2,4)', hexmap.writer.title) self.assertEqual('Trade route map generated by PyRoute for Traveller', hexmap.writer.subject) self.assertEqual('PyPDFLite', hexmap.writer.creator) @@ -93,9 +93,9 @@ def test_document_object_pdf(self): self.assertEqual(4, document.margins.left, 'Unexpected margins value') # check writer properties if is_live: - self.assertTrue(hexmap.writer.session.compression, 'PDF writer compression not set') + self.assertTrue(hexmap.compression, 'PDF writer compression not set') else: - self.assertFalse(hexmap.writer.session.compression, 'PDF writer compression set') + self.assertFalse(hexmap.compression, 'PDF writer compression set') self.assertEqual('Sector Zao Kfeng Ig Grilokh (-2,4)', hexmap.writer.title) self.assertEqual('Trade route map generated by PyRoute for Traveller', hexmap.writer.subject) self.assertEqual('PyPDFLite', hexmap.writer.creator) From 3c0824253d6cb212679c2c3bb595fa0c6545ea87 Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Wed, 27 Mar 2024 03:29:45 +1000 Subject: [PATCH 11/45] Define writer attrib in ctor --- PyRoute/Outputs/HexMap.py | 2 ++ PyRoute/Outputs/PDFHexMap.py | 1 + 2 files changed, 3 insertions(+) diff --git a/PyRoute/Outputs/HexMap.py b/PyRoute/Outputs/HexMap.py index 19a45cdad..480318dc1 100644 --- a/PyRoute/Outputs/HexMap.py +++ b/PyRoute/Outputs/HexMap.py @@ -32,6 +32,8 @@ def __init__(self, galaxy, routes, min_btn=8): self.min_btn = min_btn self.y_start = 43 self.x_start = 15 + self.sector = None + self.writer = None def write_maps(self): """ diff --git a/PyRoute/Outputs/PDFHexMap.py b/PyRoute/Outputs/PDFHexMap.py index fe7afd5dc..011c070f1 100644 --- a/PyRoute/Outputs/PDFHexMap.py +++ b/PyRoute/Outputs/PDFHexMap.py @@ -21,6 +21,7 @@ def __init__(self, galaxy, routes, min_btn=8): self.lineStart = PDFCursor(0, 0) self.lineEnd = PDFCursor(0, 0) self.min_btn = min_btn + self.writer = None def write_sector_pdf_map(self, gal_sector, is_live=True): pdf_doc = self.document(gal_sector, is_live) From 0fa520fc8ff3ffd3a38f604bd491841e19ffd3bf Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Wed, 27 Mar 2024 08:14:18 +1000 Subject: [PATCH 12/45] Stop PDFHexMap.sector_name barfing --- PyRoute/Outputs/PDFHexMap.py | 44 +++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/PyRoute/Outputs/PDFHexMap.py b/PyRoute/Outputs/PDFHexMap.py index 011c070f1..ae13f135a 100644 --- a/PyRoute/Outputs/PDFHexMap.py +++ b/PyRoute/Outputs/PDFHexMap.py @@ -10,6 +10,7 @@ from pypdflite.pdfobjects.pdfellipse import PDFEllipse from pypdflite.pdfobjects.pdfline import PDFLine from pypdflite.pdfobjects.pdftext import PDFText +from reportlab.pdfgen.canvas import Canvas from PyRoute.Outputs.Map import Map from PyRoute.StatCalculation import StatCalculation @@ -61,17 +62,24 @@ def write_base_map(self, pdf, sector): self.subsector_grid(pdf) self.hex_grid(pdf, self._draw_all, 0.5) - def sector_name(self, doc, name): + def sector_name(self, doc: Canvas, name: str): """ Write name at the top of the document """ - cursor = PDFCursor(5, -5, True) - def_font = doc.get_font() - doc.set_font('times', size=30) - width = doc.get_font()._string_width(name) - cursor.x = 306 - (width / 2) - doc.add_text(name, cursor) - doc.set_font(font=def_font) + # cursor = PDFCursor(5, -5, True) + # Save out whatever font is currently set + font_name = doc._fontname + font_size = doc._fontsize + font_leading = doc._leading + new_font = 'Times-Roman' + new_size = 30 + doc.setFont(new_font, size=new_size) + width = doc.stringWidth(name, new_font, new_size) + x = 306 - (width / 2) + textobject = doc.beginText(x, -5) + textobject.textOut(name) + # Restore saved font + doc.setFont(font_name, font_size, font_leading) def coreward_sector(self, pdf, name): cursor = PDFCursor(5, self.y_start - 15, True) @@ -355,28 +363,32 @@ def _get_line_endpoints(self, end, start): endx = (self.xm * 3 * endCol) + self.ym return endx, endy, startx, starty - def document(self, sector, is_live=True): + def document(self, sector, is_live: bool = True): """ Generated by the type of document """ path = os.path.join(self.galaxy.output_path, sector.sector_name() + " Sector.pdf") if not is_live: path = "string" - self.writer = PDFLite(path) + self.writer = Canvas(path, pageCompression=(is_live is True)) title = "Sector %s" % sector subject = "Trade route map generated by PyRoute for Traveller" author = None keywords = None creator = "PyPDFLite" - self.writer.set_information(title, subject, author, keywords, creator) - self.writer.set_compression(is_live) - document = self.writer.get_document() - document.set_margins(4) - return document + self.writer.setTitle(title) + self.writer.setAuthor(author) + self.writer.setSubject(subject) + self.writer.setKeywords(keywords) + self.writer.setCreator(creator) + #document = self.writer.get_document() + #document.set_margins(4) + return self.writer def close(self): - self.writer.close() + self.writer.showPage() + self.writer.save() def cursor(self, x=0, y=0): return PDFCursor(x, y) From 518f9861cf7e19d56aa5c8681adb0aa41b361889 Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Thu, 28 Mar 2024 02:15:00 +1000 Subject: [PATCH 13/45] Stop PDFHexMap.write_sector_pdf_map barfing --- PyRoute/Outputs/PDFHexMap.py | 216 +++++++++++++++++++++++++---------- 1 file changed, 157 insertions(+), 59 deletions(-) diff --git a/PyRoute/Outputs/PDFHexMap.py b/PyRoute/Outputs/PDFHexMap.py index ae13f135a..64cab3f1a 100644 --- a/PyRoute/Outputs/PDFHexMap.py +++ b/PyRoute/Outputs/PDFHexMap.py @@ -17,6 +17,9 @@ class PDFHexMap(Map): + + colourmap = {'gray': (128, 128, 128), 'salmon': (255, 140, 105)} + def __init__(self, galaxy, routes, min_btn=8): super(PDFHexMap, self).__init__(galaxy, routes) self.lineStart = PDFCursor(0, 0) @@ -55,7 +58,9 @@ def write_sector_pdf_map(self, gal_sector, is_live=True): self.spinward_sector(pdf_doc, gal_sector.spinward.name) if gal_sector.trailing: self.trailing_sector(pdf_doc, gal_sector.trailing.name) - return self.writer.close() + if is_live: + return self.writer.save() + return self.writer.getpdfdata() def write_base_map(self, pdf, sector): self.sector_name(pdf, sector.name) @@ -118,101 +123,194 @@ def trailing_sector(self, pdf, name): text._text(name) pdf.set_font(font=def_font) - def subsector_grid(self, pdf): - color = pdf.get_color() - color.set_color_by_name('lightgray') - pdf.set_draw_color(color) - vlineStart = PDFCursor(0, self.y_start + self.xm) - vlineEnd = PDFCursor(0, self.y_start + self.xm + (180 * 4)) + def subsector_grid(self, pdf: Canvas): + pdf.setStrokeColorRGB(211, 211, 211) + #vlineStart = PDFCursor(0, self.y_start + self.xm) + #vlineEnd = PDFCursor(0, self.y_start + self.xm + (180 * 4)) + vlineStart = [0, self.y_start + self.xm] + vlineEnd = [0, self.y_start + self.xm + (180 * 4)] for x in range(self.x_start, 595, 144): - vlineStart.x = x - vlineEnd.x = x - pdf.add_line(cursor1=vlineStart, cursor2=vlineEnd) - - hlineStart = PDFCursor(self.x_start, 0) - hlineEnd = PDFCursor(591, 0) + vlineStart[0] = x + vlineEnd[0] = x + #pdf.add_line(cursor1=vlineStart, cursor2=vlineEnd) + pdf.line(vlineStart[0], vlineStart[1], vlineEnd[0], vlineEnd[1]) + + #hlineStart = PDFCursor(self.x_start, 0) + #hlineEnd = PDFCursor(591, 0) + hlineStart = [self.x_start, 0] + hlineEnd = [591, 0] for y in range(self.y_start + self.xm, 780, 180): - hlineStart.y = y - hlineEnd.y = y - pdf.add_line(cursor1=hlineStart, cursor2=hlineEnd) + hlineStart[1] = y + hlineEnd[1] = y + #pdf.add_line(cursor1=hlineStart, cursor2=hlineEnd) + pdf.line(hlineStart[0], hlineStart[1], hlineEnd[0], hlineEnd[1]) + + def hex_grid(self, doc, draw, width, colorname='gray'): + + hlineStart, hlineEnd, hlineStartStep, hlineEndStep, colour = self._hline(doc, width, colorname) + llineStart, llineEnd, llineStartStep, llineEndStep, colour = self._lline(doc, width, colorname) + rlineStart, rlineEnd, rlineStartStep, rlineEndStep, colour = self._rline(doc, width, colorname) + + for x in range(self.x_count): + #hlineStart.x_plus() + hlineStart[0] += hlineStartStep[0] + #hlineEnd.x_plus() + hlineEnd[0] += hlineEndStep[0] + self._hline_restart_y(x, hlineStart, hlineEnd) + self._lline_restart_y(x, llineStart, llineEnd) + self._rline_restart_y(x, rlineStart, rlineEnd) + + for y in range(self.y_count): + #hlineStart.y_plus() + hlineStart[1] += hlineStartStep[1] + #hlineEnd.y_plus() + hlineEnd[1] += hlineEndStep[1] + #llineStart.y_plus() + llineStart[1] += llineStartStep[1] + #llineEnd.y_plus() + llineEnd[1] += llineEndStep[1] + #rlineStart.y_plus() + rlineStart[1] += rlineStartStep[1] + #rlineEnd.y_plus() + rlineEnd[1] += rlineEndStep[1] + hline = (hlineStart, hlineEnd) + lline = (llineStart, llineEnd) + rline = (rlineStart, rlineEnd) + + draw(x, y, hline, lline, rline, doc, width, colour) + + #llineStart.x_plus() + llineStart[0] += llineStartStep[0] + #llineEnd.x_plus() + llineEnd[0] += llineEndStep[0] + #rlineStart.x_plus() + rlineStart[0] += rlineStartStep[0] + #rlineEnd.x_plus() + rlineEnd[0] += rlineEndStep[0] + + def _draw_all(self, x, y, hline, lline, rline, pdf: Canvas, width, colour): + if (x < self.x_count - 1): + #hline._draw() + pdf.line(hline[0][0], hline[0][1], hline[1][0], hline[1][1]) + #lline._draw() + pdf.line(lline[0][0], lline[0][1], lline[1][0], lline[1][1]) + if (y > 0): + #rline._draw() + pdf.line(rline[0][0], rline[0][1], rline[1][0], rline[1][1]) + + def _draw_borders(self, x, y, hline, lline, rline, pdf: Canvas, width, colour): + q, r = self.convert_hex_to_axial(x + self.sector.dx, y + self.sector.dy - 1) + + if self.galaxy.borders.borders.get((q, r), False): + if self.galaxy.borders.borders[(q, r)] & 1: + # hline._draw() + pdf.line(hline[0][0], hline[0][1], hline[1][0], hline[1][1]) + + if self.galaxy.borders.borders[(q, r)] & 2 and y > 0: + # rline._draw() + pdf.line(rline[0][0], rline[0][1], rline[1][0], rline[1][1]) + + if self.galaxy.borders.borders[(q, r)] & 4: + # lline._draw() + pdf.line(lline[0][0], lline[0][1], lline[1][0], lline[1][1]) def _hline(self, pdf, width, colorname): - hlineStart = PDFCursor(0, 0) - hlineStart.x = 3 - hlineStart.y = self.y_start - self.ym - hlineStart.dx = self.xm * 3 - hlineStart.dy = self.ym * 2 - - hlineEnd = PDFCursor(0, 0) - hlineEnd.x = self.xm * 2.5 - hlineEnd.y = self.y_start - self.ym - hlineEnd.dx = self.xm * 3 - hlineEnd.dy = self.ym * 2 + # dx/y are step sizen + #hlineStart = PDFCursor(0, 0) + #hlineStart.x = 3 + #hlineStart.y = self.y_start - self.ym + #hlineStart.dx = self.xm * 3 + #hlineStart.dy = self.ym * 2 - color = pdf.get_color() - color.set_color_by_name(colorname) + hlineStart = [3, self.y_start - self.ym] + hlineStartStep = (self.xm * 3, self.ym * 2) + + #hlineEnd = PDFCursor(0, 0) + #hlineEnd.x = self.xm * 2.5 + #hlineEnd.y = self.y_start - self.ym + #hlineEnd.dx = self.xm * 3 + #hlineEnd.dy = self.ym * 2 + + hlineEnd = [self.xm * 2.5, self.y_start - self.ym] + hlineEndStep = (self.xm * 3, self.ym * 2) - hline = PDFLine(pdf.session, pdf.page, hlineStart, hlineEnd, stroke='solid', color=color, size=width) + colour = self.colourmap[colorname] + #pdf.setStrokeColorRGB(colour[0], colour[1], colour[2]) - return hlineStart, hlineEnd, hline + #hline = PDFLine(pdf.session, pdf.page, hlineStart, hlineEnd, stroke='solid', color=color, size=width) + + return hlineStart, hlineEnd, hlineStartStep, hlineEndStep, colour def _hline_restart_y(self, x, hlineStart, hlineEnd): if x & 1: - hlineStart.y = self.y_start - self.ym - hlineEnd.y = self.y_start - self.ym + hlineStart[1] = self.y_start - self.ym + hlineEnd[1] = self.y_start - self.ym else: - hlineStart.y = self.y_start - 2 * self.ym - hlineEnd.y = self.y_start - 2 * self.ym + hlineStart[1] = self.y_start - 2 * self.ym + hlineEnd[1] = self.y_start - 2 * self.ym def _lline(self, pdf, width, colorname): - llineStart = PDFCursor(-10, 0) - llineStart.x = self.x_start - llineStart.dx = self.xm * 3 - llineStart.dy = self.ym * 2 + #llineStart = PDFCursor(-10, 0) + #llineStart.x = self.x_start + #llineStart.dx = self.xm * 3 + #llineStart.dy = self.ym * 2 - llineEnd = PDFCursor(-10, 0) - llineEnd.x = self.x_start + self.xm - llineEnd.dx = self.xm * 3 - llineEnd.dy = self.ym * 2 + llineStart = [self.x_start, 0] + llineStartStep = (self.xm * 3, self.ym * 2) - color = pdf.get_color() - color.set_color_by_name(colorname) + #llineEnd = PDFCursor(-10, 0) + #llineEnd.x = self.x_start + self.xm + #llineEnd.dx = self.xm * 3 + #llineEnd.dy = self.ym * 2 + llineEnd = [self.x_start + self.xm, 0] + llineEndStep = (self.xm * 3, self.ym * 2) + + #color = pdf.get_color() + #color.set_color_by_name(colorname) + colour = self.colourmap[colorname] - lline = PDFLine(pdf.session, pdf.page, llineStart, llineEnd, stroke='solid', color=color, size=width) + #lline = PDFLine(pdf.session, pdf.page, llineStart, llineEnd, stroke='solid', color=color, size=width) - return llineStart, llineEnd, lline + return llineStart, llineEnd, llineStartStep, llineEndStep, colour def _lline_restart_y(self, x, llineStart, llineEnd): if x & 1: - llineStart.y = self.y_start - 2 * self.ym - llineEnd.y = self.y_start - self.ym + llineStart[1] = self.y_start - 2 * self.ym + llineEnd[1] = self.y_start - self.ym else: - llineStart.y = self.y_start - self.ym - llineEnd.y = self.y_start - 2 * self.ym + llineStart[1] = self.y_start - self.ym + llineEnd[1] = self.y_start - 2 * self.ym def _rline(self, pdf, width, colorname): rlineStart = PDFCursor(0, 0) rlineStart.x = self.x_start + self.xm rlineStart.dx = self.xm * 3 rlineStart.dy = self.ym * 2 + + rlineStart = [self.x_start + self.xm, 0] + rlineStartStep = (self.xm * 3, self.ym * 2) + rlineEnd = PDFCursor(0, 0) rlineEnd.x = self.x_start rlineEnd.dx = self.xm * 3 rlineEnd.dy = self.ym * 2 + rlineEnd = [self.x_start, 0] + rlineEndStep = (self.xm * 3, self.ym * 2) - color = pdf.get_color() - color.set_color_by_name(colorname) - rline = PDFLine(pdf.session, pdf.page, rlineStart, rlineEnd, stroke='solid', color=color, size=width) + #color = pdf.get_color() + #color.set_color_by_name(colorname) + colour = self.colourmap[colorname] + #rline = PDFLine(pdf.session, pdf.page, rlineStart, rlineEnd, stroke='solid', color=color, size=width) - return rlineStart, rlineEnd, rline + return rlineStart, rlineEnd, rlineStartStep, rlineEndStep, colour def _rline_restart_y(self, x, rlineStart, rlineEnd): if x & 1: - rlineStart.y = self.y_start - 3 * self.ym - rlineEnd.y = self.y_start - 2 * self.ym + rlineStart[1] = self.y_start - 3 * self.ym + rlineEnd[1] = self.y_start - 2 * self.ym else: - rlineStart.y = self.y_start - 2 * self.ym - rlineEnd.y = self.y_start - 3 * self.ym + rlineStart[1] = self.y_start - 2 * self.ym + rlineEnd[1] = self.y_start - 3 * self.ym def system(self, pdf, star): def_font = pdf.get_font() From 98991bf7219b85abe47db83438caf6803d13887b Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Thu, 28 Mar 2024 03:00:24 +1000 Subject: [PATCH 14/45] Stop PDFHexMap.trade_line barfing --- PyRoute/Outputs/PDFHexMap.py | 101 +++++++++++++++++++++++------------ 1 file changed, 68 insertions(+), 33 deletions(-) diff --git a/PyRoute/Outputs/PDFHexMap.py b/PyRoute/Outputs/PDFHexMap.py index 64cab3f1a..4104ef712 100644 --- a/PyRoute/Outputs/PDFHexMap.py +++ b/PyRoute/Outputs/PDFHexMap.py @@ -313,8 +313,14 @@ def _rline_restart_y(self, x, rlineStart, rlineEnd): rlineEnd[1] = self.y_start - 3 * self.ym def system(self, pdf, star): - def_font = pdf.get_font() - pdf.set_font('times', size=4) + #def_font = pdf.get_font() + font_name = pdf._fontname + font_size = pdf._fontsize + font_leading = pdf._leading + + new_font = 'Times-Roman' + new_size = 4 + pdf.setFont(new_font, size=new_size) col = (self.xm * 3 * star.col) if star.col & 1: @@ -324,21 +330,32 @@ def system(self, pdf, star): point = PDFCursor(col, row) self.zone(pdf, star, point.copy()) - - width = self.string_width(pdf.get_font(), str(star.uwp)) - point.y_plus(7) - point.x_plus(self.ym - (width // 2)) - pdf.add_text(str(star.uwp), point) + rawpoint = [col, row] + + #width = self.string_width(pdf.get_font(), str(star.uwp)) + width = pdf.stringWidth(str(star.uwp), new_font, new_size) + ##point.y_plus(7) + #point.x_plus(self.ym - (width // 2)) + rawpoint[0] += self.ym - (width // 2) + rawpoint[1] += 7 + textobject = pdf.beginText(rawpoint[0], rawpoint[1]) + textobject.textOut(str(star.uwp)) + #pdf.add_text(str(star.uwp), point) if len(star.name) > 0: for chars in range(len(star.name), 0, -1): - width = self.string_width(pdf.get_font(), star.name[:chars]) + #width = self.string_width(pdf.get_font(), star.name[:chars]) + width = pdf.stringWidth(star.name[:chars], new_font, new_size) if width <= self.xm * 3.5: break - point.y_plus(3.5) - point.x = col - point.x_plus(self.ym - (width // 2)) - pdf.add_text(star.name[:chars], point) + #point.y_plus(3.5) + #point.x = col + #point.x_plus(self.ym - (width // 2)) + rawpoint[0] = col + self.ym - (width // 2) + rawpoint[1] += 3.5 + textobject = pdf.beginText(rawpoint[0], rawpoint[1]) + textobject.textOut(star.name[:chars]) + #pdf.add_text(star.name[:chars], point) added = star.alg_code if star.tradeCode.subsector_capital: @@ -349,11 +366,17 @@ def system(self, pdf, star): added += ' ' added += '{:d}'.format(star.ggCount) - point.y_plus(3.5) - point.x = col - width = pdf.get_font()._string_width(added) - point.x_plus(self.ym - (width // 2)) - pdf.add_text(added, point) + #point.y_plus(3.5) + #point.x = col + rawpoint[0] = col + rawpoint[1] += 3.5 + #width = pdf.get_font()._string_width(added) + width = pdf.stringWidth(added) + # point.x_plus(self.ym - (width // 2)) + rawpoint[0] += self.ym - (width // 2) + #pdf.add_text(added, point) + textobject = pdf.beginText(rawpoint[0], rawpoint[1]) + textobject.textOut(added) added = '' tradeIn = StatCalculation.trade_to_btn(star.tradeIn) @@ -365,13 +388,20 @@ def system(self, pdf, star): added += "{}{} {}".format(star.baseCode, star.ggCount, star.importance) elif self.routes == 'xroute': added += " {}".format(star.importance) - width = pdf.get_font()._string_width(added) - point.y_plus(3.5) - point.x = col - point.x_plus(self.ym - (width // 2)) - pdf.add_text(added, point) - - pdf.set_font(def_font) + #width = pdf.get_font()._string_width(added) + width = pdf.stringWidth(added) + #point.y_plus(3.5) + #point.x = col + #point.x_plus(self.ym - (width // 2)) + rawpoint[0] = col + self.ym - (width // 2) + rawpoint[1] += 3.5 + #pdf.add_text(added, point) + textobject = pdf.beginText(rawpoint[0], rawpoint[1]) + textobject.textOut(added) + + #pdf.set_font(def_font) + # Restore saved font + pdf.setFont(font_name, font_size, font_leading) def trade_line(self, pdf, edge, data): @@ -395,8 +425,9 @@ def trade_line(self, pdf, edge, data): trade = 6 tradeColor = tradeColors[trade] - color = pdf.get_color() - color.set_color_by_number(tradeColor[0], tradeColor[1], tradeColor[2]) + #color = pdf.get_color() + #color.set_color_by_number(tradeColor[0], tradeColor[1], tradeColor[2]) + pdf.setStrokeColorRGB(tradeColor[0], tradeColor[1], tradeColor[2]) endCircle = end.sector == start.sector endx, endy, startx, starty = self._get_line_endpoints(end, start) @@ -404,16 +435,20 @@ def trade_line(self, pdf, edge, data): lineStart = PDFCursor(startx, starty) lineEnd = PDFCursor(endx, endy) - line = PDFLine(pdf.session, pdf.page, lineStart, lineEnd, stroke='solid', color=color, size=1) - line._draw() + pdf.line(startx, starty, endx, endy) - radius = PDFCursor(2, 2) - circle = PDFEllipse(pdf.session, pdf.page, lineStart, radius, color, size=3) - circle._draw() + #line = PDFLine(pdf.session, pdf.page, lineStart, lineEnd, stroke='solid', color=color, size=1) + #line._draw() + + #radius = PDFCursor(2, 2) + #circle = PDFEllipse(pdf.session, pdf.page, lineStart, radius, color, size=3) + #circle._draw() + pdf.ellipse(startx - 2, starty - 2, startx + 2, starty + 2) if endCircle: - circle = PDFEllipse(pdf.session, pdf.page, lineEnd, radius, color, size=3) - circle._draw() + #circle = PDFEllipse(pdf.session, pdf.page, lineEnd, radius, color, size=3) + #circle._draw() + pdf.ellipse(endx - 2, endy - 2, endx + 2, endy + 2) def comm_line(self, pdf, edge): start = edge[0] From 3da484ddf17a63ccc840edcb400951e8689bc71b Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Thu, 28 Mar 2024 03:48:59 +1000 Subject: [PATCH 15/45] Update PDF version to match previous --- PyRoute/Outputs/PDFHexMap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PyRoute/Outputs/PDFHexMap.py b/PyRoute/Outputs/PDFHexMap.py index 4104ef712..f6363f88e 100644 --- a/PyRoute/Outputs/PDFHexMap.py +++ b/PyRoute/Outputs/PDFHexMap.py @@ -503,7 +503,7 @@ def document(self, sector, is_live: bool = True): path = os.path.join(self.galaxy.output_path, sector.sector_name() + " Sector.pdf") if not is_live: path = "string" - self.writer = Canvas(path, pageCompression=(is_live is True)) + self.writer = Canvas(path, pageCompression=(is_live is True), pdfVersion=(1,7)) title = "Sector %s" % sector subject = "Trade route map generated by PyRoute for Traveller" From 6ee8d5d751e67ffec780e91faa4e1ddfe2e7afca Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Thu, 28 Mar 2024 17:43:59 +1000 Subject: [PATCH 16/45] Clean up trailing zeros in expected result --- Tests/Outputs/testHexMap.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Tests/Outputs/testHexMap.py b/Tests/Outputs/testHexMap.py index fd8194a84..0c40c4a73 100644 --- a/Tests/Outputs/testHexMap.py +++ b/Tests/Outputs/testHexMap.py @@ -143,6 +143,7 @@ def test_verify_empty_sector_write(self): matches = self.md5line.findall(result) self.assertEqual(2, len(matches), 'Should be exactly two MD5 matches') result = self.md5line.sub(oldmd5, result) + self.assertEqual(expected_result, result) def test_verify_empty_sector_write_pdf(self): @@ -187,7 +188,16 @@ def test_verify_empty_sector_write_pdf(self): matches = self.md5line.findall(result) self.assertEqual(2, len(matches), 'Should be exactly two MD5 matches') result = self.md5line.sub(oldmd5, result) - self.assertEqual(expected_result, result) + # Clean up double-trailing-zeros in expected_result + expected_result = expected_result.replace(b".00 ", b" ") + for i in range(0, 10): + old = "." + str(i) + "0 " + new = "." + str(i) + " " + enc_old = old.encode('utf-8') + enc_new = new.encode('utf-8') + expected_result = expected_result.replace(enc_old, enc_new) + + self.assertEqual(str(expected_result), str(result)) @pytest.mark.xfail(reason='Flaky on ubuntu') def test_verify_subsector_trade_write(self): From 2c109c09c4d19651248490a259e872b16ac74aa6 Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Fri, 28 Jun 2024 15:32:00 +1000 Subject: [PATCH 17/45] Square up PDF version of document_object test --- Tests/Outputs/testHexMap.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Tests/Outputs/testHexMap.py b/Tests/Outputs/testHexMap.py index 0c40c4a73..60794249c 100644 --- a/Tests/Outputs/testHexMap.py +++ b/Tests/Outputs/testHexMap.py @@ -90,16 +90,16 @@ def test_document_object_pdf(self): for msg, is_live, expected_path in blurb: with self.subTest(msg): document = hexmap.document(galaxy.sectors[secname], is_live=is_live) - self.assertEqual(4, document.margins.left, 'Unexpected margins value') + # self.assertEqual(4, document.margins.left, 'Unexpected margins value') # check writer properties - if is_live: - self.assertTrue(hexmap.compression, 'PDF writer compression not set') - else: - self.assertFalse(hexmap.compression, 'PDF writer compression set') - self.assertEqual('Sector Zao Kfeng Ig Grilokh (-2,4)', hexmap.writer.title) - self.assertEqual('Trade route map generated by PyRoute for Traveller', hexmap.writer.subject) - self.assertEqual('PyPDFLite', hexmap.writer.creator) - self.assertEqual(expected_path, hexmap.writer.filepath) + #if is_live: + # self.assertTrue(hexmap.compression, 'PDF writer compression not set') + #else: + # self.assertFalse(hexmap.compression, 'PDF writer compression set') + self.assertEqual('Sector Zao Kfeng Ig Grilokh (-2,4)', document._doc.info.title) + self.assertEqual('Trade route map generated by PyRoute for Traveller', document._doc.info.subject) + self.assertEqual('PyPDFLite', document._doc.info.creator) + self.assertEqual(expected_path, document._filename) def test_verify_empty_sector_write(self): sourcefile = self.unpack_filename('DeltaFiles/no_subsectors_named/Zao Kfeng Ig Grilokh empty.sec') From bc660e23bb32320792f8576ef6588adcff506484 Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Fri, 28 Jun 2024 17:21:32 +1000 Subject: [PATCH 18/45] Add status-quo map writes --- .../Zao Kfeng Ig Grilokh empty.pdf | Bin 0 -> 22340 bytes ...ao Kfeng Ig Grilokh - subsector P - comm.pdf | Bin 0 -> 24101 bytes ...o Kfeng Ig Grilokh - subsector P - trade.pdf | Bin 0 -> 24761 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 Tests/OutputFiles/verify_empty_sector_write/Zao Kfeng Ig Grilokh empty.pdf create mode 100644 Tests/OutputFiles/verify_subsector_comm_write/Zao Kfeng Ig Grilokh - subsector P - comm.pdf create mode 100644 Tests/OutputFiles/verify_subsector_trade_write/Zao Kfeng Ig Grilokh - subsector P - trade.pdf diff --git a/Tests/OutputFiles/verify_empty_sector_write/Zao Kfeng Ig Grilokh empty.pdf b/Tests/OutputFiles/verify_empty_sector_write/Zao Kfeng Ig Grilokh empty.pdf new file mode 100644 index 0000000000000000000000000000000000000000..94d8dd43b421f46b295141d6af455e798c47f1a0 GIT binary patch literal 22340 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 literal 0 HcmV?d00001 diff --git a/Tests/OutputFiles/verify_subsector_comm_write/Zao Kfeng Ig Grilokh - subsector P - comm.pdf b/Tests/OutputFiles/verify_subsector_comm_write/Zao Kfeng Ig Grilokh - subsector P - comm.pdf new file mode 100644 index 0000000000000000000000000000000000000000..dc4b7f8711b2ef5d7a490f1c81a8f7d6a5b4088d GIT binary patch literal 24101 zcmeHvd0bQ1*7j}fq^(73)mp1brB1ELBp?EbGmdqhihu|Kks+2rgb>oIbylES1{JU> z(mEkxMTR8TqJn}@K}7|Us|=9>Ng+T8A<4V;KIbGS*zw-`e!uto-RmFf%Gtx(>+JoU zXAS3!T{?TNh4o~I0b>i#6`n7=PhzcytzK0)gn%? zU)Uz5jg7sd0~7j%hJ|pvf(C3mUR$#`)2AvvS6q1c(Ovr!o(i{`tcaGOXC100mIsdhDEyD#y3JfpiCmoQ>fcQf=4qFTkGRK~?nZG%8tWX90l6==Kac8m3u z5dyWYP|&R_R0w*~1iJ1zGksT~gTA>igS#$98@-; zSXZeRv{q;66=|$1iLss@T6G~qH?DVe1vm3q_2v1_9xI}ymV1i=_A$8UZq(UD=+{s4 zSkXJxZgl2Ekqi1;w{}5iL-_Gw?lJsHv1UqrskCuPrgXgX#_F8zlt*V0D>H{T>B=|z z`>Pwpi;EI-R{7>6PSv!;gRf~_F?@T*uzizAl%qAPRhbKDJTT2%|8*As7 zCaZa+l5_R^mrc!Ik82%k@0eDxvjWSrf~`C@9-b9Eh`8)N*&f8jlZO%F?)5EdhjOc+ z%Xe;W%qvZ>vl2ePIoZ;(Yg^_1^sr=$fTca#?%ce!Ffimcd7V#)RC9hYx_Y^U>U|rD z?Q#2WqRtzlzS(~;@sUqFAuf#)90vKmkHF9yH}5-kF*R7pO}RjMYss;|kgTN1*_?rk z*!3Ih#@erI*l6wUTk?LAN5{s*#NqkErkl%=?h}MA4d{AgpWzMSU(vOvZQSsYmfzp5 zGngu4ePF?jaf$0Eohb=i*RL{TeM=peWf5?A7G=v9OpDr|quTHHXld`+I9#}PLE_`f zuccQxUts`-XQMAVm@{C7#7WP*DgZ6&#ZyYdl#>!#Non64GlL%P1l!S zJCI;8L3@O!9qBpg3x}!`=gU%tZ}3faI-bei^8KKK*Ipiap^EF?nbO>x^HPtfv$J}& z_BcoKa%Q);amOE83>;o`>ClyXJ1?!c5i#ZZm0*A>w z+}@vj$P27a@4dvdx5A`%d`$7pF`&w!aY;xO*Qk5@$HG{n7F(gQf}6g)a-lyA#^wHq zRe3AFe&b-LInk~|7BwgA+HVd!+NAg4{d0`H4|RMzeE*|~&zz2K_$+72`kIP<{%%9i zLL@t}9p39YV%mHnz4-@C?vsaQ(ToE3lWqmesH~kPR_I!rk$C^3m%3ZS(QF?SPBv}N zFkQFAbRoh!rVIH4GdMMvm_b-#(!1wF(}k>>JnRRo?4IzRBa6z16($);_15 zckSMA@slqW=4VR+Cde7@%HH0YYO8AWd+L-g-1SU1ceDJ9=Z>FqMQaMOtD%9}`~0J# z=%fU{rj%NbS#5&_TF2MYxRoX4r~MTC%xK}KIhGM84?f;`@tM%u{!IAQFQ3l?c^s0_ zmbvQHoG^MeY{0yIDzpI~@48@KyVw5cAdt~6Kr+90;24^SmPZq{ar@_l4OO5$l0DE3 zZyBSQ)4_c5@RfV33UVBtUMaH=9M!a9@-Pt5-o_#z09^#cpo?HCqgY$9W0TvE*=yP) zuw$lGUkX2MnY$K=aFDPFTbnLMn2P9*8y7tfZ5X2nHsFaCTV~x}F*)H^$A;}j9~;gf zU0h=#E;KvoB0U4UPpyhd!4}lQ);1c&wZ~M9N;JI+`{nt^5_C@dMqWMCadP-VqZr&Y z1J2;dbKB4vObL24tpd(BhzK39xZ6}bW-0=t(i`vhn3UW6mgv+QyTY$5Yfgx{BYa0# z_K)g-4XYbP!3wfQxeWnTE*Cqchs4 z*s*o8w`c#frgw4Udo}a=`LDd8N@Eo})U_PCVtouDS?QUurXWDFQU0g;dm$*B*>u?y z`jRGNu4(!4`9!+Q=BLCb(Y(#AuphnSfFF+pxy~y9>=u2Q3~1V8C=tW- zLK2XO>!&F|0`34f9B)9B1*o+vJkDu@)4&g?2Q;^Cq0sK%qblE{a^Ys1xYeS^=n1mR zokBZer3RcP%{b1~Ot4AlW->r4?(7!D3<}r|I9`2h1G4(mT`8u68_9VgXIM=e8=P{!8%*#9}IyE4@WXq1Kcu8mAJ&&*Wu0t0jKAoFsuKg){PV`XK zs8ti7yXa8I^0I`ERsV=s`EA&U;NHA+B|EBGG*G>&K4;B!_~ma-6&a}(J?DNR6nJCn zX)_nMSJq|`lYQ7Sj~jJ$!lmtEJel{Sc#qD&_ziOec(T{B+@|SEaxM&b|NJl9%$-dB%cHTbx3R9w)o*RX zx=zSXSBeGsA5;dMZ$FZK6Lg*ARFRl!vHbjmU$C(@U|p}Q_4)@k%ss5@o~&oYWQC8B zuFJe%-Hs=_l?}R@Un{W|p80skqK5p|E&ZOv$Nc>GWYK_EV?TzQad4As&;9H;wkh9kNemNgd2l_`J>T}g5$3lk?o+>%mHngYY%M!wUjH#q zM(k3CmZx0LT$iu^>EZZuk(cg-J}4NOv@mW}WEUCc0|IAsdK8PtVQ7L=h&f~ML zf7i36tSGvb8=Wi5|GA5G`qaJ~k6%}Yd+sQWXIvS{kDIq@pDUxRihpp5DzY|$#|T;8 z@T_HrIxq8SV=B9YHF)8J1KDPc&7xb!c`=o|xz?+uma&pDHSceet5+#TJYLy3RkJ}P z59n}gFN{8A_T>9_+MgFf?+>4|6cxO=(^t6?&3DVyD-1%J}#bKSpU4f8at@?{aza(p5o3>%eL4vv!^`yL0wsB_8~xpYh-xt2o5q z>2mcf#fXEez9*W0mnU-&`t4lZ*@y>k`#IIKGj*BU&=kCo*Djc|znj{_%suNI%Q_A# z$(1WpqspIOyi<-1Q&{C9_}Rr=ban>T<)D|U_oC{+%jX8A@U1*mc5$^zGohc)+6T|^ z;P+ZmJ@2?2zWMQdLUVuDnCT%MGvZbyj^7HWR?>{4Rz zEvnu#sspTZi-^HXYnA6Dwqq`Q7T$F|y0&Uio?^&l8##n6r}{dze*cKO;2n zlq)|K`b}|5Z^pWOt4Ni0rVb8%kV)JzF;Dlc>Oj=FJ;dN|NYLQ#UtF*i4?b^Oy|m+a z|Ine?t|JdraSncER&&^9XWq+C4lk0pb^Nd^Ha4qbr_Y*|yq?k+Zb{WgEXk$$L+Wdj zuVzGKsIEr0KC8R8GWA9#<3^`rG+du5x@!5+0!4F2PpEoCE_;>0R^8S4WrZ~(eqEfC zEL2n)@diWvWyQVMk*wJQ@+N8f-iixm=GjcubLAM%MyD&Wz0;eX?oveb$(YBYYR2e@f-p(F-@5+_HHi*nT2FzRL`P%i?m6# zJyy*FLStEAbojxVdnv4A#>1hFXziM5<6?VZ@16go6m7+>@I|B5us_zVNkUtgMmW*GR!zzX0PnGY-Ka}Qoq$Y=dm78rQD-j*Kjm-|Tp}W8zb=Wng ziswt83-%&?@P)a4KJH%fmO}OAOz-xDB?-a&BpA#!Z8-iRDWb{6mNpu)JAJe>s#3b< zu$7I27c?A(bKwi4qjQ0l(&*Cp?^Kl~IaBq>Z=<559xdEk0tbbzOrMn=GcjZR8lfLj zja+y!pkKVvh;FQPd7WOc(9wJka&S0y^ugWfD|K!Mpr(%oC#8k91N3KP2d;in?DAww z!KCH`1$C)MW=K*F^1i;!+y6dO!SGJ+-!2cVsr2jOxq-}mbh^J%?o(5lUy0?|GwmP6 zXqgwwpCXZ~zw}OeWv}jJY`N-Xy^24bc*69R(poT z7GO34a371iO+~l|^sBJ5jUo+3cVop3;6$vWIE{oKQdF3V?2gnCut@YgLRbWkN^|3) z+*r$c^3GJ%$lxxjr%Au%*INqx5MsmA42y98=psDS=_2eEnpiS^foE06`TI7 z(4v=^T(9pVMwniuFqT{fY`nony)Duv09b(H=&_6JZNNRk{07`3?*@f?q)6eO@m^uv zyS{Cd3GPAQ^k7xQgs;aT7+f+SoO|_pk1l@RQKnt_9T&cYz`>`P9gusN7%G5QjXInYeL1YT5RQyX z&lZs!9sYq7F2c$I2IGPA5x51M5BiHKq`}=?5r$)=0SY*699QiF4+Ov#UdL0QE7p-l z)FXPMhmCP^BkJMavAq-)0D(l1N}(QNpLEni^mNqWqJo9w=_Z@QF2)Fqn38p5J?svA z*oof7aM%$ZW7cH?_dxjo-6LT;(3`_HrZtZT9!0kTI&Q{g#{xMWfe{sJf)G`D2%fp~ z6A~2SEKuEu&xF+s_)M;w!e`)<=*dFi^Pe;W&=}+7&h7c8W`K=MUkEVY#~4BJ4RO;g zE*}xJCXRRI`Zo86BO;4jChjP38mao|uxgmybP4;`0$_NLuVs|CsIE?#E|Fr!2Qp%O zATt&>%?Wo%;NG7bn_{Nq*c3jKV^g?Ctg8O?1+E0mC7>(SK=ANf zJl%bgb&)2cm?^mo>QO+BofuM%A5w(g_@RYv$0-88T?Bvz7{AG}DZWRJ4f`6iMRIJ4 zLX*pY;~fT!F8l49&D;DCFSRpA$aZIGUa7qY`veJe6YpPm{91F7>lpv-_nkjcbwoBl z4BXKPsX*5L+EZ)OZpaUQHRZz?!FdAst|Q<}J1b6(*7R&>N{U5fK%!(@DwR~AJn076KIWg2tKFe+~Om=xyIJ9Mwl)rPUM3!k%=a=!#OeSPQf(L zOQiZ)y6U5iFb%w21Ii$Wmp;1zr^)WHNhn%{R&GY)QC{uH;YE)a%w-22c4e)La?8M! z&*3mJ`ef7gKmrU@7z_hp1Or<|hN0jOh&3_{Y(WE0FRYI@<-T6+ECp<6qow}Pu0OkE z({wf9-_vdD9=@mkI}QK%1MBO&&riV|kZ^gYW0$wF_BH{4fOw`Z?{wWJLEby*s+^`y zS-WkB;A{-qhSOmiqEi6du+eE}Uze7~%}5_A<^=d1G@ncNlg5W8B2R%Pf=6%S0#gY1 zTMl&}zqC(p>cEoTHtd?e;_Xq$avb}De^pUUb;nVSb7!h2;iifyG?rnn)j%8xQEGY> zo@&Hn0bd1rCKNBu%qrh0NxYqiz*}#sngIjn_O$aVA~niN10gc$+}ko^c~IXuZiD)2^u1c-Z@ zj0R^8gXy2KY5fa>Q&9?%wq}^JJ@8`aEVi#zDDWd}+IuY3_Q^%2YSWIkyz{p0hj`0h zi;S?B&mX6-*!)F-;J`QIiu1ESUy}A%5#;|xf!OOXAuB$`GW+PA!`+=buCaDw(S`p` zdRkFOk8LDL{TLHQDUr_<%0j;0FYzGr2mOu@WyC#9uv`teM~DVEA+J)%g*{x%z%lb9 zXrwWaSV(w|6!Y8hPdSn~EQ0jNu+u80um`QHE1peZ4L>iqMj(P?5fW zDuMv?!x$7`wNtba6Kz&I#ZXDnB!a*~ejP#lZXx1lp}!w6cXR4Dyd&4cl7k9Fv*M@B zK;rW6c-aFVs$yqXyz)IjWRvot5Qw10Ou!$O@0!KhTRZYi|LwiHVFJF(E^Y}p9g94Y zG<@uIOai9^9`fMxCh8Jyn@iW_sfb8klBMP|@KON=DKGVLl}%)6^Q2l0=@cjOEe_+Y zN1T7u9%DFpV!M7Y@y}WUk_#GMU+7B2&k%Jhp`Ul6Hx-kz^2EI<-nRkhO|mg2wF4q~ zoOWY`tFc6saN8{gf{;Xn3!;uNx00e#WOIu4y^g1jhYdoysi?QP*Z^y2SvkEMnx0d> zKAPJY=);p4W0IxV3Wk^@wi?|#$BG-hbEJ3zW6~%hCW#`N7AD>~Zf*okWOAB!j+brp z&XEamTnMu?tjUXtrx?H=H-|GvU$)UZCvE^db1$O6ExrMgc5YPJrhM>W2Hq;U{IJ+b zK7R!AamObY{P11BcAfR=qfABFEf>D8_=j|mcP%Dlp_35Hn?7Fhtjg?J&j+F((qC%7 z;F9wC0&Z-0ls32b0O@!{xi+!V;?~9EEQiAzgikxucMghT+DK3Ev4=JrWSod_Ae~-X z1LXAXDcwiJ`)6RMcX0Ah96FBPft}vDKC6kC{6OsVCZpL<4#k0RdLgG0PsHTW_3t5O zf1XI8jJ%>inK8IbHkT^lsE)}VgOem&a8$vWM5_I!s&MY<%>h?w&C4O$M^_xBwroBj zQVp-Q@*sP#$fya)gwKQT4?c|=u0s^fWMPo3GB#I0z}-Y~02QMqtoDBCr_4YmQb%Z= zfT_mo*!TZGp{WQtH1(w?Yu4?q;}lqHih=X(Jvt}Xb;PI5Emhd>9b~s3$NRbh&NCQ$ zPxoJzJpuW>l9sBq?3nK6>_;y9I_I}Y*3KL9Ow~GAz!#cHA;z9oQeLY4zK7G^+Pov( zn{^qI5oNOICIxu9JHR$b{>|jXNI1N4#=yEc-Sb7+37Ut7G9ff?GJKK5)RBe&mI0)Q zbOj0t8=OmsC>l~8Z2sl6j4}F2X!a{P9}3*bmHvJP2~JBsZ3l>(98a|l2#K}- zz_MhTx#HS*PF#dvU8+yJBUvj+0C}Xn|BnSuBUByo4lJMg-g&Pl zdrxy{HxjFtWX^7?JL(}AXIZRGO;pI#{b2v0%xhT@M85`6XOqe|yqH4hlM5-Pf__8< ztN1VG5FjT66bq%>afFfbcuAtzhc{C08F`g*&we9s(uef`#L!6)74sum4@605`3S`1 zP4hjhi%oW_$e&$8iC`r}vyBXv0WfVMQrT$CZy6{?Vr-1E(M5K`Tdci*Z`V5DJUNxx zCY$#Wfr1!&Gt_;^8$8%?1eV~+kVpA_`ui6sDtfM;vMs-p5^m*DLV>je%F4jRA2&q^(tFkPZT3&RAqB{{a3kCWxLi{ za4D?R6%jXeQl=K2enScuyR)?8Po^h~xJTwEsE8kSkc_xTdczd%Vb6_*d#J$WSEBs? znitu}(WByhWOjoJ!I9Ywu7MEYqz!lmBq2hMI4w^KB`64>sc;|RvLY9T*4!NB776x+ z@+__>#6)*kp{xQwOblMXXA#P?ErdMVqhHJUfCoey&G0PwHJgq`H5;ablWBA+q+le| zI8|aK(>P<)$11=oTWC2SLxOih!Q?-e;Kd2wUr+F|YloyuuQql1wQnagZA&53hEpBL zp?cakfeXpaKEPzCZ80DdQQJa_RA8PI```gp+k&y?MMQ;1E>4?}2nucjU4-327r{(4 z@r6vZA;|x4aU*krh|-pSIdO`d=W6@^IMmC5D4%t-XBCbyhYHT>e1Y(#q-&y4fynXl z$W|^ml_`)#BML?$Yr)$pLB%eJfim7ODNo%iq`=Zh-5;B5c?Pftl*AIlL#vq9NwbvbpiFG zRHxrUxCUi?pWS~cQvcp3i!t|OWlNt_yc8)Y9Je?rVtieyBt?SdDc_85YkvK6{@ZZ* zZ)%A|`_>T&g9}gH2ETgUO05l)^&P65d>^ADRu-^>LxD;|0$T3QOr8F3=mgBj{l2vI6+gT-Fy= zwKQ!Gz)EY5>nmxB!`?RWV4-OBcU|*z(}n$Ot~qRGx`+FJMpIO?dOLXzBzlWVwt3@3 z?-1~CabaJkFlKuEln*x#Ye;?fXYAmz-kCq-r0+5!-j|sb(`!=McSE6pfS=HeJJPD( zo1tP^gC3=VDkE6#>7z8M{-@0=6|^sN0RxU zR^b8#FOpH?7W_BK(S<@d^fJCbOmajn%FHbXC-he<24Ppz=x6aCkyBPI+IX)(X_Yl9 z5d@f{_wFMqOs}#{omE2_4raD5GIT1lYar-E?v@mPFLZztQaH1_qivc=X4lHycVwnw zDOtV@pa<0~T5~ez(NjQF`4xJm-IOmozhtB8*36?!u_rEW>B_(J)}&G*!eWTsmPxqKyBfAC)|K%}KOe!ULUnBu@5J)AhY5e+2T z=tYw$4seUy64pj&tI*;DPAY|BnI0CiBL*@omfRwL0 zHB)H;)wEQMju4G69iXc@=$#OvWDvM<^nO|bRu(Rrqd^Zz3<$fiDk#x}qi}_hDcpN3 zRHlzZc32N7=8hnvtN}5#;RS{&^)UL&5qKs8>=82=(1Z{THNsd-K>2Q5(497%n3@V6 z8caY0;YbruXhN=;su4bp^l(GDaU!VcQI8kNrU00jnnFEt&2RweYlf%@U8LfB*dn3^ zL%pDE4skOm)FW>ORnvzx!S%nm;;#%_#M4v{Fu}cKzlO4DA5^tHY;n_lUv&T#?M*23 zPj01WcHOc^nPhe^Jbt@RW_M<9%p^cP@9ul@Q6ttSYgTlYpnbPcrM{7Ohj@ttD$f*< zxE<8|v7z*g2>7icUkKQBTT@U58!zjk4Uzh=ibSckVi*9w^#LV*;FXyh`=dI!1Lw~X z?|9f5>SdB~E)Cbo0U`5o=Me8B!2EC<;={ioUg|)VY*kRSw~BaU1XZ$u=W&@fH&|z{ zu{BwW{W1B*Nnt%7UX7s5E>ZUB4i82X)#0Tyqv~X=lfHC7W%}%}M@+hdk7ewxg4$~+ zVNS-cvyfGH$k3#n;!$=t4s<8pI6+l+kgKCFwNH0Ab#yzyHZidiBm#fe%%7Temzs7* z{-CitF2u|;ni;*tc6;m4T-T9@5;+Is(OYac@;4%P*R8`Y4!^~=ZOsAlEw(=_>5I9t zWcc2SYCBY35T|jml$I-BUr-c{0_4YIp_t^uXfmB^cuV1hfO((77ZhuulP6dlUHl_1 z1?BosK!(coafx%E$bO$F;U8rU$VxV{NCsS8dcp$yNxDdZ3Ryx!!7LeJG=Lvfiz0Nd z43I1D^BN%q1Wq~yeq?S0wl+O;@_Y3nD7cvu5Ot9M&X)}uE`D(jb~e3tAMr1yVN74U z=^O0qQ@%shI}@Q$R9}I?kQ9O0_@#d1)V!hKP=v1(UO4eZudKO_fG@xN6K}9}!W(QZ zW=UWEH@RWz_5zsDsL=m*a)a@onA}Jc!P{#=|9yMyFS?ICPyU^6uT}rYy}g!+DwwzN zJZvDgM`kt*j_!X;S^nRavPd5ir7ZB)xJfNH`S!CR<3POqOvMj~x1ayNeQkI672V2) z&6$fKSKBc7U_-M*-1?=CGV_;9s<_NtW?NGEcb|BQzm#kjIWpo`i(>j^z5015>*Tp; z?Q09(yweyxd!@Q(pwBR$yXrkB+dD^YnzZSta3_;-DliA9{a{b z80O0MBaVz^%U~GyS4V}i#(WFIoE$I)hGD}n?+$(U1LcUedeC7-oTbeC&h0AhiS!fh zWv)JlVVHR{t{sovG!BN@w|%oCWAGvvrvIzALs%yhV3?l%Q3urrR)P+D#_#z?IXDG| zS^DYHk7ed#U>N2x=87`ccfW>VY-gN09$Rbk;VP`5_q|w#P-e-C zPhux8Nt@(R#wwAGQLdfL4!>(w+n#zy8*j11M>+kWtgXAXcS?iSVM9-v{ud4FS>kie z9%Y5!Y@UlY`Ek@z-Iy&R4}mb|iLAS`I%7@jRrj~F4G;9&%318MZmp5lJNZ5CJrXpv zLwDwBYvme&y6gwxSI--soYS|R>t0o~#!Ou{tMTESo)w>Xax2F)ho%XxcWs_zBkCC^ zbv(Rja=T9M^VwN`YP%5?%cS{SarlN&N9zS!aLII zx(5#GvJvs2@hX*|a*S+NBP%Rk_h6Pt8JGKu(^PFrSCx2&-YiY7mx_1P72UPf#lFwh zYldgFwI5AjHL}MQtynGhbi3|JgHi@ zEK47vZ!2dDRCQ+37=4?J*KGHo_s33&bYDzc6fam+UAOjPb93g~%7eV#ps&?At+DLh z-b961sN&@;&gVyj=D1$$>Nt0E$IsVpS$5>}*Bz>BJtw=Xs5#Rh(#kE_`tBX-TV1-X zE(vN?>r>J9+H-j>`emaX5;P6h!yAJ=(=-}}O14HEF8=Iq>h7Dm$_RE$Z(ADIV(Vnj zcHTpMOiTASB9VsoP#3gRW5E;bInJ$G(>46fi1);g6l`5vok|rWe!r{hhcdSSmjp|7 z?Ph(RZ*7{os6`uly*m66+qbp9NY2x(=&dYD)2r(u*jgV^Hvf>D3L-VCSZkJNu)4gh zRlc?8N{vS4EKSqO%hyGF%e!``i8X3ZwyvYHw)IfBE~T1lkt4Vr?5Y_a*=#QOR?2$V zsgBN*=6dfHza1y8VT;(kY!`;UU8sK8)fVI4Fgd8Q&EapNXyI7KEkVTS*k-NcMzJzc zD$j`?qc1F%YCBIwb`|-yN`q^A`wibO)bEn&v$kFlh({_)>(tj$WXhhO9UR2AdTX6L zR-moal>flcZ;aW@7|4J43FD4J@T_#%=-8J6J%6blHa5dytUy&Dl639Xm`6C5^_;11 zR>Xv{FKc{bE!D}j4#y<1EfEg+?bCH7S6elCk9g5H#E;6;0@z~qycn>ISWwnXXuy6(mN z=&ZcZt&m70hh)KNacu|HTYFXPXiX+V9LoN|JF8jUu-{!Xy!SrqN&d5@yTw&Sn!4Ve zyKCfoABaAWvTcumeWxuFsJk=_mgKY6wmD`xpicWYtAPi2hS zC0Exfx=*^+JL*KwhdVFYD6}(EIuu2U#CjnwspOUZ3dPrKU2jz^TiRRersKXHx=#E& zf;;oOtF_@RTu;6HTqZZjLAXj+q=<=U(M8040aZyO6TZ6Pld*RqbFF|mu z@-Bbte(v!JPt6oOzNXGBVvT0Zf;|t$EqK(sdCiu}4tsUhq%@W|_gulV`A5VxQUNkS zfCKMV^-^_HYs?8&k@&c<{3CwO-uW?_lHg-f3(<JBu$@*S( zM@^=zL4V)<`d97hjx=e6My+84dt}5+7v@SkUh3=$m#gztot>S!ri>Jx+1Bu$(P>Kr zQ38FZ&g<`5t4;Z>?QSyNI+jK}R@dUCV_X<=qaA(dnEYlj`N?DSY2_C_yc{~fa-qLZ zDANsnmU%hT9epu59DRp*faPM2kH6PU?lz{I6> zlgk4vXL^Nl(Epg0^EiQ9Ibr_ZUIQ#wgl`PP-+V@2eugRL1jEOc{e%6OmTUZjX9S1( zQ$NtgxBsK@SNaEWLM@hagS>+OxfvY(*JnT6ZAh@CenR5vX=3NOf{X%C)^u8 zZao1eTjU?cnZ(=_7PcjHnx&!@jmA%bWYX@t4TkDCmwK~$)N_Oqh&DPcmPL#=SAXtOGeSWjH>)X!8*2)^rs!2N=TWc%0)qQ@m zv4Snp=eH@=u-W?jWoC>P!N18IZNga_#b8Mc-{a2 literal 0 HcmV?d00001 diff --git a/Tests/OutputFiles/verify_subsector_trade_write/Zao Kfeng Ig Grilokh - subsector P - trade.pdf b/Tests/OutputFiles/verify_subsector_trade_write/Zao Kfeng Ig Grilokh - subsector P - trade.pdf new file mode 100644 index 0000000000000000000000000000000000000000..94dcd7657dc55f17cdb87a4a7a5ff2929d91d602 GIT binary patch literal 24761 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 O>P}p^aJ`NB#Qz5n^fxj9 literal 0 HcmV?d00001 From 06c242884437fbe3efa4179a29b14c88e96c4971 Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Sat, 29 Jun 2024 09:06:31 +1000 Subject: [PATCH 19/45] Add pymupdf and convert pdfs to pngs for comparison --- Tests/Outputs/testHexMap.py | 35 ++++++++++++++++------------------- requirements.txt | 1 + 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/Tests/Outputs/testHexMap.py b/Tests/Outputs/testHexMap.py index 60794249c..e00feaa86 100644 --- a/Tests/Outputs/testHexMap.py +++ b/Tests/Outputs/testHexMap.py @@ -5,6 +5,7 @@ import networkx as nx import pytest +from pymupdf import pymupdf from PyRoute.DeltaDebug.DeltaDictionary import DeltaDictionary, SectorDictionary from PyRoute.DeltaDebug.DeltaGalaxy import DeltaGalaxy @@ -261,7 +262,7 @@ def test_verify_subsector_trade_write(self): def test_verify_subsector_trade_write_pdf(self): sourcefile = self.unpack_filename('DeltaFiles/no_subsectors_named/Zao Kfeng Ig Grilokh - subsector P.sec') - outfile = self.unpack_filename('OutputFiles/verify_subsector_trade_write/Zao Kfeng Ig Grilokh - subsector P - trade.txt') + srcpdf = self.unpack_filename('OutputFiles/verify_subsector_trade_write/Zao Kfeng Ig Grilokh - subsector P - trade.pdf') starsfile = self.unpack_filename('OutputFiles/verify_subsector_trade_write/trade stars.txt') rangesfile = self.unpack_filename('OutputFiles/verify_subsector_trade_write/trade ranges.txt') @@ -299,24 +300,20 @@ def test_verify_subsector_trade_write_pdf(self): hexmap = PDFHexMap(galaxy, 'trade') - oldtime = b'20230912001440' - oldmd5 = b'b1f97f6ac37340ab332a9a0568711ec0' - - with open(outfile, 'rb') as file: - expected_result = file.read() - - result = hexmap.write_sector_pdf_map(galaxy.sectors[secname], is_live=False) - self.assertIsNotNone(result) - # rather than try to mock datetime.now(), patch the output result. - # this also lets us check that there's only a single match - matches = self.timeline.search(result) - self.assertEqual(1, len(matches.groups()), 'Should be exactly one create-date match') - result = self.timeline.sub(oldtime, result) - # likewise patch md5 output - matches = self.md5line.findall(result) - self.assertEqual(2, len(matches), 'Should be exactly two MD5 matches') - result = self.md5line.sub(oldmd5, result) - self.assertEqual(expected_result, result) + targpath = os.path.abspath(args.output + '/Zao Kfeng Ig Grilokh Sector.pdf') + result = hexmap.write_sector_pdf_map(galaxy.sectors[secname], is_live=True) + src_img = pymupdf.open(srcpdf) + src_iter = src_img.pages(0) + for page in src_iter: + src = page.get_pixmap() + srcfile = os.path.abspath(args.output + '/Zao Kfeng Ig Grilokh Sector original.png') + src.save(srcfile) + trg_img = pymupdf.open(targpath) + trg_iter = trg_img.pages(0) + for page in trg_iter: + trg = page.get_pixmap() + trgfile = os.path.abspath(args.output + '/Zao Kfeng Ig Grilokh Sector remix.png') + trg.save(trgfile) def test_verify_subsector_comm_write(self): sourcefile = self.unpack_filename('DeltaFiles/no_subsectors_named/Zao Kfeng Ig Grilokh - subsector P.sec') diff --git a/requirements.txt b/requirements.txt index 3bf0616d8..5163f9999 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,6 +8,7 @@ jinja2 jsonpickle lark==1.1.8 numpy >= 2.0 +pymupdf pytest >= 8.0.0 pytest-console-scripts pytest-randomly From 49523ba8535287f5b90dc5fb1feaca79b87e96c9 Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Sat, 29 Jun 2024 09:15:18 +1000 Subject: [PATCH 20/45] Flip co-ords in pdf hex map --- PyRoute/Outputs/PDFHexMap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PyRoute/Outputs/PDFHexMap.py b/PyRoute/Outputs/PDFHexMap.py index f6363f88e..18c2d16f5 100644 --- a/PyRoute/Outputs/PDFHexMap.py +++ b/PyRoute/Outputs/PDFHexMap.py @@ -503,7 +503,7 @@ def document(self, sector, is_live: bool = True): path = os.path.join(self.galaxy.output_path, sector.sector_name() + " Sector.pdf") if not is_live: path = "string" - self.writer = Canvas(path, pageCompression=(is_live is True), pdfVersion=(1,7)) + self.writer = Canvas(path, pageCompression=(is_live is True), pdfVersion=(1, 7), bottomup=0) title = "Sector %s" % sector subject = "Trade route map generated by PyRoute for Traveller" From 3603436265af2f299c08b0901f37ce3ae04ae2c6 Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Sat, 29 Jun 2024 10:04:28 +1000 Subject: [PATCH 21/45] Actually commit title text to PDF --- PyRoute/Outputs/PDFHexMap.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/PyRoute/Outputs/PDFHexMap.py b/PyRoute/Outputs/PDFHexMap.py index 18c2d16f5..7d422d2bb 100644 --- a/PyRoute/Outputs/PDFHexMap.py +++ b/PyRoute/Outputs/PDFHexMap.py @@ -83,6 +83,8 @@ def sector_name(self, doc: Canvas, name: str): x = 306 - (width / 2) textobject = doc.beginText(x, -5) textobject.textOut(name) + textobject.setStrokeColor('black') + doc.drawText(textobject) # Restore saved font doc.setFont(font_name, font_size, font_leading) From c982cc6f26f60efa524a95f3deffd13f0658acc8 Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Sat, 29 Jun 2024 12:13:12 +1000 Subject: [PATCH 22/45] Make PDF size US Letter instead of A4 --- PyRoute/Outputs/PDFHexMap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PyRoute/Outputs/PDFHexMap.py b/PyRoute/Outputs/PDFHexMap.py index 7d422d2bb..a1c85ef78 100644 --- a/PyRoute/Outputs/PDFHexMap.py +++ b/PyRoute/Outputs/PDFHexMap.py @@ -505,7 +505,7 @@ def document(self, sector, is_live: bool = True): path = os.path.join(self.galaxy.output_path, sector.sector_name() + " Sector.pdf") if not is_live: path = "string" - self.writer = Canvas(path, pageCompression=(is_live is True), pdfVersion=(1, 7), bottomup=0) + self.writer = Canvas(path, pageCompression=(is_live is True), pdfVersion=(1, 7), bottomup=0, pagesize=[612, 792]) title = "Sector %s" % sector subject = "Trade route map generated by PyRoute for Traveller" From 9c6bf16e81e9ed3238b84ead13f51032d2284d68 Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Sat, 29 Jun 2024 14:16:26 +1000 Subject: [PATCH 23/45] Scale RGB colour settings --- PyRoute/Outputs/PDFHexMap.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/PyRoute/Outputs/PDFHexMap.py b/PyRoute/Outputs/PDFHexMap.py index a1c85ef78..49d80de96 100644 --- a/PyRoute/Outputs/PDFHexMap.py +++ b/PyRoute/Outputs/PDFHexMap.py @@ -126,7 +126,7 @@ def trailing_sector(self, pdf, name): pdf.set_font(font=def_font) def subsector_grid(self, pdf: Canvas): - pdf.setStrokeColorRGB(211, 211, 211) + pdf.setStrokeColorRGB(211/255.0, 211/255.0, 211/255.0) #vlineStart = PDFCursor(0, self.y_start + self.xm) #vlineEnd = PDFCursor(0, self.y_start + self.xm + (180 * 4)) vlineStart = [0, self.y_start + self.xm] @@ -152,6 +152,7 @@ def hex_grid(self, doc, draw, width, colorname='gray'): hlineStart, hlineEnd, hlineStartStep, hlineEndStep, colour = self._hline(doc, width, colorname) llineStart, llineEnd, llineStartStep, llineEndStep, colour = self._lline(doc, width, colorname) rlineStart, rlineEnd, rlineStartStep, rlineEndStep, colour = self._rline(doc, width, colorname) + doc.setStrokeColorRGB(colour[0]/255.0, colour[1]/255.0, colour[2]/255.0) for x in range(self.x_count): #hlineStart.x_plus() @@ -429,7 +430,7 @@ def trade_line(self, pdf, edge, data): tradeColor = tradeColors[trade] #color = pdf.get_color() #color.set_color_by_number(tradeColor[0], tradeColor[1], tradeColor[2]) - pdf.setStrokeColorRGB(tradeColor[0], tradeColor[1], tradeColor[2]) + pdf.setStrokeColorRGB(tradeColor[0]/255.0, tradeColor[1]/255.0, tradeColor[2]/255.0) endCircle = end.sector == start.sector endx, endy, startx, starty = self._get_line_endpoints(end, start) From beb0289425af2e3b2f3632de1957ac4e8b43158d Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Sat, 29 Jun 2024 14:47:48 +1000 Subject: [PATCH 24/45] Commit system text to PDF --- PyRoute/Outputs/PDFHexMap.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/PyRoute/Outputs/PDFHexMap.py b/PyRoute/Outputs/PDFHexMap.py index 49d80de96..fbd69dcfb 100644 --- a/PyRoute/Outputs/PDFHexMap.py +++ b/PyRoute/Outputs/PDFHexMap.py @@ -48,6 +48,7 @@ def write_sector_pdf_map(self, gal_sector, is_live=True): trgstar = self.galaxy.star_mapping[neighbor] self.trade_line(pdf_doc, [srcstar, trgstar], data) + pdf_doc.setStrokeColorRGB(0, 0, 0) for star in gal_sector.worlds: self.system(pdf_doc, star) if gal_sector.coreward: @@ -343,6 +344,7 @@ def system(self, pdf, star): rawpoint[1] += 7 textobject = pdf.beginText(rawpoint[0], rawpoint[1]) textobject.textOut(str(star.uwp)) + pdf.drawText(textobject) #pdf.add_text(str(star.uwp), point) if len(star.name) > 0: @@ -358,6 +360,7 @@ def system(self, pdf, star): rawpoint[1] += 3.5 textobject = pdf.beginText(rawpoint[0], rawpoint[1]) textobject.textOut(star.name[:chars]) + pdf.drawText(textobject) #pdf.add_text(star.name[:chars], point) added = star.alg_code @@ -380,6 +383,7 @@ def system(self, pdf, star): #pdf.add_text(added, point) textobject = pdf.beginText(rawpoint[0], rawpoint[1]) textobject.textOut(added) + pdf.drawText(textobject) added = '' tradeIn = StatCalculation.trade_to_btn(star.tradeIn) @@ -401,6 +405,7 @@ def system(self, pdf, star): #pdf.add_text(added, point) textobject = pdf.beginText(rawpoint[0], rawpoint[1]) textobject.textOut(added) + pdf.drawText(textobject) #pdf.set_font(def_font) # Restore saved font From 9d01c3d387d871a00165d50d3fa1217fbd2daca6 Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Sat, 29 Jun 2024 23:20:12 +1000 Subject: [PATCH 25/45] Set hex-grid line width --- PyRoute/Outputs/PDFHexMap.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/PyRoute/Outputs/PDFHexMap.py b/PyRoute/Outputs/PDFHexMap.py index fbd69dcfb..c674ab930 100644 --- a/PyRoute/Outputs/PDFHexMap.py +++ b/PyRoute/Outputs/PDFHexMap.py @@ -153,7 +153,8 @@ def hex_grid(self, doc, draw, width, colorname='gray'): hlineStart, hlineEnd, hlineStartStep, hlineEndStep, colour = self._hline(doc, width, colorname) llineStart, llineEnd, llineStartStep, llineEndStep, colour = self._lline(doc, width, colorname) rlineStart, rlineEnd, rlineStartStep, rlineEndStep, colour = self._rline(doc, width, colorname) - doc.setStrokeColorRGB(colour[0]/255.0, colour[1]/255.0, colour[2]/255.0) + doc.setStrokeColorRGB(colour[0]/256.0, colour[1]/256.0, colour[2]/256.0) + doc.setLineWidth(width) for x in range(self.x_count): #hlineStart.x_plus() From 850ea641ef32b961528a285dfe573c8de8214de8 Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Sun, 30 Jun 2024 13:16:20 +1000 Subject: [PATCH 26/45] Fix vertical location of sector name --- PyRoute/Outputs/PDFHexMap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PyRoute/Outputs/PDFHexMap.py b/PyRoute/Outputs/PDFHexMap.py index c674ab930..31bc0e8f1 100644 --- a/PyRoute/Outputs/PDFHexMap.py +++ b/PyRoute/Outputs/PDFHexMap.py @@ -82,7 +82,7 @@ def sector_name(self, doc: Canvas, name: str): doc.setFont(new_font, size=new_size) width = doc.stringWidth(name, new_font, new_size) x = 306 - (width / 2) - textobject = doc.beginText(x, -5) + textobject = doc.beginText(x, 31) textobject.textOut(name) textobject.setStrokeColor('black') doc.drawText(textobject) From b655ad4aa330661a0eece66ed1714b698ead3816 Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Sun, 30 Jun 2024 14:58:03 +1000 Subject: [PATCH 27/45] Fix trade link endpoints --- PyRoute/Outputs/PDFHexMap.py | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/PyRoute/Outputs/PDFHexMap.py b/PyRoute/Outputs/PDFHexMap.py index 31bc0e8f1..f820447a1 100644 --- a/PyRoute/Outputs/PDFHexMap.py +++ b/PyRoute/Outputs/PDFHexMap.py @@ -437,27 +437,18 @@ def trade_line(self, pdf, edge, data): #color = pdf.get_color() #color.set_color_by_number(tradeColor[0], tradeColor[1], tradeColor[2]) pdf.setStrokeColorRGB(tradeColor[0]/255.0, tradeColor[1]/255.0, tradeColor[2]/255.0) + pdf.setFillColorRGB(tradeColor[0] / 255.0, tradeColor[1] / 255.0, tradeColor[2] / 255.0) endCircle = end.sector == start.sector endx, endy, startx, starty = self._get_line_endpoints(end, start) - lineStart = PDFCursor(startx, starty) - lineEnd = PDFCursor(endx, endy) - pdf.line(startx, starty, endx, endy) - #line = PDFLine(pdf.session, pdf.page, lineStart, lineEnd, stroke='solid', color=color, size=1) - #line._draw() - - #radius = PDFCursor(2, 2) - #circle = PDFEllipse(pdf.session, pdf.page, lineStart, radius, color, size=3) - #circle._draw() - pdf.ellipse(startx - 2, starty - 2, startx + 2, starty + 2) + pdf.ellipse(startx - 3, starty - 3, startx + 3, starty + 3, fill=1) if endCircle: - #circle = PDFEllipse(pdf.session, pdf.page, lineEnd, radius, color, size=3) - #circle._draw() - pdf.ellipse(endx - 2, endy - 2, endx + 2, endy + 2) + pdf.ellipse(endx - 3, endy - 3, endx + 3, endy + 3, fill=1) + pdf.setFillColorRGB(0, 0, 0) def comm_line(self, pdf, edge): start = edge[0] From 88536c4883e78127335ccd8da7a477e65ef9a874 Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Sun, 30 Jun 2024 15:07:24 +1000 Subject: [PATCH 28/45] Fix trade/comm line widths --- PyRoute/Outputs/PDFHexMap.py | 1 + 1 file changed, 1 insertion(+) diff --git a/PyRoute/Outputs/PDFHexMap.py b/PyRoute/Outputs/PDFHexMap.py index f820447a1..d7e6dde25 100644 --- a/PyRoute/Outputs/PDFHexMap.py +++ b/PyRoute/Outputs/PDFHexMap.py @@ -34,6 +34,7 @@ def write_sector_pdf_map(self, gal_sector, is_live=True): worlds = [item.index for item in gal_sector.worlds] comm_routes = [star for star in self.galaxy.stars.edges(worlds, True) if star[2].get('xboat', False) or star[2].get('comm', False)] + pdf_doc.setLineWidth(1) for (star, neighbor, data) in comm_routes: srcstar = self.galaxy.star_mapping[star] trgstar = self.galaxy.star_mapping[neighbor] From 8d3e9436cf80598772bd51c7f988cfeb94dc73a9 Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Sun, 30 Jun 2024 16:08:32 +1000 Subject: [PATCH 29/45] Cut empty, comm and trade pdf tests over to image comparison --- ... Kfeng Ig Grilokh - subsector P - comm.pdf | Bin 24101 -> 24152 bytes Tests/Outputs/testHexMap.py | 102 ++++++++++-------- 2 files changed, 57 insertions(+), 45 deletions(-) diff --git a/Tests/OutputFiles/verify_subsector_comm_write/Zao Kfeng Ig Grilokh - subsector P - comm.pdf b/Tests/OutputFiles/verify_subsector_comm_write/Zao Kfeng Ig Grilokh - subsector P - comm.pdf index dc4b7f8711b2ef5d7a490f1c81a8f7d6a5b4088d..afa46f62e68834a783fb78569b1bccd5b208ce5f 100644 GIT binary patch literal 24152 zcmeHvd011|)_!|!YfEc!xVBm=Nu4JMgMf&Tq6p47)PbTzz=%{NmO*3)xm8;niHL%T zk~-IsDhgE~xt1y@NGe!y08(U#7$HJ{5E7E#+WVZ7oY48}{hsIdJm2jf#g()7K5MVD z_q*4-);eeOLQgMiI~!+<(S_#<&lg@OylCOTv}N)FKeL!NjrB>)MlO@JFd&#~0sjx@ zhD9>%;Ob%v)?#i1FDg8U8^N?Ee((XIdrxSI;Oq7^W-T^=ob+g&JEaLvH5Uyl{9P3qXX4y7sJjE=WIRJvPU~HaK)M>+dk^^ z#`WF%%*TW9=G=Xg`yvu|_$sjk*fM_XlG z(^i(Z9(r1IrgiiD)@JNpW0(1YLG~BnrQD&?cMPZyqQ7QtQBsm6Qm-gZLpOD#I^nW%;cY zV=`7wI#m+t+o!_v+QZE|<}bUpVqV&#%Ws@|zu;j*^UW|;!HPLZKAC%ZWoZ5nV|=LV zEZlqnB@(?iQKB=xYDL{G3*USaDb8X0gS_w1H26d4%Kn8N7y?y@ubH}_eW zy4zX2;uYRS58orMtbdoGvZ`-#^eu$oY<4Rb_cT>s}tC)eO zw=36e38^lJ{?6A{{9x)n3v9pr$O!TAw@yXPExc*%wl~MokQTzAYLntm%s@7v6%wc)ahiD(~dg z2M%zZ8Sg!4o+9dc563@sJ+%7M+{r7e%KL=)4MM8aY{PaK z(z)Nf`FMKs51UrmymyIa6if4YuwpN5GqXbHiqmQLN4?tB;r^>;=+zz82;B^@M(Fc` zeLc`*mE87^A3t~vbjfwD#SiGKBj&wp%zO9jHJ=xl;W5n&ESD7neOha3`V?M2cIoN6 z0TKPCOVi_)C|k~}t#)er-n`c5sJyg59jlGj>5$hH|j-MJF`a>LohG zW1MR6jSbFKFO2_zBS^X-UmvSE(Eg%;aUe_G)Y-7$ie1K!OY@^kZ(S*K>OZ_`0jw`1 z!}=<73au{Ry3TKq=W@j)q;Th7zSXkL~@c>=SZD&4KIToB4b6ZlWMHU|3bb95aW9K%b9eiZN z!>Q%H#60t>ur%rReZOD@?P1KdJhowe|5Z2R;sz~Hgcd$X1}q!^BHDsTbT=2_Orc*z z>zdXWaP{`8;FrT^+MYkY=hNKwB`pV@MZF>O?~`q>E?5!0Hr=&-Y$

4K!XmIq2 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 MMvtE9<7M&x0AoIbylES1{JU> z(mEkxMTR8TqJn}@K}7|Us|=9>Ng+T8A<4V;KIbGS*zw-`e!uto-RmFf%Gtx(>+JoU zXAS3!T{?TNh4o~I0b>i#6`n7=PhzcytzK0)gn%? zU)Uz5jg7sd0~7j%hJ|pvf(C3mUR$#`)2AvvS6q1c(Ovr!o(i{`tcaGOXC100mIsdhDEyD#y3JfpiCmoQ>fcQf=4qFTkGRK~?nZG%8tWX90l6==Kac8m3u z5dyWYP|&R_R0w*~1iJ1zGksT~gTA>igS#$98@-; zSXZeRv{q;66=|$1iLss@T6G~qH?DVe1vm3q_2v1_9xI}ymV1i=_A$8UZq(UD=+{s4 zSkXJxZgl2Ekqi1;w{}5iL-_Gw?lJsHv1UqrskCuPrgXgX#_F8zlt*V0D>H{T>B=|z z`>Pwpi;EI-R{7>6PSv!;gRf~_F?@T*uzizAl%qAPRhbKDJTT2%|8*As7 zCaZa+l5_R^mrc!Ik82%k@0eDxvjWSrf~`C@9-b9Eh`8)N*&f8jlZO%F?)5EdhjOc+ z%Xe;W%qvZ>vl2ePIoZ;(Yg^_1^sr=$fTca#?%ce!Ffimcd7V#)RC9hYx_Y^U>U|rD z?Q#2WqRtzlzS(~;@sUqFAuf#)90vKmkHF9yH}5-kF*R7pO}RjMYss;|kgTN1*_?rk z*!3Ih#@erI*l6wUTk?LAN5{s*#NqkErkl%=?h}MA4d{AgpWzMSU(vOvZQSsYmfzp5 zGngu4ePF?jaf$0Eohb=i*RL{TeM=peWf5?A7G=v9OpDr|quTHHXld`+I9#}PLE_`f zuccQxUts`-XQMAVm@{C7#7WP*DgZ6&#ZyYdl#>!#Non64GlL%P1l!S zJCI;8L3@O!9qBpg3x}!`=gU%tZ}3faI-bei^8KKK*Ipiap^EF?nbO>x^HPtfv$J}& z_BcoKa%Q);amOE83>;o`>ClyXJ1?!c5i#ZZm0*A>w z+}@vj$P27a@4dvdx5A`%d`$7pF`&w!aY;xO*Qk5@$HG{n7F(gQf}6g)a-lyA#^wHq zRe3AFe&b-LInk~|7BwgA+HVd!+NAg4{d0`H4|RMzeE*|~&zz2K_$+72`kIP<{%%9i zLL@t}9p39YV%mHnz4-@C?vsaQ(ToE3lWqmesH~kPR_I!rk$C^3m%3ZS(QF?SPBv}N zFkQFAbRoh!rVIH4GdMMvm_b-#(!1wF(}k>>JnRRo?4IzRBa6z16($);_15 zckSMA@slqW=4VR+Cde7@%HH0YYO8AWd+L-g-1SU1ceDJ9=Z>FqMQaMOtD%9}`~0J# z=%fU{rj%NbS#5&_TF2MYxRoX4r~MTC%xK}KIhGM84?f;`@tM%u{!IAQFQ3l?c^s0_ zmbvQHoG^MeY{0yIDzpI~@48@KyVw5cAdt~6Kr+90;24^SmPZq{ar@_l4OO5$l0DE3 zZyBSQ)4_c5@RfV33UVBtUMaH=9M!a9@-Pt5-o_#z09^#cpo?HCqgY$9W0TvE*=yP) zuw$lGUkX2MnY$K=aFDPFTbnLMn2P9*8y7tfZ5X2nHsFaCTV~x}F*)H^$A;}j9~;gf zU0h=#E;KvoB0U4UPpyhd!4}lQ);1c&wZ~M9N;JI+`{nt^5_C@dMqWMCadP-VqZr&Y z1J2;dbKB4vObL24tpd(BhzK39xZ6}bW-0=t(i`vhn3UW6mgv+QyTY$5Yfgx{BYa0# z_K)g-4XYbP!3wfQxeWnTE*Cqchs4 z*s*o8w`c#frgw4Udo}a=`LDd8N@Eo})U_PCVtouDS?QUurXWDFQU0g;dm$*B*>u?y z`jRGNu4(!4`9!+Q=BLCb(Y(#AuphnSfFF+pxy~y9>=u2Q3~1V8C=tW- zLK2XO>!&F|0`34f9B)9B1*o+vJkDu@)4&g?2Q;^Cq0sK%qblE{a^Ys1xYeS^=n1mR zokBZer3RcP%{b1~Ot4AlW->r4?(7!D3<}r|I9`2h1G4(mT`8u68_9VgXIM=e8=P{!8%*#9}IyE4@WXq1Kcu8mAJ&&*Wu0t0jKAoFsuKg){PV`XK zs8ti7yXa8I^0I`ERsV=s`EA&U;NHA+B|EBGG*G>&K4;B!_~ma-6&a}(J?DNR6nJCn zX)_nMSJq|`lYQ7Sj~jJ$!lmtEJel{Sc#qD&_ziOec(T{B+@|SEaxM&b|NJl9%$-dB%cHTbx3R9w)o*RX zx=zSXSBeGsA5;dMZ$FZK6Lg*ARFRl!vHbjmU$C(@U|p}Q_4)@k%ss5@o~&oYWQC8B zuFJe%-Hs=_l?}R@Un{W|p80skqK5p|E&ZOv$Nc>GWYK_EV?TzQad4As&;9H;wkh9kNemNgd2l_`J>T}g5$3lk?o+>%mHngYY%M!wUjH#q zM(k3CmZx0LT$iu^>EZZuk(cg-J}4NOv@mW}WEUCc0|IAsdK8PtVQ7L=h&f~ML zf7i36tSGvb8=Wi5|GA5G`qaJ~k6%}Yd+sQWXIvS{kDIq@pDUxRihpp5DzY|$#|T;8 z@T_HrIxq8SV=B9YHF)8J1KDPc&7xb!c`=o|xz?+uma&pDHSceet5+#TJYLy3RkJ}P z59n}gFN{8A_T>9_+MgFf?+>4|6cxO=(^t6?&3DVyD-1%J}#bKSpU4f8at@?{aza(p5o3>%eL4vv!^`yL0wsB_8~xpYh-xt2o5q z>2mcf#fXEez9*W0mnU-&`t4lZ*@y>k`#IIKGj*BU&=kCo*Djc|znj{_%suNI%Q_A# z$(1WpqspIOyi<-1Q&{C9_}Rr=ban>T<)D|U_oC{+%jX8A@U1*mc5$^zGohc)+6T|^ z;P+ZmJ@2?2zWMQdLUVuDnCT%MGvZbyj^7HWR?>{4Rz zEvnu#sspTZi-^HXYnA6Dwqq`Q7T$F|y0&Uio?^&l8##n6r}{dze*cKO;2n zlq)|K`b}|5Z^pWOt4Ni0rVb8%kV)JzF;Dlc>Oj=FJ;dN|NYLQ#UtF*i4?b^Oy|m+a z|Ine?t|JdraSncER&&^9XWq+C4lk0pb^Nd^Ha4qbr_Y*|yq?k+Zb{WgEXk$$L+Wdj zuVzGKsIEr0KC8R8GWA9#<3^`rG+du5x@!5+0!4F2PpEoCE_;>0R^8S4WrZ~(eqEfC zEL2n)@diWvWyQVMk*wJQ@+N8f-iixm=GjcubLAM%MyD&Wz0;eX?oveb$(YBYYR2e@f-p(F-@5+_HHi*nT2FzRL`P%i?m6# zJyy*FLStEAbojxVdnv4A#>1hFXziM5<6?VZ@16go6m7+>@I|B5us_zVNkUtgMmW*GR!zzX0PnGY-Ka}Qoq$Y=dm78rQD-j*Kjm-|Tp}W8zb=Wng ziswt83-%&?@P)a4KJH%fmO}OAOz-xDB?-a&BpA#!Z8-iRDWb{6mNpu)JAJe>s#3b< zu$7I27c?A(bKwi4qjQ0l(&*Cp?^Kl~IaBq>Z=<559xdEk0tbbzOrMn=GcjZR8lfLj zja+y!pkKVvh;FQPd7WOc(9wJka&S0y^ugWfD|K!Mpr(%oC#8k91N3KP2d;in?DAww z!KCH`1$C)MW=K*F^1i;!+y6dO!SGJ+-!2cVsr2jOxq-}mbh^J%?o(5lUy0?|GwmP6 zXqgwwpCXZ~zw}OeWv}jJY`N-Xy^24bc*69R(poT z7GO34a371iO+~l|^sBJ5jUo+3cVop3;6$vWIE{oKQdF3V?2gnCut@YgLRbWkN^|3) z+*r$c^3GJ%$lxxjr%Au%*INqx5MsmA42y98=psDS=_2eEnpiS^foE06`TI7 z(4v=^T(9pVMwniuFqT{fY`nony)Duv09b(H=&_6JZNNRk{07`3?*@f?q)6eO@m^uv zyS{Cd3GPAQ^k7xQgs;aT7+f+SoO|_pk1l@RQKnt_9T&cYz`>`P9gusN7%G5QjXInYeL1YT5RQyX z&lZs!9sYq7F2c$I2IGPA5x51M5BiHKq`}=?5r$)=0SY*699QiF4+Ov#UdL0QE7p-l z)FXPMhmCP^BkJMavAq-)0D(l1N}(QNpLEni^mNqWqJo9w=_Z@QF2)Fqn38p5J?svA z*oof7aM%$ZW7cH?_dxjo-6LT;(3`_HrZtZT9!0kTI&Q{g#{xMWfe{sJf)G`D2%fp~ z6A~2SEKuEu&xF+s_)M;w!e`)<=*dFi^Pe;W&=}+7&h7c8W`K=MUkEVY#~4BJ4RO;g zE*}xJCXRRI`Zo86BO;4jChjP38mao|uxgmybP4;`0$_NLuVs|CsIE?#E|Fr!2Qp%O zATt&>%?Wo%;NG7bn_{Nq*c3jKV^g?Ctg8O?1+E0mC7>(SK=ANf zJl%bgb&)2cm?^mo>QO+BofuM%A5w(g_@RYv$0-88T?Bvz7{AG}DZWRJ4f`6iMRIJ4 zLX*pY;~fT!F8l49&D;DCFSRpA$aZIGUa7qY`veJe6YpPm{91F7>lpv-_nkjcbwoBl z4BXKPsX*5L+EZ)OZpaUQHRZz?!FdAst|Q<}J1b6(*7R&>N{U5fK%!(@DwR~AJn076KIWg2tKFe+~Om=xyIJ9Mwl)rPUM3!k%=a=!#OeSPQf(L zOQiZ)y6U5iFb%w21Ii$Wmp;1zr^)WHNhn%{R&GY)QC{uH;YE)a%w-22c4e)La?8M! z&*3mJ`ef7gKmrU@7z_hp1Or<|hN0jOh&3_{Y(WE0FRYI@<-T6+ECp<6qow}Pu0OkE z({wf9-_vdD9=@mkI}QK%1MBO&&riV|kZ^gYW0$wF_BH{4fOw`Z?{wWJLEby*s+^`y zS-WkB;A{-qhSOmiqEi6du+eE}Uze7~%}5_A<^=d1G@ncNlg5W8B2R%Pf=6%S0#gY1 zTMl&}zqC(p>cEoTHtd?e;_Xq$avb}De^pUUb;nVSb7!h2;iifyG?rnn)j%8xQEGY> zo@&Hn0bd1rCKNBu%qrh0NxYqiz*}#sngIjn_O$aVA~niN10gc$+}ko^c~IXuZiD)2^u1c-Z@ zj0R^8gXy2KY5fa>Q&9?%wq}^JJ@8`aEVi#zDDWd}+IuY3_Q^%2YSWIkyz{p0hj`0h zi;S?B&mX6-*!)F-;J`QIiu1ESUy}A%5#;|xf!OOXAuB$`GW+PA!`+=buCaDw(S`p` zdRkFOk8LDL{TLHQDUr_<%0j;0FYzGr2mOu@WyC#9uv`teM~DVEA+J)%g*{x%z%lb9 zXrwWaSV(w|6!Y8hPdSn~EQ0jNu+u80um`QHE1peZ4L>iqMj(P?5fW zDuMv?!x$7`wNtba6Kz&I#ZXDnB!a*~ejP#lZXx1lp}!w6cXR4Dyd&4cl7k9Fv*M@B zK;rW6c-aFVs$yqXyz)IjWRvot5Qw10Ou!$O@0!KhTRZYi|LwiHVFJF(E^Y}p9g94Y zG<@uIOai9^9`fMxCh8Jyn@iW_sfb8klBMP|@KON=DKGVLl}%)6^Q2l0=@cjOEe_+Y zN1T7u9%DFpV!M7Y@y}WUk_#GMU+7B2&k%Jhp`Ul6Hx-kz^2EI<-nRkhO|mg2wF4q~ zoOWY`tFc6saN8{gf{;Xn3!;uNx00e#WOIu4y^g1jhYdoysi?QP*Z^y2SvkEMnx0d> zKAPJY=);p4W0IxV3Wk^@wi?|#$BG-hbEJ3zW6~%hCW#`N7AD>~Zf*okWOAB!j+brp z&XEamTnMu?tjUXtrx?H=H-|GvU$)UZCvE^db1$O6ExrMgc5YPJrhM>W2Hq;U{IJ+b zK7R!AamObY{P11BcAfR=qfABFEf>D8_=j|mcP%Dlp_35Hn?7Fhtjg?J&j+F((qC%7 z;F9wC0&Z-0ls32b0O@!{xi+!V;?~9EEQiAzgikxucMghT+DK3Ev4=JrWSod_Ae~-X z1LXAXDcwiJ`)6RMcX0Ah96FBPft}vDKC6kC{6OsVCZpL<4#k0RdLgG0PsHTW_3t5O zf1XI8jJ%>inK8IbHkT^lsE)}VgOem&a8$vWM5_I!s&MY<%>h?w&C4O$M^_xBwroBj zQVp-Q@*sP#$fya)gwKQT4?c|=u0s^fWMPo3GB#I0z}-Y~02QMqtoDBCr_4YmQb%Z= zfT_mo*!TZGp{WQtH1(w?Yu4?q;}lqHih=X(Jvt}Xb;PI5Emhd>9b~s3$NRbh&NCQ$ zPxoJzJpuW>l9sBq?3nK6>_;y9I_I}Y*3KL9Ow~GAz!#cHA;z9oQeLY4zK7G^+Pov( zn{^qI5oNOICIxu9JHR$b{>|jXNI1N4#=yEc-Sb7+37Ut7G9ff?GJKK5)RBe&mI0)Q zbOj0t8=OmsC>l~8Z2sl6j4}F2X!a{P9}3*bmHvJP2~JBsZ3l>(98a|l2#K}- zz_MhTx#HS*PF#dvU8+yJBUvj+0C}Xn|BnSuBUByo4lJMg-g&Pl zdrxy{HxjFtWX^7?JL(}AXIZRGO;pI#{b2v0%xhT@M85`6XOqe|yqH4hlM5-Pf__8< ztN1VG5FjT66bq%>afFfbcuAtzhc{C08F`g*&we9s(uef`#L!6)74sum4@605`3S`1 zP4hjhi%oW_$e&$8iC`r}vyBXv0WfVMQrT$CZy6{?Vr-1E(M5K`Tdci*Z`V5DJUNxx zCY$#Wfr1!&Gt_;^8$8%?1eV~+kVpA_`ui6sDtfM;vMs-p5^m*DLV>je%F4jRA2&q^(tFkPZT3&RAqB{{a3kCWxLi{ za4D?R6%jXeQl=K2enScuyR)?8Po^h~xJTwEsE8kSkc_xTdczd%Vb6_*d#J$WSEBs? znitu}(WByhWOjoJ!I9Ywu7MEYqz!lmBq2hMI4w^KB`64>sc;|RvLY9T*4!NB776x+ z@+__>#6)*kp{xQwOblMXXA#P?ErdMVqhHJUfCoey&G0PwHJgq`H5;ablWBA+q+le| zI8|aK(>P<)$11=oTWC2SLxOih!Q?-e;Kd2wUr+F|YloyuuQql1wQnagZA&53hEpBL zp?cakfeXpaKEPzCZ80DdQQJa_RA8PI```gp+k&y?MMQ;1E>4?}2nucjU4-327r{(4 z@r6vZA;|x4aU*krh|-pSIdO`d=W6@^IMmC5D4%t-XBCbyhYHT>e1Y(#q-&y4fynXl z$W|^ml_`)#BML?$Yr)$pLB%eJfim7ODNo%iq`=Zh-5;B5c?Pftl*AIlL#vq9NwbvbpiFG zRHxrUxCUi?pWS~cQvcp3i!t|OWlNt_yc8)Y9Je?rVtieyBt?SdDc_85YkvK6{@ZZ* zZ)%A|`_>T&g9}gH2ETgUO05l)^&P65d>^ADRu-^>LxD;|0$T3QOr8F3=mgBj{l2vI6+gT-Fy= zwKQ!Gz)EY5>nmxB!`?RWV4-OBcU|*z(}n$Ot~qRGx`+FJMpIO?dOLXzBzlWVwt3@3 z?-1~CabaJkFlKuEln*x#Ye;?fXYAmz-kCq-r0+5!-j|sb(`!=McSE6pfS=HeJJPD( zo1tP^gC3=VDkE6#>7z8M{-@0=6|^sN0RxU zR^b8#FOpH?7W_BK(S<@d^fJCbOmajn%FHbXC-he<24Ppz=x6aCkyBPI+IX)(X_Yl9 z5d@f{_wFMqOs}#{omE2_4raD5GIT1lYar-E?v@mPFLZztQaH1_qivc=X4lHycVwnw zDOtV@pa<0~T5~ez(NjQF`4xJm-IOmozhtB8*36?!u_rEW>B_(J)}&G*!eWTsmPxqKyBfAC)|K%}KOe!ULUnBu@5J)AhY5e+2T z=tYw$4seUy64pj&tI*;DPAY|BnI0CiBL*@omfRwL0 zHB)H;)wEQMju4G69iXc@=$#OvWDvM<^nO|bRu(Rrqd^Zz3<$fiDk#x}qi}_hDcpN3 zRHlzZc32N7=8hnvtN}5#;RS{&^)UL&5qKs8>=82=(1Z{THNsd-K>2Q5(497%n3@V6 z8caY0;YbruXhN=;su4bp^l(GDaU!VcQI8kNrU00jnnFEt&2RweYlf%@U8LfB*dn3^ zL%pDE4skOm)FW>ORnvzx!S%nm;;#%_#M4v{Fu}cKzlO4DA5^tHY;n_lUv&T#?M*23 zPj01WcHOc^nPhe^Jbt@RW_M<9%p^cP@9ul@Q6ttSYgTlYpnbPcrM{7Ohj@ttD$f*< zxE<8|v7z*g2>7icUkKQBTT@U58!zjk4Uzh=ibSckVi*9w^#LV*;FXyh`=dI!1Lw~X z?|9f5>SdB~E)Cbo0U`5o=Me8B!2EC<;={ioUg|)VY*kRSw~BaU1XZ$u=W&@fH&|z{ zu{BwW{W1B*Nnt%7UX7s5E>ZUB4i82X)#0Tyqv~X=lfHC7W%}%}M@+hdk7ewxg4$~+ zVNS-cvyfGH$k3#n;!$=t4s<8pI6+l+kgKCFwNH0Ab#yzyHZidiBm#fe%%7Temzs7* z{-CitF2u|;ni;*tc6;m4T-T9@5;+Is(OYac@;4%P*R8`Y4!^~=ZOsAlEw(=_>5I9t zWcc2SYCBY35T|jml$I-BUr-c{0_4YIp_t^uXfmB^cuV1hfO((77ZhuulP6dlUHl_1 z1?BosK!(coafx%E$bO$F;U8rU$VxV{NCsS8dcp$yNxDdZ3Ryx!!7LeJG=Lvfiz0Nd z43I1D^BN%q1Wq~yeq?S0wl+O;@_Y3nD7cvu5Ot9M&X)}uE`D(jb~e3tAMr1yVN74U z=^O0qQ@%shI}@Q$R9}I?kQ9O0_@#d1)V!hKP=v1(UO4eZudKO_fG@xN6K}9}!W(QZ zW=UWEH@RWz_5zsDsL=m*a)a@onA}Jc!P{#=|9yMyFS?ICPyU^6uT}rYy}g!+DwwzN zJZvDgM`kt*j_!X;S^nRavPd5ir7ZB)xJfNH`S!CR<3POqOvMj~x1ayNeQkI672V2) z&6$fKSKBc7U_-M*-1?=CGV_;9s<_NtW?NGEcb|BQzm#kjIWpo`i(>j^z5015>*Tp; z?Q09(yweyxd!@Q(pwBR$yXrkB+dD^YnzZSta3_;-DliA9{a{b z80O0MBaVz^%U~GyS4V}i#(WFIoE$I)hGD}n?+$(U1LcUedeC7-oTbeC&h0AhiS!fh zWv)JlVVHR{t{sovG!BN@w|%oCWAGvvrvIzALs%yhV3?l%Q3urrR)P+D#_#z?IXDG| zS^DYHk7ed#U>N2x=87`ccfW>VY-gN09$Rbk;VP`5_q|w#P-e-C zPhux8Nt@(R#wwAGQLdfL4!>(w+n#zy8*j11M>+kWtgXAXcS?iSVM9-v{ud4FS>kie z9%Y5!Y@UlY`Ek@z-Iy&R4}mb|iLAS`I%7@jRrj~F4G;9&%318MZmp5lJNZ5CJrXpv zLwDwBYvme&y6gwxSI--soYS|R>t0o~#!Ou{tMTESo)w>Xax2F)ho%XxcWs_zBkCC^ zbv(Rja=T9M^VwN`YP%5?%cS{SarlN&N9zS!aLII zx(5#GvJvs2@hX*|a*S+NBP%Rk_h6Pt8JGKu(^PFrSCx2&-YiY7mx_1P72UPf#lFwh zYldgFwI5AjHL}MQtynGhbi3|JgHi@ zEK47vZ!2dDRCQ+37=4?J*KGHo_s33&bYDzc6fam+UAOjPb93g~%7eV#ps&?At+DLh z-b961sN&@;&gVyj=D1$$>Nt0E$IsVpS$5>}*Bz>BJtw=Xs5#Rh(#kE_`tBX-TV1-X zE(vN?>r>J9+H-j>`emaX5;P6h!yAJ=(=-}}O14HEF8=Iq>h7Dm$_RE$Z(ADIV(Vnj zcHTpMOiTASB9VsoP#3gRW5E;bInJ$G(>46fi1);g6l`5vok|rWe!r{hhcdSSmjp|7 z?Ph(RZ*7{os6`uly*m66+qbp9NY2x(=&dYD)2r(u*jgV^Hvf>D3L-VCSZkJNu)4gh zRlc?8N{vS4EKSqO%hyGF%e!``i8X3ZwyvYHw)IfBE~T1lkt4Vr?5Y_a*=#QOR?2$V zsgBN*=6dfHza1y8VT;(kY!`;UU8sK8)fVI4Fgd8Q&EapNXyI7KEkVTS*k-NcMzJzc zD$j`?qc1F%YCBIwb`|-yN`q^A`wibO)bEn&v$kFlh({_)>(tj$WXhhO9UR2AdTX6L zR-moal>flcZ;aW@7|4J43FD4J@T_#%=-8J6J%6blHa5dytUy&Dl639Xm`6C5^_;11 zR>Xv{FKc{bE!D}j4#y<1EfEg+?bCH7S6elCk9g5H#E;6;0@z~qycn>ISWwnXXuy6(mN z=&ZcZt&m70hh)KNacu|HTYFXPXiX+V9LoN|JF8jUu-{!Xy!SrqN&d5@yTw&Sn!4Ve zyKCfoABaAWvTcumeWxuFsJk=_mgKY6wmD`xpicWYtAPi2hS zC0Exfx=*^+JL*KwhdVFYD6}(EIuu2U#CjnwspOUZ3dPrKU2jz^TiRRersKXHx=#E& zf;;oOtF_@RTu;6HTqZZjLAXj+q=<=U(M8040aZyO6TZ6Pld*RqbFF|mu z@-Bbte(v!JPt6oOzNXGBVvT0Zf;|t$EqK(sdCiu}4tsUhq%@W|_gulV`A5VxQUNkS zfCKMV^-^_HYs?8&k@&c<{3CwO-uW?_lHg-f3(<JBu$@*S( zM@^=zL4V)<`d97hjx=e6My+84dt}5+7v@SkUh3=$m#gztot>S!ri>Jx+1Bu$(P>Kr zQ38FZ&g<`5t4;Z>?QSyNI+jK}R@dUCV_X<=qaA(dnEYlj`N?DSY2_C_yc{~fa-qLZ zDANsnmU%hT9epu59DRp*faPM2kH6PU?lz{I6> zlgk4vXL^Nl(Epg0^EiQ9Ibr_ZUIQ#wgl`PP-+V@2eugRL1jEOc{e%6OmTUZjX9S1( zQ$NtgxBsK@SNaEWLM@hagS>+OxfvY(*JnT6ZAh@CenR5vX=3NOf{X%C)^u8 zZao1eTjU?cnZ(=_7PcjHnx&!@jmA%bWYX@t4TkDCmwK~$)N_Oqh&DPcmPL#=SAXtOGeSWjH>)X!8*2)^rs!2N=TWc%0)qQ@m zv4Snp=eH@=u-W?jWoC>P!N18IZNga_#b8Mc-{a2 diff --git a/Tests/Outputs/testHexMap.py b/Tests/Outputs/testHexMap.py index e00feaa86..b6fd3ad4f 100644 --- a/Tests/Outputs/testHexMap.py +++ b/Tests/Outputs/testHexMap.py @@ -2,8 +2,10 @@ import os import re import unittest +from PIL import Image import networkx as nx +import numpy as np import pytest from pymupdf import pymupdf @@ -149,8 +151,8 @@ def test_verify_empty_sector_write(self): def test_verify_empty_sector_write_pdf(self): sourcefile = self.unpack_filename('DeltaFiles/no_subsectors_named/Zao Kfeng Ig Grilokh empty.sec') - - outfile = self.unpack_filename('OutputFiles/verify_empty_sector_write/Zao Kfeng Ig Grilokh empty.txt') + srcpdf = self.unpack_filename( + 'OutputFiles/verify_empty_sector_write/Zao Kfeng Ig Grilokh empty.pdf') args = self._make_args() args.interestingline = None @@ -172,33 +174,29 @@ def test_verify_empty_sector_write_pdf(self): hexmap = PDFHexMap(galaxy, 'trade') - oldtime = b'20230911163653' - oldmd5 = b'8419949643701e6b438d6f3f93239cf7' + targpath = os.path.abspath(args.output + '/Zao Kfeng Ig Grilokh Sector.pdf') + result = hexmap.write_sector_pdf_map(galaxy.sectors[secname], is_live=True) + src_img = pymupdf.open(srcpdf) + src_iter = src_img.pages(0) + for page in src_iter: + src = page.get_pixmap() + srcfile = os.path.abspath(args.output + '/Zao Kfeng Ig Grilokh Sector original.png') + src.save(srcfile) + trg_img = pymupdf.open(targpath) + trg_iter = trg_img.pages(0) + for page in trg_iter: + trg = page.get_pixmap() + trgfile = os.path.abspath(args.output + '/Zao Kfeng Ig Grilokh Sector remix.png') + trg.save(trgfile) - with open(outfile, 'rb') as file: - expected_result = file.read() + image1 = Image.open(srcfile) + image2 = Image.open(trgfile) - result = hexmap.write_sector_pdf_map(galaxy.sectors[secname], is_live=False) - self.assertIsNotNone(result) - # rather than try to mock datetime.now(), patch the output result. - # this also lets us check that there's only a single match - matches = self.timeline.search(result) - self.assertEqual(1, len(matches.groups()), 'Should be exactly one create-date match') - result = self.timeline.sub(oldtime, result) - # likewise patch md5 outout - matches = self.md5line.findall(result) - self.assertEqual(2, len(matches), 'Should be exactly two MD5 matches') - result = self.md5line.sub(oldmd5, result) - # Clean up double-trailing-zeros in expected_result - expected_result = expected_result.replace(b".00 ", b" ") - for i in range(0, 10): - old = "." + str(i) + "0 " - new = "." + str(i) + " " - enc_old = old.encode('utf-8') - enc_new = new.encode('utf-8') - expected_result = expected_result.replace(enc_old, enc_new) + array1 = np.array(image1) + array2 = np.array(image2) - self.assertEqual(str(expected_result), str(result)) + mse = np.mean((array1 - array2) ** 2) + self.assertTrue(0.2 > mse, "Image difference above threshold") @pytest.mark.xfail(reason='Flaky on ubuntu') def test_verify_subsector_trade_write(self): @@ -315,6 +313,15 @@ def test_verify_subsector_trade_write_pdf(self): trgfile = os.path.abspath(args.output + '/Zao Kfeng Ig Grilokh Sector remix.png') trg.save(trgfile) + image1 = Image.open(srcfile) + image2 = Image.open(trgfile) + + array1 = np.array(image1) + array2 = np.array(image2) + + mse = np.mean((array1 - array2) ** 2) + self.assertTrue(0.2 > mse, "Image difference above threshold") + def test_verify_subsector_comm_write(self): sourcefile = self.unpack_filename('DeltaFiles/no_subsectors_named/Zao Kfeng Ig Grilokh - subsector P.sec') outfile = self.unpack_filename('OutputFiles/verify_subsector_comm_write/Zao Kfeng Ig Grilokh - subsector P - comm.txt') @@ -377,10 +384,10 @@ def test_verify_subsector_comm_write(self): def test_verify_subsector_comm_write_pdf(self): sourcefile = self.unpack_filename('DeltaFiles/no_subsectors_named/Zao Kfeng Ig Grilokh - subsector P.sec') - outfile = self.unpack_filename('OutputFiles/verify_subsector_comm_write/Zao Kfeng Ig Grilokh - subsector P - comm.txt') + srcpdf = self.unpack_filename( + 'OutputFiles/verify_subsector_comm_write/Zao Kfeng Ig Grilokh - subsector P - comm.pdf') starsfile = self.unpack_filename('OutputFiles/verify_subsector_comm_write/comm stars.txt') - rangesfile = self.unpack_filename('OutputFiles/verify_subsector_comm_write/comm ranges.txt') args = self._make_args() @@ -414,26 +421,31 @@ def test_verify_subsector_comm_write_pdf(self): secname = 'Zao Kfeng Ig Grilokh' - hexmap = PDFHexMap(galaxy, 'trade') + hexmap = PDFHexMap(galaxy, 'comm') - oldtime = b'20230912013953' - oldmd5 = b'ff091edb9d8ca0abacea39e5791a9843' + targpath = os.path.abspath(args.output + '/Zao Kfeng Ig Grilokh Sector.pdf') + result = hexmap.write_sector_pdf_map(galaxy.sectors[secname], is_live=True) + src_img = pymupdf.open(srcpdf) + src_iter = src_img.pages(0) + for page in src_iter: + src = page.get_pixmap() + srcfile = os.path.abspath(args.output + '/Zao Kfeng Ig Grilokh Sector original.png') + src.save(srcfile) + trg_img = pymupdf.open(targpath) + trg_iter = trg_img.pages(0) + for page in trg_iter: + trg = page.get_pixmap() + trgfile = os.path.abspath(args.output + '/Zao Kfeng Ig Grilokh Sector remix.png') + trg.save(trgfile) - with open(outfile, 'rb') as file: - expected_result = file.read() + image1 = Image.open(srcfile) + image2 = Image.open(trgfile) - result = hexmap.write_sector_pdf_map(galaxy.sectors[secname], is_live=False) - self.assertIsNotNone(result) - # rather than try to mock datetime.now(), patch the output result. - # this also lets us check that there's only a single match - matches = self.timeline.search(result) - self.assertEqual(1, len(matches.groups()), 'Should be exactly one create-date match') - result = self.timeline.sub(oldtime, result) - # likewise patch md5 output - matches = self.md5line.findall(result) - self.assertEqual(2, len(matches), 'Should be exactly two MD5 matches') - result = self.md5line.sub(oldmd5, result) - self.assertEqual(expected_result, result) + array1 = np.array(image1) + array2 = np.array(image2) + + mse = np.mean((array1 - array2) ** 2) + self.assertTrue(0.2 > mse, "Image difference above threshold") def _load_ranges(self, galaxy, sourcefile): with open(sourcefile, "rb") as f: From cbe9862e7c5e83a9245373b73a876760c8c4c7fd Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Sun, 30 Jun 2024 17:40:37 +1000 Subject: [PATCH 30/45] Fix test blast damage --- Tests/Hypothesis/Inputs/testHypothesisStarlineParser.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tests/Hypothesis/Inputs/testHypothesisStarlineParser.py b/Tests/Hypothesis/Inputs/testHypothesisStarlineParser.py index a75e49489..6298fe793 100644 --- a/Tests/Hypothesis/Inputs/testHypothesisStarlineParser.py +++ b/Tests/Hypothesis/Inputs/testHypothesisStarlineParser.py @@ -73,6 +73,7 @@ def comparison_line(draw): '0000 000000000000000 ???????-? 000000000000 (0 - (000-0) [0000] B - A 000 00?', '0000 000000000000000 0000000-0 (00000000000000 B - A 000 ?0)0000000000', '0000 000000000000000 0000000-0 (00000000000000 - (000-0) - B - A 000 0?' + '0000 000000000000000 0000000-0 [00000000000000 - - [0000] - - A 000 00?' ] candidate = draw(from_regex(regex=ParseStarInput.starline, alphabet='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWYXZ -{}()[]?\'+*')) @@ -323,6 +324,7 @@ class testHypothesisStarlineParser(unittest.TestCase): @example('0000 000000000000000 ???????-? 000000000000 (0 - (000-0) [0000] B - A 000 00?', 'weird') @example('0000 000000000000000 0000000-0 (00000000000000 B - A 000 ?0)0000000000', 'weird') @example('0000 000000000000000 0000000-0 (00000000000000 - (000-0) - B - A 000 0?', 'weird') + @example('0000 000000000000000 0000000-0 [00000000000000 - - [0000] - - A 000 00?', 'weird') def test_starline_parser_against_regex(self, s, match): # if it's a known weird-parse case, assume it out now assume(match != 'weird') From a69c955ea1dc89688c8be8c11a829b4c1ba9fcb2 Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Mon, 1 Jul 2024 14:26:16 +1000 Subject: [PATCH 31/45] Wring out coreward/rimward sector names --- PyRoute/Outputs/PDFHexMap.py | 46 +++++++--- .../no_subsectors_named/Ngathksirz empty.sec | 40 +++++++++ .../Ngathksirz Sector.pdf | Bin 0 -> 22532 bytes .../Zao Kfeng Ig Grilokh Sector.pdf | Bin 0 -> 22537 bytes Tests/Outputs/testHexMap.py | 83 ++++++++++++++++++ 5 files changed, 156 insertions(+), 13 deletions(-) create mode 100644 Tests/DeltaFiles/no_subsectors_named/Ngathksirz empty.sec create mode 100644 Tests/OutputFiles/verify_coreward_rimward/Ngathksirz Sector.pdf create mode 100644 Tests/OutputFiles/verify_coreward_rimward/Zao Kfeng Ig Grilokh Sector.pdf diff --git a/PyRoute/Outputs/PDFHexMap.py b/PyRoute/Outputs/PDFHexMap.py index d7e6dde25..5654755ff 100644 --- a/PyRoute/Outputs/PDFHexMap.py +++ b/PyRoute/Outputs/PDFHexMap.py @@ -91,21 +91,41 @@ def sector_name(self, doc: Canvas, name: str): doc.setFont(font_name, font_size, font_leading) def coreward_sector(self, pdf, name): - cursor = PDFCursor(5, self.y_start - 15, True) - def_font = pdf.get_font() - pdf.set_font('times', size=10) - width = pdf.get_font()._string_width(name) / 2 - cursor.x = 306 - width - pdf.add_text(name, cursor) - pdf.set_font(font=def_font) + # Save out whatever font is currently set + font_name = pdf._fontname + font_size = pdf._fontsize + font_leading = pdf._leading + + new_font = 'Times-Roman' + new_size = 10 + pdf.setFont(new_font, size=new_size) + width = pdf.stringWidth(name, new_font, new_size) + + x = 306 - (width/2) + textobject = pdf.beginText(x, self.y_start - 3) + textobject.textOut(name) + textobject.setStrokeColor('black') + pdf.drawText(textobject) + # Restore saved font + pdf.setFont(font_name, font_size, font_leading) def rimward_sector(self, pdf, name): - cursor = PDFCursor(306, 767, True) - def_font = pdf.get_font() - pdf.set_font('times', size=10) - cursor.x_plus(-pdf.get_font()._string_width(name) / 2) - pdf.add_text(name, cursor) - pdf.set_font(font=def_font) + # Save out whatever font is currently set + font_name = pdf._fontname + font_size = pdf._fontsize + font_leading = pdf._leading + + new_font = 'Times-Roman' + new_size = 10 + pdf.setFont(new_font, size=new_size) + width = pdf.stringWidth(name, new_font, new_size) + x = 328 - (width/2) + textobject = pdf.beginText(x, 779) + textobject.textOut(name) + textobject.setStrokeColor('black') + pdf.drawText(textobject) + # Restore saved font + pdf.setFont(font_name, font_size, font_leading) def spinward_sector(self, pdf, name): cursor = PDFCursor(self.x_start - 5, 396, True) diff --git a/Tests/DeltaFiles/no_subsectors_named/Ngathksirz empty.sec b/Tests/DeltaFiles/no_subsectors_named/Ngathksirz empty.sec new file mode 100644 index 000000000..b332f05a5 --- /dev/null +++ b/Tests/DeltaFiles/no_subsectors_named/Ngathksirz empty.sec @@ -0,0 +1,40 @@ +# Generated by https://travellermap.com +# 2023-12-23T00:53:40-08:00 + +# Ngathksirz +# -2,3 + +# Name: Ngathksirz + +# Abbreviation: Ngat + +# Milieu: M1105 + +# Credits: Ngathksirz sector was designed by John G. Wood. Stellar positions were developed by Joe D. Fugate Sr. and appear in The MegaTraveller Alien, Volume 1: Vilani & Vargr (Digest Group Publications, 1990). + +# Author: John G. Wood +# Source: Underdeveloped Sectors +# Ref: https://web.archive.org/web/20160205040546/http://homepage.ntlworld.com/elvwood/Traveller/Sectors/ + +# Subsector A: Odzgoegh +# Subsector B: Kfirzin +# Subsector C: Kueroe +# Subsector D: Aengangvuer +# Subsector E: Dhaendzouts +# Subsector F: Ruevoga +# Subsector G: Aenael +# Subsector H: Roenvoegazdu +# Subsector I: Zarrkoe +# Subsector J: Thafo +# Subsector K: Uengou +# Subsector L: Raez +# Subsector M: Vaerr +# Subsector N: Nouksaezor +# Subsector O: Oegerag +# Subsector P: Serrken + +# Alleg: Va: "Non-Aligned, Vargr-dominated" +# Alleg: Ve: "Empire of Varroerth" + +Hex Name UWP Remarks {Ix} (Ex) [Cx] N B Z PBG W A Stellar +---- ------------------------------ --------- ----------------------- ---- ------- ------ - - - --- -- -- -------------------------- diff --git a/Tests/OutputFiles/verify_coreward_rimward/Ngathksirz Sector.pdf b/Tests/OutputFiles/verify_coreward_rimward/Ngathksirz Sector.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c9778c2c9a9f2e7607a1daecc1e0642a97bd1d9f GIT binary patch literal 22532 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 literal 0 HcmV?d00001 diff --git a/Tests/OutputFiles/verify_coreward_rimward/Zao Kfeng Ig Grilokh Sector.pdf b/Tests/OutputFiles/verify_coreward_rimward/Zao Kfeng Ig Grilokh Sector.pdf new file mode 100644 index 0000000000000000000000000000000000000000..b012dd3023f217726f6ba7c28c6e36369956cfd4 GIT binary patch literal 22537 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 literal 0 HcmV?d00001 diff --git a/Tests/Outputs/testHexMap.py b/Tests/Outputs/testHexMap.py index b6fd3ad4f..00e5b2355 100644 --- a/Tests/Outputs/testHexMap.py +++ b/Tests/Outputs/testHexMap.py @@ -447,6 +447,89 @@ def test_verify_subsector_comm_write_pdf(self): mse = np.mean((array1 - array2) ** 2) self.assertTrue(0.2 > mse, "Image difference above threshold") + def test_verify_coreward_rimward_sector(self): + source1file = self.unpack_filename('DeltaFiles/no_subsectors_named/Zao Kfeng Ig Grilokh empty.sec') + source2file = self.unpack_filename('DeltaFiles/no_subsectors_named/Ngathksirz empty.sec') + + source1pdf = self.unpack_filename('OutputFiles/verify_coreward_rimward/Zao Kfeng Ig Grilokh Sector.pdf') + source2pdf = self.unpack_filename('OutputFiles/verify_coreward_rimward/Ngathksirz Sector.pdf') + + args = self._make_args() + args.interestingline = None + args.interestingtype = None + args.maps = True + args.routes = 'trade' + args.subsectors = False + + delta = DeltaDictionary() + sector = SectorDictionary.load_traveller_map_file(source1file) + delta[sector.name] = sector + sector = SectorDictionary.load_traveller_map_file(source2file) + delta[sector.name] = sector + + galaxy = DeltaGalaxy(args.btn, args.max_jump) + galaxy.read_sectors(delta, args.pop_code, args.ru_calc, + args.route_reuse, args.routes, args.route_btn, args.mp_threads, args.debug_flag) + galaxy.output_path = args.output + + galaxy.generate_routes() + + zaokpath = os.path.abspath(args.output + '/Zao Kfeng Ig Grilokh Sector.pdf') + ngatpath = os.path.abspath(args.output + '/Ngathksirz Sector.pdf') + + hexmap = PDFHexMap(galaxy, 'trade') + + secname = 'Zao Kfeng Ig Grilokh' + hexmap.write_sector_pdf_map(galaxy.sectors[secname], is_live=True) + secname = 'Ngathksirz' + hexmap.write_sector_pdf_map(galaxy.sectors[secname], is_live=True) + + srczaok = os.path.abspath(args.output + '/Zao Kfeng Ig Grilokh Sector original.png') + srcngat = os.path.abspath(args.output + '/Ngathksirz Sector original.png') + trgzaok = os.path.abspath(args.output + '/Zao Kfeng Ig Grilokh Sector remix.png') + trgngat = os.path.abspath(args.output + '/Ngathksirz Sector remix.png') + + src_img = pymupdf.open(source1pdf) + src_iter = src_img.pages(0) + for page in src_iter: + src = page.get_pixmap() + src.save(srczaok) + + src_img = pymupdf.open(source2pdf) + src_iter = src_img.pages(0) + for page in src_iter: + src = page.get_pixmap() + src.save(srcngat) + + trg_img = pymupdf.open(zaokpath) + trg_iter = trg_img.pages(0) + for page in trg_iter: + trg = page.get_pixmap() + trg.save(trgzaok) + + trg_img = pymupdf.open(ngatpath) + trg_iter = trg_img.pages(0) + for page in trg_iter: + trg = page.get_pixmap() + trg.save(trgngat) + + image1 = Image.open(srczaok) + image2 = Image.open(trgzaok) + + array1 = np.array(image1) + array2 = np.array(image2) + + mse = np.mean((array1 - array2) ** 2) + self.assertTrue(0.2 > mse, "Image difference above threshold") + + image1 = Image.open(srcngat) + image2 = Image.open(trgngat) + array1 = np.array(image1) + array2 = np.array(image2) + + mse = np.mean((array1 - array2) ** 2) + self.assertTrue(0.2 > mse, "Image difference above threshold") + def _load_ranges(self, galaxy, sourcefile): with open(sourcefile, "rb") as f: lines = f.readlines() From 1a501d270c1eaad825070be457321527368618f0 Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Mon, 1 Jul 2024 15:53:31 +1000 Subject: [PATCH 32/45] Wring out spinward-trailing sector names --- PyRoute/Outputs/PDFHexMap.py | 56 ++++++++---- .../no_subsectors_named/Knaeleng empty.sec | 40 +++++++++ .../Knaeleng Sector.pdf | Bin 0 -> 22533 bytes .../Zao Kfeng Ig Grilokh Sector.pdf | Bin 0 -> 22544 bytes Tests/Outputs/testHexMap.py | 83 ++++++++++++++++++ 5 files changed, 163 insertions(+), 16 deletions(-) create mode 100644 Tests/DeltaFiles/no_subsectors_named/Knaeleng empty.sec create mode 100644 Tests/OutputFiles/verify_spinward_trailing/Knaeleng Sector.pdf create mode 100644 Tests/OutputFiles/verify_spinward_trailing/Zao Kfeng Ig Grilokh Sector.pdf diff --git a/PyRoute/Outputs/PDFHexMap.py b/PyRoute/Outputs/PDFHexMap.py index 5654755ff..617b52751 100644 --- a/PyRoute/Outputs/PDFHexMap.py +++ b/PyRoute/Outputs/PDFHexMap.py @@ -128,24 +128,48 @@ def rimward_sector(self, pdf, name): pdf.setFont(font_name, font_size, font_leading) def spinward_sector(self, pdf, name): - cursor = PDFCursor(self.x_start - 5, 396, True) - def_font = pdf.get_font() - pdf.set_font('times', size=10) - cursor.y_plus(pdf.get_font()._string_width(name) / 2) - text = PDFText(pdf.session, pdf.page, None, cursor=cursor) - text.text_rotate(90) - text._text(name) - pdf.set_font(font=def_font) + # Save out whatever font is currently set + font_name = pdf._fontname + font_size = pdf._fontsize + font_leading = pdf._leading + + new_font = 'Times-Roman' + new_size = 10 + pdf.setFont(new_font, size=new_size) + width = pdf.stringWidth(name, new_font, new_size) + y = 390 + (width / 2) + x = self.x_start - 5 + + textobject = pdf.beginText(x, y) + textobject.setTextTransform(0, -1, 1, 0, x, y) + textobject.textOut(name) + textobject.setStrokeColor('black') + pdf.drawText(textobject) + + # Restore saved font + pdf.setFont(font_name, font_size, font_leading) def trailing_sector(self, pdf, name): - cursor = PDFCursor(598, 396 - self.y_start, True) - def_font = pdf.get_font() - pdf.set_font('times', size=10) - cursor.y_plus(-(pdf.get_font()._string_width(name) / 2)) - text = PDFText(pdf.session, pdf.page, None, cursor=cursor) - text.text_rotate(-90) - text._text(name) - pdf.set_font(font=def_font) + # Save out whatever font is currently set + font_name = pdf._fontname + font_size = pdf._fontsize + font_leading = pdf._leading + + new_font = 'Times-Roman' + new_size = 10 + pdf.setFont(new_font, size=new_size) + width = pdf.stringWidth(name, new_font, new_size) + y = 421 - (width / 2) + x = 598 + + textobject = pdf.beginText(x, y) + textobject.setTextTransform(0, 1, -1, 0, x, y) + textobject.textOut(name) + textobject.setStrokeColor('black') + pdf.drawText(textobject) + + # Restore saved font + pdf.setFont(font_name, font_size, font_leading) def subsector_grid(self, pdf: Canvas): pdf.setStrokeColorRGB(211/255.0, 211/255.0, 211/255.0) diff --git a/Tests/DeltaFiles/no_subsectors_named/Knaeleng empty.sec b/Tests/DeltaFiles/no_subsectors_named/Knaeleng empty.sec new file mode 100644 index 000000000..0bca1cdc9 --- /dev/null +++ b/Tests/DeltaFiles/no_subsectors_named/Knaeleng empty.sec @@ -0,0 +1,40 @@ +# Generated by https://travellermap.com +# 2023-12-23T00:50:36-08:00 + +# Knaeleng +# -1,4 + +# Name: Knaeleng (va) + +# Abbreviation: Knae + +# Milieu: M1105 + +# Author: John G. Wood +# Source: Underdeveloped Sectors +# Ref: https://web.archive.org/web/20160205040546/http://homepage.ntlworld.com/elvwood/Traveller/Sectors/ + +# Subsector A: +# Subsector B: +# Subsector C: +# Subsector D: +# Subsector E: +# Subsector F: +# Subsector G: +# Subsector H: +# Subsector I: +# Subsector J: +# Subsector K: +# Subsector L: +# Subsector M: +# Subsector N: +# Subsector O: +# Subsector P: + +# Alleg: Va: "Non-Aligned, Vargr-dominated" +# Alleg: VA: "Alliance of Tju" +# Alleg: Ve: "Empire of Varroerth" +# Alleg: VK: "Koenotz Empire" + +Hex Name UWP Remarks {Ix} (Ex) [Cx] N B Z PBG W A Stellar +---- -------------------- --------- -------------------- ---- ---- ---- - -- - --- - -- ------------------------ diff --git a/Tests/OutputFiles/verify_spinward_trailing/Knaeleng Sector.pdf b/Tests/OutputFiles/verify_spinward_trailing/Knaeleng Sector.pdf new file mode 100644 index 0000000000000000000000000000000000000000..69cd51a24ee7999885b651db538a51bc393c0da8 GIT binary patch literal 22533 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 literal 0 HcmV?d00001 diff --git a/Tests/Outputs/testHexMap.py b/Tests/Outputs/testHexMap.py index 00e5b2355..6d8e023f1 100644 --- a/Tests/Outputs/testHexMap.py +++ b/Tests/Outputs/testHexMap.py @@ -530,6 +530,89 @@ def test_verify_coreward_rimward_sector(self): mse = np.mean((array1 - array2) ** 2) self.assertTrue(0.2 > mse, "Image difference above threshold") + def test_verify_spinward_trailing_sector(self): + source1file = self.unpack_filename('DeltaFiles/no_subsectors_named/Zao Kfeng Ig Grilokh empty.sec') + source2file = self.unpack_filename('DeltaFiles/no_subsectors_named/Knaeleng empty.sec') + + source1pdf = self.unpack_filename('OutputFiles/verify_spinward_trailing/Zao Kfeng Ig Grilokh Sector.pdf') + source2pdf = self.unpack_filename('OutputFiles/verify_spinward_trailing/Knaeleng Sector.pdf') + + args = self._make_args() + args.interestingline = None + args.interestingtype = None + args.maps = True + args.routes = 'trade' + args.subsectors = False + + delta = DeltaDictionary() + sector = SectorDictionary.load_traveller_map_file(source1file) + delta[sector.name] = sector + sector = SectorDictionary.load_traveller_map_file(source2file) + delta[sector.name] = sector + + galaxy = DeltaGalaxy(args.btn, args.max_jump) + galaxy.read_sectors(delta, args.pop_code, args.ru_calc, + args.route_reuse, args.routes, args.route_btn, args.mp_threads, args.debug_flag) + galaxy.output_path = args.output + + galaxy.generate_routes() + + zaokpath = os.path.abspath(args.output + '/Zao Kfeng Ig Grilokh Sector.pdf') + ngatpath = os.path.abspath(args.output + '/Knaeleng Sector.pdf') + + hexmap = PDFHexMap(galaxy, 'trade') + + secname = 'Zao Kfeng Ig Grilokh' + hexmap.write_sector_pdf_map(galaxy.sectors[secname], is_live=True) + secname = 'Knaeleng' + hexmap.write_sector_pdf_map(galaxy.sectors[secname], is_live=True) + + srczaok = os.path.abspath(args.output + '/Zao Kfeng Ig Grilokh Sector original.png') + srcngat = os.path.abspath(args.output + '/Knaeleng Sector original.png') + trgzaok = os.path.abspath(args.output + '/Zao Kfeng Ig Grilokh Sector remix.png') + trgngat = os.path.abspath(args.output + '/Knaeleng Sector remix.png') + + src_img = pymupdf.open(source1pdf) + src_iter = src_img.pages(0) + for page in src_iter: + src = page.get_pixmap() + src.save(srczaok) + + src_img = pymupdf.open(source2pdf) + src_iter = src_img.pages(0) + for page in src_iter: + src = page.get_pixmap() + src.save(srcngat) + + trg_img = pymupdf.open(zaokpath) + trg_iter = trg_img.pages(0) + for page in trg_iter: + trg = page.get_pixmap() + trg.save(trgzaok) + + trg_img = pymupdf.open(ngatpath) + trg_iter = trg_img.pages(0) + for page in trg_iter: + trg = page.get_pixmap() + trg.save(trgngat) + + image1 = Image.open(srczaok) + image2 = Image.open(trgzaok) + + array1 = np.array(image1) + array2 = np.array(image2) + + mse = np.mean((array1 - array2) ** 2) + self.assertTrue(0.2 > mse, "Image difference above threshold") + + image1 = Image.open(srcngat) + image2 = Image.open(trgngat) + array1 = np.array(image1) + array2 = np.array(image2) + + mse = np.mean((array1 - array2) ** 2) + self.assertTrue(0.2 > mse, "Image difference above threshold") + def _load_ranges(self, galaxy, sourcefile): with open(sourcefile, "rb") as f: lines = f.readlines() From 19b4844475b4f8e81555b3a887b6174a5e9c0892 Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Tue, 2 Jul 2024 15:59:03 +1000 Subject: [PATCH 33/45] Wring out xboat map write --- PyRoute/Outputs/PDFHexMap.py | 45 ++++++++++------ .../Zarushagar Sector.pdf | Bin 0 -> 24663 bytes Tests/Outputs/testHexMap.py | 51 ++++++++++++++++++ 3 files changed, 81 insertions(+), 15 deletions(-) create mode 100644 Tests/OutputFiles/verify_subsector_xroute_write/Zarushagar Sector.pdf diff --git a/PyRoute/Outputs/PDFHexMap.py b/PyRoute/Outputs/PDFHexMap.py index 617b52751..f90857a61 100644 --- a/PyRoute/Outputs/PDFHexMap.py +++ b/PyRoute/Outputs/PDFHexMap.py @@ -18,7 +18,8 @@ class PDFHexMap(Map): - colourmap = {'gray': (128, 128, 128), 'salmon': (255, 140, 105)} + colourmap = {'gray': (128, 128, 128), 'salmon': (255, 140, 105), 'goldenrod': (218, 165, 32), + 'crimson': (220, 20, 60)} def __init__(self, galaxy, routes, min_btn=8): super(PDFHexMap, self).__init__(galaxy, routes) @@ -171,6 +172,20 @@ def trailing_sector(self, pdf, name): # Restore saved font pdf.setFont(font_name, font_size, font_leading) + def zone(self, doc, star, point): + offset = 3 + point[0] += self.xm + offset + point[1] += self.xm + offset + #point.x_plus(self.xm) + #point.y_plus(self.ym) + + if star.zone in ['R', 'F']: + self.add_circle(doc, point, self.xm, 'crimson') + elif star.zone in ['A', 'U']: + self.add_circle(doc, point, self.xm, 'goldenrod') + else: # no zone -> do nothing + return + def subsector_grid(self, pdf: Canvas): pdf.setStrokeColorRGB(211/255.0, 211/255.0, 211/255.0) #vlineStart = PDFCursor(0, self.y_start + self.xm) @@ -378,8 +393,9 @@ def system(self, pdf, star): else: row = (self.y_start - self.ym) + (star.row * self.ym * 2) - point = PDFCursor(col, row) - self.zone(pdf, star, point.copy()) + #point = PDFCursor(col, row) + point = [col, row] + self.zone(pdf, star, point) rawpoint = [col, row] #width = self.string_width(pdf.get_font(), str(star.uwp)) @@ -498,16 +514,13 @@ def trade_line(self, pdf, edge, data): def comm_line(self, pdf, edge): start = edge[0] end = edge[1] - color = pdf.get_color() - color.set_color_by_number(102, 178, 102) - endx, endy, startx, starty = self._get_line_endpoints(end, start) + pdf.setStrokeColorRGB(102 / 255.0, 178 / 255.0, 102 / 255.0) + pdf.setLineWidth(3) - lineStart = PDFCursor(startx, starty) - lineEnd = PDFCursor(endx, endy) + endx, endy, startx, starty = self._get_line_endpoints(end, start) - line = PDFLine(pdf.session, pdf.page, lineStart, lineEnd, stroke='solid', color=color, size=3) - line._draw() + pdf.line(startx, starty, endx, endy) def _get_line_endpoints(self, end, start): starty = self.y_start + (self.ym * 2 * start.row) - (self.ym * (1 if start.col & 1 else 0)) @@ -581,11 +594,13 @@ def add_line(self, pdf, start, end, colorname): pdf.add_line(cursor1=start, cursor2=end) def add_circle(self, pdf, center, radius, colorname): - color = pdf.get_color() - color.set_color_by_name(colorname) - radius = PDFCursor(radius, radius) - circle = PDFEllipse(pdf.session, pdf.page, center, radius, color, size=2) - circle._draw() + colour = PDFHexMap.colourmap[colorname] + pdf.setStrokeColorRGB(colour[0] / 255.0, colour[1] / 255.0, colour[2] / 255.0) + pdf.setLineWidth(2) + + startx = center[0] + starty = center[1] + pdf.ellipse(startx - radius, starty - radius, startx + radius, starty + radius, fill=0) def get_line(self, doc, start, end, colorname, width): """ diff --git a/Tests/OutputFiles/verify_subsector_xroute_write/Zarushagar Sector.pdf b/Tests/OutputFiles/verify_subsector_xroute_write/Zarushagar Sector.pdf new file mode 100644 index 0000000000000000000000000000000000000000..930d3804bd8cf79dc39852f332f9b0ab555480eb GIT binary patch literal 24663 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 literal 0 HcmV?d00001 diff --git a/Tests/Outputs/testHexMap.py b/Tests/Outputs/testHexMap.py index 6d8e023f1..90d3874eb 100644 --- a/Tests/Outputs/testHexMap.py +++ b/Tests/Outputs/testHexMap.py @@ -613,6 +613,57 @@ def test_verify_spinward_trailing_sector(self): mse = np.mean((array1 - array2) ** 2) self.assertTrue(0.2 > mse, "Image difference above threshold") + def test_verify_xboat_write_pdf(self): + sourcefile = self.unpack_filename('DeltaFiles/Zarushagar-Ibara.sec') + srcpdf = self.unpack_filename( + 'OutputFiles/verify_subsector_xroute_write/Zarushagar Sector.pdf') + + args = self._make_args() + args.interestingline = None + args.interestingtype = None + args.maps = True + args.routes = 'xroute' + args.subsectors = False + + delta = DeltaDictionary() + sector = SectorDictionary.load_traveller_map_file(sourcefile) + delta[sector.name] = sector + + galaxy = DeltaGalaxy(args.btn, args.max_jump) + galaxy.read_sectors(delta, args.pop_code, args.ru_calc, + args.route_reuse, args.routes, args.route_btn, args.mp_threads, args.debug_flag) + galaxy.output_path = args.output + + galaxy.generate_routes() + + secname = 'Zarushagar' + + hexmap = PDFHexMap(galaxy, 'xroute') + + targpath = os.path.abspath(args.output + '/Zarushagar Sector.pdf') + result = hexmap.write_sector_pdf_map(galaxy.sectors[secname], is_live=True) + src_img = pymupdf.open(srcpdf) + src_iter = src_img.pages(0) + for page in src_iter: + src = page.get_pixmap() + srcfile = os.path.abspath(args.output + '/Zarushagar Sector original.png') + src.save(srcfile) + trg_img = pymupdf.open(targpath) + trg_iter = trg_img.pages(0) + for page in trg_iter: + trg = page.get_pixmap() + trgfile = os.path.abspath(args.output + '/Zarushagar Sector remix.png') + trg.save(trgfile) + + image1 = Image.open(srcfile) + image2 = Image.open(trgfile) + + array1 = np.array(image1) + array2 = np.array(image2) + + mse = np.mean((array1 - array2) ** 2) + self.assertTrue(0.2 > mse, "Image difference " + str(mse) + " above threshold") + def _load_ranges(self, galaxy, sourcefile): with open(sourcefile, "rb") as f: lines = f.readlines() From 0ec8b7865c7948a9f3be51813651524d228a52ef Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Wed, 3 Jul 2024 17:22:46 +1000 Subject: [PATCH 34/45] Delegate map writing to {PDF,}HexMap --- PyRoute/Outputs/Map.py | 39 +++------------------------------------ 1 file changed, 3 insertions(+), 36 deletions(-) diff --git a/PyRoute/Outputs/Map.py b/PyRoute/Outputs/Map.py index fbde3d38c..c4e5f7ab3 100644 --- a/PyRoute/Outputs/Map.py +++ b/PyRoute/Outputs/Map.py @@ -99,42 +99,9 @@ def write_maps(self): Starting point for writing PDF files. Call this to output the trade maps """ - logging.getLogger("PyRoute.Map").info("writing {:d} sector maps...".format(len(self.galaxy.sectors))) - for sector in self.galaxy.sectors.values(): - doc = self.document(sector) - self.write_base_map(doc, sector) - - self.draw_borders(doc, sector) - - sector_trade = [star for star in self.galaxy.stars.edges(sector.worlds, True) - if star[2]['trade'] > 0 and StatCalculation.trade_to_btn(star[2]['trade']) >= self.min_btn] - - logging.getLogger('PyRoute.Map').debug("Worlds with trade: {}".format(len(sector_trade))) - - sector_trade.sort(key=lambda line: line[2]['trade']) - - for (star, neighbor, data) in sector_trade: - self.galaxy.stars[star][neighbor]['trade btn'] = StatCalculation.trade_to_btn(data['trade']) - self.trade_line(doc, [star, neighbor], data) - - # Get all the worlds in this sector - # for (star, neighbor, data) in self.galaxy.stars.edges(sector.worlds, True): - # if star.sector != sector: - # continue# - # if data['trade'] > 0 and self.trade_to_btn(data['trade']) >= self.min_btn: - # self.galaxy.stars[star][neighbor]['trade btn'] = self.trade_to_btn(data['trade']) - # self.trade_line(doc, [star, neighbor], data) - # elif star.sector != neighbor.sector: - # data = self.galaxy.stars.get_edge_data(neighbor, star) - # if data is not None and \ - # data['trade'] > 0 and \ - # self.trade_to_btn(data['trade']) >= self.min_btn: - # self.trade_line(doc, [star, neighbor], data) - - for star in sector.worlds: - self.place_system(doc, star) - - self.close() + logging.getLogger("PyRoute.HexMap").info("writing {:d} sector maps...".format(len(self.galaxy.sectors))) + for gal_sector in self.galaxy.sectors.values(): + self.write_sector_pdf_map(gal_sector) def write_base_map(self, doc, sector): self.sector_name(doc, sector.name) From 430624190a77b919c88e9784ffd5542b2903ca2b Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Wed, 3 Jul 2024 20:05:14 +1000 Subject: [PATCH 35/45] Set up test for cross-sector-border trade links --- .../quadripoint_trade_write/Corridor.sec | 75 +++++++++++++++++ .../quadripoint_trade_write/Deneb.sec | 65 +++++++++++++++ .../quadripoint_trade_write/Provence.sec | 75 +++++++++++++++++ .../quadripoint_trade_write/Tuglikki.sec | 78 ++++++++++++++++++ .../Corridor Sector.pdf | Bin 0 -> 25495 bytes .../Deneb Sector.pdf | Bin 0 -> 25035 bytes .../Provence Sector.pdf | Bin 0 -> 25429 bytes .../Tuglikki Sector.pdf | Bin 0 -> 25097 bytes Tests/Outputs/testHexMap.py | 71 ++++++++++++++++ 9 files changed, 364 insertions(+) create mode 100644 Tests/DeltaFiles/quadripoint_trade_write/Corridor.sec create mode 100644 Tests/DeltaFiles/quadripoint_trade_write/Deneb.sec create mode 100644 Tests/DeltaFiles/quadripoint_trade_write/Provence.sec create mode 100644 Tests/DeltaFiles/quadripoint_trade_write/Tuglikki.sec create mode 100644 Tests/OutputFiles/verify_quadripoint_trade_write/Corridor Sector.pdf create mode 100644 Tests/OutputFiles/verify_quadripoint_trade_write/Deneb Sector.pdf create mode 100644 Tests/OutputFiles/verify_quadripoint_trade_write/Provence Sector.pdf create mode 100644 Tests/OutputFiles/verify_quadripoint_trade_write/Tuglikki Sector.pdf diff --git a/Tests/DeltaFiles/quadripoint_trade_write/Corridor.sec b/Tests/DeltaFiles/quadripoint_trade_write/Corridor.sec new file mode 100644 index 000000000..f52c7d4f0 --- /dev/null +++ b/Tests/DeltaFiles/quadripoint_trade_write/Corridor.sec @@ -0,0 +1,75 @@ +# Generated by https://travellermap.com +# 2023-12-23T00:42:38-08:00 + +# Corridor +# -2,1 + +# Name: Corridor +# Name: Amshagi (vi) +# Name: Eneri (vi) +# Name: Roughantz (va) +# Name: Llananae Tourz (va) + +# Abbreviation: Corr + +# Milieu: M1105 + +# Credits: Corridor sector was designed by Marc W. Miller and appears in Atlas of the Imperium (GDW, 1984). It was refined by David Riddell. Portions appear in The Travellers Digest. + +# Source: Traveller 5 Second Survey + +# Subsector A: Khouth +# Subsector B: Khukish +# Subsector C: Lemish +# Subsector D: The Narrows +# Subsector E: Ian +# Subsector F: Strand +# Subsector G: Naadi +# Subsector H: Uantil +# Subsector I: Shush +# Subsector J: The Empty Void +# Subsector K: Atu'l +# Subsector L: Kivu +# Subsector M: Two Worlds +# Subsector N: Ashishinipar +# Subsector O: Sinta +# Subsector P: Sashrakusha + +# Alleg: CsIm: "Client state, Third Imperium" +# Alleg: ImDv: "Third Imperium, Domain of Vland" +# Alleg: ImLc: "Third Imperium, Lancian Cultural Region" +# Alleg: NaHu: "Non-Aligned, Human-dominated" +# Alleg: NaVa: "Non-Aligned, Vargr-dominated" + +Hex Name UWP Remarks {Ix} (Ex) [Cx] N B Z PBG W A Stellar +---- -------------------------- --------- -------------------------- ------ ------- ------ ----- -- - --- -- ---- -------------- +0102 Tersta C574522-7 Ag Ni An { -1 } (743-5) [1413] BC - - 320 11 ImDv F3 V +0104 Khouth A8C3999-D Fl Hi In Cp { 4 } (D8G+5) [AD6E] BEF - - 420 13 ImDv M3 V +0105 Ankirst C556112-9 Lo { -1 } (600-5) [1115] B - - 421 11 ImDv K3 V M1 V +0106 Ofo-Nebus B541488-9 He Ni Po Varg1 { 0 } (733+1) [4459] B N - 701 11 ImDv M0 V M1 V +0109 Synez E864256-7 Lo { -3 } (410-4) [1146] B - - 620 9 ImDv M2 V +0205 Koergfoes B54359A-B Ni Po VargW RsE { 1 } (B45+3) [767D] B N - 322 7 ImDv M1 V +0206 Mikesh C8B7ACB-D Fl Hi In Pz { 3 } (K9F+5) [CD7F] BE S A 625 10 ImDv K1 V +0209 Pergzitt B625354-B Lo { 1 } (721-1) [1439] B - - 311 11 ImDv K5 V M6 V +0210 Dry C310877-A Na Ph Pi { 1 } (B7A+1) [895A] BDe S - 801 6 ImDv M1 V M9 V +0301 Overlook E5758B7-5 Pa Ph Pi { -2 } (A75-2) [8655] BcDe - - 601 12 ImDv M0 V +0304 Serk B89A866-C Wa Ph Pi Mr { 2 } (F7C+1) [7A4B] BDe N - 723 8 ImDv M2 V +0306 Hesarus B553556-9 Ni Po { 0 } (944-1) [4548] B N - 320 8 ImDv M1 V +0307 Caulins Belt A000268-C As Lo Va Mr { 1 } (611+1) [235C] B N - 911 9 ImDv M5 V +0308 Faraway D682546-4 Ni Pr { -3 } (741-4) [4243] Bc S - 713 7 ImDv M1 V +0401 Auritaurus C859344-8 Lo { -2 } (820-4) [1136] B S - 621 12 ImDv F9 V +0402 Rrev Rigr B100657-C Na Ni Va Varg6 { 1 } (955+1) [675C] B - - 610 11 ImDv M7 V +0407 Sigma 7 C000795-7 As Na Va Pi { -1 } (967-3) [5635] BD - - 112 16 ImDv A9 V M9 V +0502 Koppel E563305-7 Lo { -3 } (520-5) [1135] B - - 334 10 ImDv K9 V +0509 Naxx-Iygo C7B5464-8 Fl Ni O:0910 { -2 } (931-4) [2236] B - - 712 10 ImDv M8 V +0510 Pamock C561542-9 Ni Pr { -1 } (C43-5) [1415] Bc - - 623 11 ImDv K8 V M2 V +0601 Greenrok A560236-C De Lo { 1 } (511+1) [134B] B - - 501 14 ImDv M2 V M5 V +0603 Taratun D893443-6 Ni { -3 } (631-5) [1123] B S - 323 12 ImDv M5 V +0605 Aka Gee B432579-B Ni Po { 1 } (A45+2) [666C] B - - 103 13 ImDv K3 V M0 V +0606 Degarla A611644-A Ic Na Ni { 1 } (955-1) [4738] B N - 310 12 ImDv K8 V +0608 Desolate B9B4502-A Fl Ni { 2 } (946-2) [1716] B W - 420 8 ImDv G5 V +0609 Semiplast C561662-8 Ni Ri O:0910 { -1 } (A53-5) [2514] BC - - 502 9 ImDv M1 V M7 V +0701 Gzorraeth A590410-9 De He Ni { 0 } (733-4) [1414] - - - 501 11 NaVa K6 V +0709 Mowanda B200534-A Ni Va { 2 } (946+1) [3738] B NS - 411 11 ImDv A9 V +0804 Tsetsurri D435775-6 { -2 } (965-4) [5534] - - - 523 9 NaVa F4 V +0807 Bersha E552302-5 Lo Po { -3 } (520-5) [1111] B - - 902 10 ImDv K2 V M0 V \ No newline at end of file diff --git a/Tests/DeltaFiles/quadripoint_trade_write/Deneb.sec b/Tests/DeltaFiles/quadripoint_trade_write/Deneb.sec new file mode 100644 index 000000000..e71c827af --- /dev/null +++ b/Tests/DeltaFiles/quadripoint_trade_write/Deneb.sec @@ -0,0 +1,65 @@ +# Generated by https://travellermap.com +# 2023-12-23T00:43:40-08:00 + +# Deneb +# -3,1 + +# Name: Deneb +# Name: Nieklsdia (zh) + +# Abbreviation: Dene + +# Milieu: M1105 + +# Credits: Deneb sector was designed by Marc W. Miller and appears in Atlas of the Imperium (GDW, 1984). It was refined by James Holden. Portions appear in The Travellers Digest and The MegaTraveller Journal #3 (Digest Group Publications, 1992). + +# Source: Traveller 5 Second Survey + +# Subsector A: Pretoria +# Subsector B: Lamas +# Subsector C: Antra +# Subsector D: Million +# Subsector E: Sabine +# Subsector F: Inar +# Subsector G: Dunmag +# Subsector H: Atsah +# Subsector I: Star Lane +# Subsector J: Vincennes +# Subsector K: Usani +# Subsector L: Geniishir +# Subsector M: Gulf +# Subsector N: Zeng +# Subsector O: Kamlar +# Subsector P: Vast Heavens + +# Alleg: CsIm: "Client state, Third Imperium" +# Alleg: ImDd: "Third Imperium, Domain of Deneb" +# Alleg: NaHu: "Non-Aligned, Human-dominated" +# Alleg: NaVa: "Non-Aligned, Vargr-dominated" +# Alleg: VAug: "United Followers of Augurgh" +# Alleg: VDzF: "Dzarrgh Federate" + +Hex Name UWP Remarks {Ix} (Ex) [Cx] N B Z PBG W A Stellar +---- -------------------- --------- -------------------------------- ------ ------- ------ ---- -- - --- -- ---- -------------- +2502 Tubb C89A799-9 Wa Pi { 0 } (A69+1) [876A] - - - 701 9 NaHu G7 V M6 V +2506 Moskene E200101-A Lo Va { -1 } (600-5) [1116] B - - 603 13 ImDd F5 V G7 V +2603 Unekh C20088B-7 Na Va Ph Pi { -1 } (A77+1) [A779] - - - 502 10 VAug M1 V +2702 Fosfog A57559C-9 Ag Ni Da { 1 } (C45+4) [868C] - - A 114 14 VAug G5 IV M3 V +2704 Talon C543976-A Hi In Po (Souggvuez)8 Varg2 { 4 } (H8E+3) [8D49] - CK - 224 18 VAug K4 V +2802 495-524 D785300-4 Lo Ga An { -3 } (520-5) [1111] - - - 422 9 NaHu F3 V M6 V +2804 Akkip C5426B7-8 He Ni Po { -2 } (C52-2) [6458] - - - 522 16 NaHu F8 V +2807 Gabrael A573646-C Ni Varg3 { 1 } (E55+1) [574B] B - - 424 13 ImDd K4 V +2810 Tensas C556550-6 Ag Ni { -1 } (743-5) [1411] BC - - 504 10 ImDd K8 V +2903 Metare A42465A-E Ni { 1 } (955+3) [877G] - - - 801 9 NaHu M2 V M8 V +2904 Fel A7A7898-A Fl Ph { 2 } (E7B+2) [8A5A] - - - 622 10 VAug M3 V +2905 Ikhaba C675512-8 Ag Ni Da { -1 } (D43-5) [1414] BC - A 933 10 ImDd M4 V +2906 Nurara C5A5422-A Fl Ni { 0 } (A33-4) [1416] B S - 604 9 ImDd M0 V +2907 Hammand E736132-6 Lo Da { -3 } (300-5) [1112] B - A 120 13 ImDd M2 V M9 V +2908 Wyn C546444-5 Ni Pa { -2 } (631-4) [2233] Bc - - 701 12 ImDd M2 V +2910 Crozat C410424-7 Ni { -2 } (631-4) [2235] B - - 404 12 ImDd M4 V M6 V +3004 Oluk Dzas B563521-7 Ni Pr { -1 } (743-5) [1413] - - - 814 14 VAug K8 V +3005 Arleshanu A562333-D Lo { 1 } (721-2) [142A] B N - 420 13 ImDd F4 V +3008 Taproban B8A8787-B Fl Pz { 2 } (B6C+2) [795B] B N A 720 15 ImDd M2 V +3104 Aerfor A584520-D Ag Ni Pr RsD { 2 } (E46-2) [1718] - - - 534 15 CsIm F7 V +3110 Olokono E6A5211-8 Fl Lo { -3 } (510-5) [1114] B - - 801 14 ImDd M0 V +3201 Goukhsar C563224-5 Lo { -2 } (410-4) [1133] - - - 310 11 VDzF K6 V \ No newline at end of file diff --git a/Tests/DeltaFiles/quadripoint_trade_write/Provence.sec b/Tests/DeltaFiles/quadripoint_trade_write/Provence.sec new file mode 100644 index 000000000..c86e3c849 --- /dev/null +++ b/Tests/DeltaFiles/quadripoint_trade_write/Provence.sec @@ -0,0 +1,75 @@ +# Generated by https://travellermap.com +# 2023-12-23T00:55:02-08:00 + +# Provence +# -2,2 + +# Name: Provence +# Name: Orraenang (va) +# Name: Amshagi (vi) + +# Abbreviation: Prov + +# Milieu: M1105 + +# Credits: Provence sector was designed by David Drazul. + +# Author: David Drazul +# Source: Traveller 5 Second Survey + +# Subsector A: Llaezgaen +# Subsector B: Vorvoun +# Subsector C: Grnouf +# Subsector D: Tsaegag +# Subsector E: Anghikh +# Subsector F: Kaegrogz +# Subsector G: Gveghz +# Subsector H: Thon +# Subsector I: Dzarrvaer +# Subsector J: Aenkuk +# Subsector K: Karrdzae +# Subsector L: Kaerfoz +# Subsector M: Lighoz +# Subsector N: Voudzeur +# Subsector O: Uekhourg +# Subsector P: Daekvagul + +# Alleg: NaVa: "Non-Aligned, Vargr-dominated" +# Alleg: VDzF: "Dzarrgh Federate" +# Alleg: VGoT: "Glory of Taarskoerzn" +# Alleg: VIrM: "Irrgh Manifest" +# Alleg: VJoF: "Jihad of Faarzgaen" +# Alleg: VLIn: "Llaeghskath Interacterate" +# Alleg: VLPr: "Lair Protectorate" +# Alleg: VVar: "Empire of Varroerth" + +Hex Name UWP Remarks {Ix} (Ex) [Cx] N B Z PBG W A Stellar +---- -------------------- --------- ---------------------- ------ ------- ------ - -- - --- -- ---- ------------ +0131 Dzaegkaek D422115-8 He Lo Po { -3 } (600-5) [1136] - - - 321 11 VDzF G0 V M6 V +0132 Uevoto B583779-A Ri { 3 } (A6C+4) [8A6B] - K - 201 12 VDzF K4 V M4 V +0137 Anthonkfoers C300423-9 Ni Va { -1 } (B32-4) [1326] - C - 823 11 VDzF M1 V +0140 Aenggvug B000489-B As Ni Va { 1 } (A34+2) [556C] - K - 213 9 VDzF M0 V +0232 Kuronraer B556789-A Ag { 3 } (B6C+4) [8A6B] - K - 502 7 VDzF G1 V +0237 Gingrue C50249A-9 Ic Ni Va { -1 } (932+1) [637B] - C - 421 12 VDzF F9 V +0238 Lloezoers BAC3312-B Fl Lo { 1 } (821-3) [1417] - K - 503 11 VDzF F4 V +0240 Aeghzisung A200776-C Na Va Pi { 2 } (D6C+1) [694B] - - - 422 10 VDzF F2 II M2 V +0331 Errkous C557242-6 Lo { -2 } (410-5) [1112] - C - 105 17 VDzF G4 V D +0335 Lighoz A555976-C Hi { 3 } (D8E+2) [8C4B] - C - 702 9 VDzF G4 V +0432 Dzirrsrue C9B3233-A Fl Lo { 0 } (710-3) [1227] - K - 903 11 VDzF F0 V +0532 Ugzugu D773572-5 Ni { -3 } (741-5) [1211] - - - 424 11 VDzF F8 V +0533 Kaghoekne C799333-9 Lo { -1 } (A20-4) [1226] - - - 514 12 VDzF F2 III M4 V +0537 Ug Kanek B66899E-9 Hi Pr Pz { 2 } (G8C+5) [DB9D] - C A 823 12 VIrM G5 V M3 V +0631 Aenloegin C574572-9 Ag Ni { 0 } (C44-4) [1515] - - - 114 12 VDzF G0 V +0632 Thueni B574346-8 Lo { -1 } (820-2) [2247] - M - 103 15 VDzF F7 V +0634 Arerzthoe A565A76-B Hi { 3 } (F9E+2) [9D4A] - K - 503 8 VIrM F4 V +0635 Uedtzon B434524-D Ni { 1 } (845-1) [363B] - - - 701 7 VIrM K2 V D +0637 Kalongourr C78A977-A Hi Wa Pr { 2 } (G8C+2) [9B5A] - C - 923 13 VIrM F6 IV +0733 Okfaeng A75577B-A Ag Ga { 3 } (A6C+5) [9A7C] - - - 101 10 VDzF F2 V D +0735 Ongvoerrg B525672-B Ni { 1 } (C55-3) [2717] - K - 904 12 VIrM M3 V +0736 Irr Zouth B579678-A Ni { 1 } (B55+1) [675A] - K - 703 15 VIrM G4 V +0737 Toulingitsa B9A4332-B Fl Lo { 1 } (621-3) [1417] - - - 201 8 VIrM M0 V +0739 Duethrang A788621-B Ag Ni Ri { 3 } (C57-1) [2917] - - - 604 13 VIrM G4 V +0740 Kukhou B536555-B Ni { 1 } (A45-1) [3639] - K - 603 11 VIrM M0 V +0831 Kirg A98A733-D Ri Wa { 3 } (D6E+1) [4A2A] - K - 622 14 VDzF K7 V +0837 Tarrthek C672676-8 He Ni { -2 } (B52-3) [5447] - CM - 812 14 VIrM F9 V M1 V +0838 Orsaethvok B737405-C Ni { 1 } (B34-1) [253A] - K - 123 13 VIrM F7 V M5 V \ No newline at end of file diff --git a/Tests/DeltaFiles/quadripoint_trade_write/Tuglikki.sec b/Tests/DeltaFiles/quadripoint_trade_write/Tuglikki.sec new file mode 100644 index 000000000..c76cd9c54 --- /dev/null +++ b/Tests/DeltaFiles/quadripoint_trade_write/Tuglikki.sec @@ -0,0 +1,78 @@ +# Generated by https://travellermap.com +# 2023-12-23T00:58:47-08:00 + +# Tuglikki +# -3,2 + +# Name: Tuglikki (va) +# Name: Dravr (zh) + +# Abbreviation: Tugl + +# Milieu: M1105 + +# Credits: Tuglikki sector was designed by David Drazul, with further refinements by Robert Eaglestone. + +# Author: David Drazul +# Source: Traveller 5 Second Survey + +# Subsector A: Dokh +# Subsector B: Saerrgh +# Subsector C: Orrgueg +# Subsector D: Knoukhs +# Subsector E: Darrgouts +# Subsector F: Uerrul +# Subsector G: Tsaerlon +# Subsector H: Gaerith +# Subsector I: Kaeng +# Subsector J: Ludzghaeth +# Subsector K: Aenkazarr +# Subsector L: Onrakae +# Subsector M: Anfharsgzo +# Subsector N: Ogaeka +# Subsector O: Ourrvikh +# Subsector P: Aegaek + +# Alleg: NaVa: "Non-Aligned, Vargr-dominated" +# Alleg: VAkh: "Akhstuti" +# Alleg: VAug: "United Followers of Augurgh" +# Alleg: VBkA: "Bakne Alliance" +# Alleg: VDzF: "Dzarrgh Federate" +# Alleg: VKfu: "Kfue" +# Alleg: VLIn: "Llaeghskath Interacterate" +# Alleg: VNoe: "Noefa" +# Alleg: VOuz: "Ouzvothon" +# Alleg: VRrS: "Rranglloez Stronghold" +# Alleg: VSEq: "Society of Equals" +# Alleg: VThE: "Thoengling Empire" +# Alleg: VVar: "Empire of Varroerth" +# Alleg: VWan: "People of Wanz" +# Alleg: VWP2: "Windhorn Pact of Two" + +Hex Name UWP Remarks {Ix} (Ex) [Cx] N B Z PBG W A Stellar +---- -------------------- --------- -------------------- ------ ------- ------ - -- - --- -- ---- ----------------- +2534 Ik Oenguek C200663-7 Na Ni Va O:2335 { -2 } (852-5) [3424] - K - 604 15 VWP2 G9 II +2536 Agzknagh B897897-A Pa Ph Pi { 2 } (F7B+2) [8A5A] - K - 214 13 VWP2 F4 V +2537 Aes Dzonnaek C7C177C-9 Fl He { 0 } (A69+3) [A78C] - - - 101 12 VWP2 F0 IV M9 V +2540 Zegzaen C5646A5-8 Ag Ni Ri { 0 } (C54-2) [4636] - K - 722 13 NaVa F6 V +2633 Uel Thungllas E76A8A9-7 Ri Wa Ph { -1 } (A77+1) [9768] - - - 814 18 VWP2 G3 V D +2637 Aellzirtath C758762-4 Ag O:2537 { 0 } (966-4) [3711] - - - 401 14 VWP2 G8 V +2732 Ae Uefa A593356-D Lo { 1 } (921+1) [244C] - C - 522 17 VWP2 M2 V D +2733 Igh Daeg B586322-B Lo { 1 } (921-3) [1417] - - - 504 14 VWP2 F2 V +2735 Okh Knillal A787432-B Ni Ga Pa { 1 } (B34-3) [1517] - C - 805 18 VWP2 G4 V +2737 Thaengzar D653445-5 Ni Po { -3 } (630-5) [2133] - - - 923 17 VWP2 G9 V +2832 Aes Zoukhung B9A46A5-9 Fl Ni { 0 } (A54-2) [4637] - - - 502 14 VWP2 K9 V M7 V +2839 Tuen B5416A6-A He Ni Po { 1 } (B55+1) [5749] - - - 503 12 VAug F4 V +2840 An Logh C435576-A Ni { 0 } (844-1) [4549] - C - 101 11 VAug K9 V M8 V +2937 Okhthalloe B65278C-A Po { 2 } (C6B+5) [A98D] - K - 503 12 VAug F8 V +2938 Rusafaks B547585-B Ag Ni { 2 } (B46+1) [3739] - K - 404 14 VAug K2 V +3031 Knoe C000200-A As Lo Va { 0 } (910-4) [1215] - - - 414 14 VDzF M1 V +3035 Guedhoek D59677C-5 Ag Pi { -1 } (966+2) [A688] - - - 401 9 VAug G5 V M3 V +3037 Augurgh A200654-F Na Ni Va Cx { 1 } (A55-1) [473D] - K - 502 11 VAug F3 V D +3038 Ozngenra C310352-C Lo { 0 } (620-4) [1318] - C - 501 14 VAug M1 V +3039 Sue Tuen B645442-A Ni Pa { 1 } (B34-3) [1516] - - - 605 17 VAug F4 V +3132 Uen Kha B89A312-B Lo Wa { 1 } (C21-3) [1417] - - - 225 15 VDzF G3 V M2 V M4 V +3133 Aegzorgh B546778-A Ag Pi { 3 } (F6C+3) [7A5A] - C - 115 10 VDzF F7 V +3235 Khon A00048A-C As Ni Va { 1 } (B34+3) [657E] - K - 514 15 VDzF F4 IV M2 V +3238 Ikreghz C646210-9 Lo { -1 } (710-5) [1114] - C - 421 14 VDzF K1 V +3240 Uegnaedz C543320-9 Lo Po { -1 } (720-5) [1214] - - - 202 12 VDzF M0 V D diff --git a/Tests/OutputFiles/verify_quadripoint_trade_write/Corridor Sector.pdf b/Tests/OutputFiles/verify_quadripoint_trade_write/Corridor Sector.pdf new file mode 100644 index 0000000000000000000000000000000000000000..27af247c905ceb7800fbe1bae52687bc20dd5f03 GIT binary patch literal 25495 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 NN^#L5OFJvY{{dw!X6OI_ literal 0 HcmV?d00001 diff --git a/Tests/OutputFiles/verify_quadripoint_trade_write/Provence Sector.pdf b/Tests/OutputFiles/verify_quadripoint_trade_write/Provence Sector.pdf new file mode 100644 index 0000000000000000000000000000000000000000..0a517a0648c25962dba2c40ac17e2aed45f68589 GIT binary patch literal 25429 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 literal 0 HcmV?d00001 diff --git a/Tests/Outputs/testHexMap.py b/Tests/Outputs/testHexMap.py index 90d3874eb..d26a79bfc 100644 --- a/Tests/Outputs/testHexMap.py +++ b/Tests/Outputs/testHexMap.py @@ -664,6 +664,77 @@ def test_verify_xboat_write_pdf(self): mse = np.mean((array1 - array2) ** 2) self.assertTrue(0.2 > mse, "Image difference " + str(mse) + " above threshold") + def test_verify_quadripoint_trade_write(self): + source1file = self.unpack_filename('DeltaFiles/quadripoint_trade_write/Tuglikki.sec') + source2file = self.unpack_filename('DeltaFiles/quadripoint_trade_write/Provence.sec') + source3file = self.unpack_filename('DeltaFiles/quadripoint_trade_write/Deneb.sec') + source4file = self.unpack_filename('DeltaFiles/quadripoint_trade_write/Corridor.sec') + + args = self._make_args() + args.interestingline = None + args.interestingtype = None + args.maps = True + args.routes = 'trade' + args.subsectors = False + args.btn = 7 + + delta = DeltaDictionary() + sector = SectorDictionary.load_traveller_map_file(source1file) + delta[sector.name] = sector + sector = SectorDictionary.load_traveller_map_file(source2file) + delta[sector.name] = sector + sector = SectorDictionary.load_traveller_map_file(source3file) + delta[sector.name] = sector + sector = SectorDictionary.load_traveller_map_file(source4file) + delta[sector.name] = sector + + galaxy = DeltaGalaxy(args.btn, args.max_jump) + galaxy.read_sectors(delta, args.pop_code, args.ru_calc, + args.route_reuse, args.routes, args.route_btn, args.mp_threads, args.debug_flag) + galaxy.output_path = args.output + + galaxy.generate_routes() + galaxy.trade.calculate_routes() + + secname = ['Tuglikki', 'Provence', 'Deneb', 'Corridor'] + + hexmap = PDFHexMap(galaxy, 'trade', args.btn) + for sector_name in secname: + hexmap.write_sector_pdf_map(galaxy.sectors[sector_name], is_live=True) + + fullname = ['Tuglikki Sector', 'Provence Sector', 'Deneb Sector', 'Corridor Sector'] + srcstem = self.unpack_filename('OutputFiles/verify_quadripoint_trade_write/Corridor Sector.pdf') + srcstem = srcstem[:-20] + trgstem = os.path.abspath(args.output + '/') + + for full in fullname: + srcpdf = srcstem + '/' + full + '.pdf' + trgpdf = trgstem + '/' + full + '.pdf' + + src_img = pymupdf.open(srcpdf) + src_iter = src_img.pages(0) + for page in src_iter: + src = page.get_pixmap() + + srcfile = os.path.abspath(args.output + '/' + full + ' original.png') + src.save(srcfile) + + trg_img = pymupdf.open(trgpdf) + trg_iter = trg_img.pages(0) + for page in trg_iter: + trg = page.get_pixmap() + trgfile = os.path.abspath(args.output + '/' + full + ' remix.png') + trg.save(trgfile) + + image1 = Image.open(srcfile) + image2 = Image.open(trgfile) + + array1 = np.array(image1) + array2 = np.array(image2) + + mse = np.mean((array1 - array2) ** 2) + self.assertTrue(0.24 > mse, "Image difference " + str(mse) + " above threshold for " + full) + def _load_ranges(self, galaxy, sourcefile): with open(sourcefile, "rb") as f: lines = f.readlines() From 6e35534d70f9b85e35ced2cca5cfff925a444bf1 Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Wed, 3 Jul 2024 20:25:21 +1000 Subject: [PATCH 36/45] Add endpoint clipping to PDFHexMap --- PyRoute/Outputs/PDFHexMap.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/PyRoute/Outputs/PDFHexMap.py b/PyRoute/Outputs/PDFHexMap.py index f90857a61..37087c52f 100644 --- a/PyRoute/Outputs/PDFHexMap.py +++ b/PyRoute/Outputs/PDFHexMap.py @@ -678,6 +678,38 @@ def string_width(font, string): w += font.character_widths[i] if i in font.character_widths else 600 return w * font.font_size / 1000.0 + def clipping(self, startx, starty, endx, endy): + points_t = [0.0, 1.0] + line_pt_1 = [startx, starty] + line_pt_2 = [endx, endy] + + if startx == endx: + if starty > endy: + return ((startx, min(max(starty, endy), 780)), + (startx, max(min(starty, endy), 42))) + else: + return ((startx, max(min(starty, endy), 42)), + (startx, min(max(starty, endy), 780))) + + if starty == endy: + if startx > endx: + return ((min(max(startx, endx), 600), starty), + (max(min(startx, endx), 15), starty)) + else: + return ((max(min(startx, endx), 15), starty), + (min(max(startx, endx), 600), starty)) + + points_t.append(float(15 - startx) / (endx - startx)) + points_t.append(float(600 - startx) / (endx - startx)) + points_t.append(float(780 - starty) / (endy - starty)) + points_t.append(float(42 - starty) / (endy - starty)) + + points_t.sort() + result = [(pt_1 + t * (pt_2 - pt_1)) for t in (points_t[2], points_t[3]) for (pt_1, pt_2) in + zip(line_pt_1, line_pt_2)] + logging.getLogger("PyRoute.HexMap").debug(result) + return (result[0], result[1]), (result[2], result[3]) + @property def compression(self): return self.writer.session.compression From 809ecd5afbc16afa76be1405aabe00c5e843d3c2 Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Wed, 3 Jul 2024 20:55:06 +1000 Subject: [PATCH 37/45] Fix rimward-sector alignment --- PyRoute/Outputs/PDFHexMap.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/PyRoute/Outputs/PDFHexMap.py b/PyRoute/Outputs/PDFHexMap.py index 37087c52f..588792264 100644 --- a/PyRoute/Outputs/PDFHexMap.py +++ b/PyRoute/Outputs/PDFHexMap.py @@ -119,8 +119,9 @@ def rimward_sector(self, pdf, name): new_font = 'Times-Roman' new_size = 10 pdf.setFont(new_font, size=new_size) - width = pdf.stringWidth(name, new_font, new_size) - x = 328 - (width/2) + # width = pdf.stringWidth(name, new_font, new_size) + # x = 328 - (width/2) + x = 306 textobject = pdf.beginText(x, 779) textobject.textOut(name) textobject.setStrokeColor('black') From 221388beb9bb744cc800048047e39da377b5e086 Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Wed, 3 Jul 2024 23:16:57 +1000 Subject: [PATCH 38/45] Extend quadripoint test to also draw borders --- PyRoute/Outputs/PDFHexMap.py | 14 +++++++++----- .../Corridor Sector.pdf | Bin 25495 -> 25877 bytes .../Deneb Sector.pdf | Bin 25035 -> 25660 bytes .../Provence Sector.pdf | Bin 25429 -> 26016 bytes .../Tuglikki Sector.pdf | Bin 25097 -> 25647 bytes Tests/Outputs/testHexMap.py | 1 + 6 files changed, 10 insertions(+), 5 deletions(-) diff --git a/PyRoute/Outputs/PDFHexMap.py b/PyRoute/Outputs/PDFHexMap.py index 588792264..5e37c7fff 100644 --- a/PyRoute/Outputs/PDFHexMap.py +++ b/PyRoute/Outputs/PDFHexMap.py @@ -12,6 +12,7 @@ from pypdflite.pdfobjects.pdftext import PDFText from reportlab.pdfgen.canvas import Canvas +from PyRoute.Position.Hex import Hex from PyRoute.Outputs.Map import Map from PyRoute.StatCalculation import StatCalculation @@ -265,18 +266,21 @@ def _draw_all(self, x, y, hline, lline, rline, pdf: Canvas, width, colour): pdf.line(rline[0][0], rline[0][1], rline[1][0], rline[1][1]) def _draw_borders(self, x, y, hline, lline, rline, pdf: Canvas, width, colour): - q, r = self.convert_hex_to_axial(x + self.sector.dx, y + self.sector.dy - 1) + offset = Hex.dy_offset(y, (self.sector.dy // 40)) + q, r = Hex.hex_to_axial(x + (self.sector.dx), offset) - if self.galaxy.borders.borders.get((q, r), False): - if self.galaxy.borders.borders[(q, r)] & 1: + border_val = self.galaxy.borders.borders_map.get((q, r), False) + + if border_val is not False: + if border_val & Hex.BOTTOM: # hline._draw() pdf.line(hline[0][0], hline[0][1], hline[1][0], hline[1][1]) - if self.galaxy.borders.borders[(q, r)] & 2 and y > 0: + if border_val & Hex.BOTTOMRIGHT and y > 0: # rline._draw() pdf.line(rline[0][0], rline[0][1], rline[1][0], rline[1][1]) - if self.galaxy.borders.borders[(q, r)] & 4: + if border_val & Hex.BOTTOMLEFT: # lline._draw() pdf.line(lline[0][0], lline[0][1], lline[1][0], lline[1][1]) diff --git a/Tests/OutputFiles/verify_quadripoint_trade_write/Corridor Sector.pdf b/Tests/OutputFiles/verify_quadripoint_trade_write/Corridor Sector.pdf index 27af247c905ceb7800fbe1bae52687bc20dd5f03..a60509450ce86c659be8a7a954b0ab53e6ffc2e9 100644 GIT binary patch literal 25877 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 literal 25495 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 M&Z<>g9cX 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 NN^#L5OFJvY{{dw!X6OI_ diff --git a/Tests/OutputFiles/verify_quadripoint_trade_write/Provence Sector.pdf b/Tests/OutputFiles/verify_quadripoint_trade_write/Provence Sector.pdf index 0a517a0648c25962dba2c40ac17e2aed45f68589..166fcde8e99185e9b997e056719819944a91217a 100644 GIT binary patch delta 13465 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 delta 12908 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

jBi;*A@{}^ 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 P+hEVK&6_P9td{*B)P<~< diff --git a/Tests/OutputFiles/verify_quadripoint_trade_write/Tuglikki Sector.pdf b/Tests/OutputFiles/verify_quadripoint_trade_write/Tuglikki Sector.pdf index 58d83b9df4c856389c284eab2c20e117002027c2..76b9bdba27d66026c742c72b65682b69ec7138da 100644 GIT binary patch literal 25647 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 diff --git a/Tests/Outputs/testHexMap.py b/Tests/Outputs/testHexMap.py index d26a79bfc..ab0d4b1f5 100644 --- a/Tests/Outputs/testHexMap.py +++ b/Tests/Outputs/testHexMap.py @@ -694,6 +694,7 @@ def test_verify_quadripoint_trade_write(self): galaxy.output_path = args.output galaxy.generate_routes() + galaxy.set_borders(args.borders, args.ally_match) galaxy.trade.calculate_routes() secname = ['Tuglikki', 'Provence', 'Deneb', 'Corridor'] From b285c7dfef8eb1674d6c452a8cd010e8bd5c3595 Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Wed, 3 Jul 2024 23:24:44 +1000 Subject: [PATCH 39/45] Drop remaining scaffolding code --- PyRoute/Outputs/PDFHexMap.py | 85 ------------------------------------ 1 file changed, 85 deletions(-) diff --git a/PyRoute/Outputs/PDFHexMap.py b/PyRoute/Outputs/PDFHexMap.py index 5e37c7fff..05737371e 100644 --- a/PyRoute/Outputs/PDFHexMap.py +++ b/PyRoute/Outputs/PDFHexMap.py @@ -75,7 +75,6 @@ def sector_name(self, doc: Canvas, name: str): """ Write name at the top of the document """ - # cursor = PDFCursor(5, -5, True) # Save out whatever font is currently set font_name = doc._fontname font_size = doc._fontsize @@ -178,8 +177,6 @@ def zone(self, doc, star, point): offset = 3 point[0] += self.xm + offset point[1] += self.xm + offset - #point.x_plus(self.xm) - #point.y_plus(self.ym) if star.zone in ['R', 'F']: self.add_circle(doc, point, self.xm, 'crimson') @@ -190,24 +187,18 @@ def zone(self, doc, star, point): def subsector_grid(self, pdf: Canvas): pdf.setStrokeColorRGB(211/255.0, 211/255.0, 211/255.0) - #vlineStart = PDFCursor(0, self.y_start + self.xm) - #vlineEnd = PDFCursor(0, self.y_start + self.xm + (180 * 4)) vlineStart = [0, self.y_start + self.xm] vlineEnd = [0, self.y_start + self.xm + (180 * 4)] for x in range(self.x_start, 595, 144): vlineStart[0] = x vlineEnd[0] = x - #pdf.add_line(cursor1=vlineStart, cursor2=vlineEnd) pdf.line(vlineStart[0], vlineStart[1], vlineEnd[0], vlineEnd[1]) - #hlineStart = PDFCursor(self.x_start, 0) - #hlineEnd = PDFCursor(591, 0) hlineStart = [self.x_start, 0] hlineEnd = [591, 0] for y in range(self.y_start + self.xm, 780, 180): hlineStart[1] = y hlineEnd[1] = y - #pdf.add_line(cursor1=hlineStart, cursor2=hlineEnd) pdf.line(hlineStart[0], hlineStart[1], hlineEnd[0], hlineEnd[1]) def hex_grid(self, doc, draw, width, colorname='gray'): @@ -219,26 +210,18 @@ def hex_grid(self, doc, draw, width, colorname='gray'): doc.setLineWidth(width) for x in range(self.x_count): - #hlineStart.x_plus() hlineStart[0] += hlineStartStep[0] - #hlineEnd.x_plus() hlineEnd[0] += hlineEndStep[0] self._hline_restart_y(x, hlineStart, hlineEnd) self._lline_restart_y(x, llineStart, llineEnd) self._rline_restart_y(x, rlineStart, rlineEnd) for y in range(self.y_count): - #hlineStart.y_plus() hlineStart[1] += hlineStartStep[1] - #hlineEnd.y_plus() hlineEnd[1] += hlineEndStep[1] - #llineStart.y_plus() llineStart[1] += llineStartStep[1] - #llineEnd.y_plus() llineEnd[1] += llineEndStep[1] - #rlineStart.y_plus() rlineStart[1] += rlineStartStep[1] - #rlineEnd.y_plus() rlineEnd[1] += rlineEndStep[1] hline = (hlineStart, hlineEnd) lline = (llineStart, llineEnd) @@ -246,23 +229,16 @@ def hex_grid(self, doc, draw, width, colorname='gray'): draw(x, y, hline, lline, rline, doc, width, colour) - #llineStart.x_plus() llineStart[0] += llineStartStep[0] - #llineEnd.x_plus() llineEnd[0] += llineEndStep[0] - #rlineStart.x_plus() rlineStart[0] += rlineStartStep[0] - #rlineEnd.x_plus() rlineEnd[0] += rlineEndStep[0] def _draw_all(self, x, y, hline, lline, rline, pdf: Canvas, width, colour): if (x < self.x_count - 1): - #hline._draw() pdf.line(hline[0][0], hline[0][1], hline[1][0], hline[1][1]) - #lline._draw() pdf.line(lline[0][0], lline[0][1], lline[1][0], lline[1][1]) if (y > 0): - #rline._draw() pdf.line(rline[0][0], rline[0][1], rline[1][0], rline[1][1]) def _draw_borders(self, x, y, hline, lline, rline, pdf: Canvas, width, colour): @@ -273,41 +249,22 @@ def _draw_borders(self, x, y, hline, lline, rline, pdf: Canvas, width, colour): if border_val is not False: if border_val & Hex.BOTTOM: - # hline._draw() pdf.line(hline[0][0], hline[0][1], hline[1][0], hline[1][1]) if border_val & Hex.BOTTOMRIGHT and y > 0: - # rline._draw() pdf.line(rline[0][0], rline[0][1], rline[1][0], rline[1][1]) if border_val & Hex.BOTTOMLEFT: - # lline._draw() pdf.line(lline[0][0], lline[0][1], lline[1][0], lline[1][1]) def _hline(self, pdf, width, colorname): - # dx/y are step sizen - #hlineStart = PDFCursor(0, 0) - #hlineStart.x = 3 - #hlineStart.y = self.y_start - self.ym - #hlineStart.dx = self.xm * 3 - #hlineStart.dy = self.ym * 2 - hlineStart = [3, self.y_start - self.ym] hlineStartStep = (self.xm * 3, self.ym * 2) - #hlineEnd = PDFCursor(0, 0) - #hlineEnd.x = self.xm * 2.5 - #hlineEnd.y = self.y_start - self.ym - #hlineEnd.dx = self.xm * 3 - #hlineEnd.dy = self.ym * 2 - hlineEnd = [self.xm * 2.5, self.y_start - self.ym] hlineEndStep = (self.xm * 3, self.ym * 2) colour = self.colourmap[colorname] - #pdf.setStrokeColorRGB(colour[0], colour[1], colour[2]) - - #hline = PDFLine(pdf.session, pdf.page, hlineStart, hlineEnd, stroke='solid', color=color, size=width) return hlineStart, hlineEnd, hlineStartStep, hlineEndStep, colour @@ -320,27 +277,14 @@ def _hline_restart_y(self, x, hlineStart, hlineEnd): hlineEnd[1] = self.y_start - 2 * self.ym def _lline(self, pdf, width, colorname): - #llineStart = PDFCursor(-10, 0) - #llineStart.x = self.x_start - #llineStart.dx = self.xm * 3 - #llineStart.dy = self.ym * 2 - llineStart = [self.x_start, 0] llineStartStep = (self.xm * 3, self.ym * 2) - #llineEnd = PDFCursor(-10, 0) - #llineEnd.x = self.x_start + self.xm - #llineEnd.dx = self.xm * 3 - #llineEnd.dy = self.ym * 2 llineEnd = [self.x_start + self.xm, 0] llineEndStep = (self.xm * 3, self.ym * 2) - #color = pdf.get_color() - #color.set_color_by_name(colorname) colour = self.colourmap[colorname] - #lline = PDFLine(pdf.session, pdf.page, llineStart, llineEnd, stroke='solid', color=color, size=width) - return llineStart, llineEnd, llineStartStep, llineEndStep, colour def _lline_restart_y(self, x, llineStart, llineEnd): @@ -367,10 +311,7 @@ def _rline(self, pdf, width, colorname): rlineEnd = [self.x_start, 0] rlineEndStep = (self.xm * 3, self.ym * 2) - #color = pdf.get_color() - #color.set_color_by_name(colorname) colour = self.colourmap[colorname] - #rline = PDFLine(pdf.session, pdf.page, rlineStart, rlineEnd, stroke='solid', color=color, size=width) return rlineStart, rlineEnd, rlineStartStep, rlineEndStep, colour @@ -383,7 +324,6 @@ def _rline_restart_y(self, x, rlineStart, rlineEnd): rlineEnd[1] = self.y_start - 3 * self.ym def system(self, pdf, star): - #def_font = pdf.get_font() font_name = pdf._fontname font_size = pdf._fontsize font_leading = pdf._leading @@ -398,37 +338,27 @@ def system(self, pdf, star): else: row = (self.y_start - self.ym) + (star.row * self.ym * 2) - #point = PDFCursor(col, row) point = [col, row] self.zone(pdf, star, point) rawpoint = [col, row] - #width = self.string_width(pdf.get_font(), str(star.uwp)) width = pdf.stringWidth(str(star.uwp), new_font, new_size) - ##point.y_plus(7) - #point.x_plus(self.ym - (width // 2)) rawpoint[0] += self.ym - (width // 2) rawpoint[1] += 7 textobject = pdf.beginText(rawpoint[0], rawpoint[1]) textobject.textOut(str(star.uwp)) pdf.drawText(textobject) - #pdf.add_text(str(star.uwp), point) if len(star.name) > 0: for chars in range(len(star.name), 0, -1): - #width = self.string_width(pdf.get_font(), star.name[:chars]) width = pdf.stringWidth(star.name[:chars], new_font, new_size) if width <= self.xm * 3.5: break - #point.y_plus(3.5) - #point.x = col - #point.x_plus(self.ym - (width // 2)) rawpoint[0] = col + self.ym - (width // 2) rawpoint[1] += 3.5 textobject = pdf.beginText(rawpoint[0], rawpoint[1]) textobject.textOut(star.name[:chars]) pdf.drawText(textobject) - #pdf.add_text(star.name[:chars], point) added = star.alg_code if star.tradeCode.subsector_capital: @@ -439,15 +369,10 @@ def system(self, pdf, star): added += ' ' added += '{:d}'.format(star.ggCount) - #point.y_plus(3.5) - #point.x = col rawpoint[0] = col rawpoint[1] += 3.5 - #width = pdf.get_font()._string_width(added) width = pdf.stringWidth(added) - # point.x_plus(self.ym - (width // 2)) rawpoint[0] += self.ym - (width // 2) - #pdf.add_text(added, point) textobject = pdf.beginText(rawpoint[0], rawpoint[1]) textobject.textOut(added) pdf.drawText(textobject) @@ -462,19 +387,13 @@ def system(self, pdf, star): added += "{}{} {}".format(star.baseCode, star.ggCount, star.importance) elif self.routes == 'xroute': added += " {}".format(star.importance) - #width = pdf.get_font()._string_width(added) width = pdf.stringWidth(added) - #point.y_plus(3.5) - #point.x = col - #point.x_plus(self.ym - (width // 2)) rawpoint[0] = col + self.ym - (width // 2) rawpoint[1] += 3.5 - #pdf.add_text(added, point) textobject = pdf.beginText(rawpoint[0], rawpoint[1]) textobject.textOut(added) pdf.drawText(textobject) - #pdf.set_font(def_font) # Restore saved font pdf.setFont(font_name, font_size, font_leading) @@ -500,8 +419,6 @@ def trade_line(self, pdf, edge, data): trade = 6 tradeColor = tradeColors[trade] - #color = pdf.get_color() - #color.set_color_by_number(tradeColor[0], tradeColor[1], tradeColor[2]) pdf.setStrokeColorRGB(tradeColor[0]/255.0, tradeColor[1]/255.0, tradeColor[2]/255.0) pdf.setFillColorRGB(tradeColor[0] / 255.0, tradeColor[1] / 255.0, tradeColor[2] / 255.0) @@ -578,8 +495,6 @@ def document(self, sector, is_live: bool = True): self.writer.setSubject(subject) self.writer.setKeywords(keywords) self.writer.setCreator(creator) - #document = self.writer.get_document() - #document.set_margins(4) return self.writer def close(self): From a634f9c9c5c950f31ca52dfd5e48d09ed392892f Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Wed, 3 Jul 2024 23:31:56 +1000 Subject: [PATCH 40/45] Hollow out unused methods --- PyRoute/Outputs/PDFHexMap.py | 76 +++--------------------------------- 1 file changed, 5 insertions(+), 71 deletions(-) diff --git a/PyRoute/Outputs/PDFHexMap.py b/PyRoute/Outputs/PDFHexMap.py index 05737371e..c048daec2 100644 --- a/PyRoute/Outputs/PDFHexMap.py +++ b/PyRoute/Outputs/PDFHexMap.py @@ -6,10 +6,7 @@ import os import logging -from pypdflite import PDFCursor, PDFLite -from pypdflite.pdfobjects.pdfellipse import PDFEllipse -from pypdflite.pdfobjects.pdfline import PDFLine -from pypdflite.pdfobjects.pdftext import PDFText +from pypdflite import PDFCursor from reportlab.pdfgen.canvas import Canvas from PyRoute.Position.Hex import Hex @@ -502,16 +499,13 @@ def close(self): self.writer.save() def cursor(self, x=0, y=0): - return PDFCursor(x, y) + raise NotImplementedError def add_line(self, pdf, start, end, colorname): """ Add a line to the document, from start to end, in color """ - color = pdf.get_color() - color.set_color_by_name(colorname) - pdf.set_draw_color(color) - pdf.add_line(cursor1=start, cursor2=end) + raise NotImplementedError def add_circle(self, pdf, center, radius, colorname): colour = PDFHexMap.colourmap[colorname] @@ -526,70 +520,10 @@ def get_line(self, doc, start, end, colorname, width): """ Get a line draw method processor """ - color = doc.get_color() - color.set_color_by_name(colorname) - return PDFLine(doc.session, doc.page, start, end, stroke='solid', color=color, size=width) + raise NotImplementedError def place_system(self, pdf, star): - def_font = pdf.get_font() - pdf.set_font('times', size=4) - - col = (self.xm * 3 * star.col) - if star.col & 1: - row = (self.y_start - self.ym * 2) + (star.row * self.ym * 2) - else: - row = (self.y_start - self.ym) + (star.row * self.ym * 2) - - point = PDFCursor(col, row) - self.zone(pdf, star, point.copy()) - - width = self.string_width(pdf.get_font(), str(star.uwp)) - point.y_plus(7) - point.x_plus(self.ym - (width / 2)) - pdf.add_text(str(star.uwp).encode('ascii', 'replace'), point) - - if len(star.name) > 0: - for chars in range(len(star.name), 0, -1): - width = self.string_width(pdf.get_font(), star.name[:chars]) - if width <= self.xm * 3.5: - break - point.y_plus(3.5) - point.x = col - point.x_plus(self.ym - (width / 2)) - pdf.add_text(star.name[:chars].encode('ascii', 'replace'), point) - - added = star.alg - if 'Cp' in star.tradeCode: - added += '+' - elif 'Cx' in star.tradeCode or 'Cs' in star.tradeCode: - added += '*' - else: - added += ' ' - - added += '{:d}'.format(star.ggCount) - point.y_plus(3.5) - point.x = col - width = pdf.get_font()._string_width(added) - point.x_plus(self.ym - (width / 2)) - pdf.add_text(added, point) - - added = '' - tradeIn = StatCalculation.trade_to_btn(star.tradeIn) - tradeThrough = StatCalculation.trade_to_btn(star.tradeIn + star.tradeOver) - - if self.routes == 'trade': - added += "{:X}{:X}{:X}{:d}".format(star.wtn, tradeIn, tradeThrough, star.starportSize) - elif self.routes == 'comm': - added += "{}{} {}".format(star.baseCode, star.ggCount, star.importance) - elif self.routes == 'xroute': - added += " {}".format(star.importance) - width = pdf.get_font()._string_width(added) - point.y_plus(3.5) - point.x = col - point.x_plus(self.ym - (width / 2)) - pdf.add_text(added, point) - - pdf.set_font(def_font) + raise NotImplementedError @staticmethod def string_width(font, string): From 2e44543fd27de70f5f984dc0bb15ee7400a375cc Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Thu, 4 Jul 2024 09:23:39 +1000 Subject: [PATCH 41/45] Shut up, ruff! --- PyRoute/Outputs/HexMap.py | 1 + PyRoute/Outputs/Map.py | 2 -- PyRoute/Outputs/PDFHexMap.py | 8 ++++---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/PyRoute/Outputs/HexMap.py b/PyRoute/Outputs/HexMap.py index 480318dc1..f3a46a170 100644 --- a/PyRoute/Outputs/HexMap.py +++ b/PyRoute/Outputs/HexMap.py @@ -520,6 +520,7 @@ def clipping(self, startx, starty, endx, endy): def compression(self): return self.writer.session.compression + if __name__ == '__main__': sector = Sector('# Core', '# 0,0') hexMap = HexMap(None) diff --git a/PyRoute/Outputs/Map.py b/PyRoute/Outputs/Map.py index c4e5f7ab3..3971e3048 100644 --- a/PyRoute/Outputs/Map.py +++ b/PyRoute/Outputs/Map.py @@ -5,8 +5,6 @@ """ import logging -from PyRoute.StatCalculation import StatCalculation - class Map(object): x_count = 33 diff --git a/PyRoute/Outputs/PDFHexMap.py b/PyRoute/Outputs/PDFHexMap.py index c048daec2..54e35ec98 100644 --- a/PyRoute/Outputs/PDFHexMap.py +++ b/PyRoute/Outputs/PDFHexMap.py @@ -99,7 +99,7 @@ def coreward_sector(self, pdf, name): pdf.setFont(new_font, size=new_size) width = pdf.stringWidth(name, new_font, new_size) - x = 306 - (width/2) + x = 306 - (width / 2) textobject = pdf.beginText(x, self.y_start - 3) textobject.textOut(name) textobject.setStrokeColor('black') @@ -183,7 +183,7 @@ def zone(self, doc, star, point): return def subsector_grid(self, pdf: Canvas): - pdf.setStrokeColorRGB(211/255.0, 211/255.0, 211/255.0) + pdf.setStrokeColorRGB(211 / 255.0, 211 / 255.0, 211 / 255.0) vlineStart = [0, self.y_start + self.xm] vlineEnd = [0, self.y_start + self.xm + (180 * 4)] for x in range(self.x_start, 595, 144): @@ -203,7 +203,7 @@ def hex_grid(self, doc, draw, width, colorname='gray'): hlineStart, hlineEnd, hlineStartStep, hlineEndStep, colour = self._hline(doc, width, colorname) llineStart, llineEnd, llineStartStep, llineEndStep, colour = self._lline(doc, width, colorname) rlineStart, rlineEnd, rlineStartStep, rlineEndStep, colour = self._rline(doc, width, colorname) - doc.setStrokeColorRGB(colour[0]/256.0, colour[1]/256.0, colour[2]/256.0) + doc.setStrokeColorRGB(colour[0] / 256.0, colour[1] / 256.0, colour[2] / 256.0) doc.setLineWidth(width) for x in range(self.x_count): @@ -416,7 +416,7 @@ def trade_line(self, pdf, edge, data): trade = 6 tradeColor = tradeColors[trade] - pdf.setStrokeColorRGB(tradeColor[0]/255.0, tradeColor[1]/255.0, tradeColor[2]/255.0) + pdf.setStrokeColorRGB(tradeColor[0] / 255.0, tradeColor[1] / 255.0, tradeColor[2] / 255.0) pdf.setFillColorRGB(tradeColor[0] / 255.0, tradeColor[1] / 255.0, tradeColor[2] / 255.0) endCircle = end.sector == start.sector From 220bbb58f760b19d3a15a2fbfb6f100cb51b1355 Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Thu, 4 Jul 2024 10:55:40 +1000 Subject: [PATCH 42/45] Fix test blast damage --- Tests/Hypothesis/Inputs/testHypothesisStarlineParser.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Tests/Hypothesis/Inputs/testHypothesisStarlineParser.py b/Tests/Hypothesis/Inputs/testHypothesisStarlineParser.py index 6298fe793..a2e2e4f24 100644 --- a/Tests/Hypothesis/Inputs/testHypothesisStarlineParser.py +++ b/Tests/Hypothesis/Inputs/testHypothesisStarlineParser.py @@ -72,8 +72,9 @@ def comparison_line(draw): '0000 000000000000000 ???????-? 000000000000000 {0} - [0000] - A 000 ?0', '0000 000000000000000 ???????-? 000000000000 (0 - (000-0) [0000] B - A 000 00?', '0000 000000000000000 0000000-0 (00000000000000 B - A 000 ?0)0000000000', - '0000 000000000000000 0000000-0 (00000000000000 - (000-0) - B - A 000 0?' - '0000 000000000000000 0000000-0 [00000000000000 - - [0000] - - A 000 00?' + '0000 000000000000000 0000000-0 (00000000000000 - (000-0) - B - A 000 0?', + '0000 000000000000000 0000000-0 [00000000000000 - - [0000] - - A 000 00?', + '0000 000000000000000 0000000-0 (00000000000000 B A A 000 0 0?)0000000000' ] candidate = draw(from_regex(regex=ParseStarInput.starline, alphabet='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWYXZ -{}()[]?\'+*')) @@ -325,6 +326,7 @@ class testHypothesisStarlineParser(unittest.TestCase): @example('0000 000000000000000 0000000-0 (00000000000000 B - A 000 ?0)0000000000', 'weird') @example('0000 000000000000000 0000000-0 (00000000000000 - (000-0) - B - A 000 0?', 'weird') @example('0000 000000000000000 0000000-0 [00000000000000 - - [0000] - - A 000 00?', 'weird') + @example('0000 000000000000000 0000000-0 (00000000000000 B A A 000 0 0?)0000000000', 'weird') def test_starline_parser_against_regex(self, s, match): # if it's a known weird-parse case, assume it out now assume(match != 'weird') From 9ca814184b82e05f5f691ff85c9c1e7574319d89 Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Thu, 4 Jul 2024 13:07:49 +1000 Subject: [PATCH 43/45] Cut sector-map printing over to PDFHexMap --- PyRoute/DeltaDebug/DeltaReduce.py | 4 ++-- PyRoute/route.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/PyRoute/DeltaDebug/DeltaReduce.py b/PyRoute/DeltaDebug/DeltaReduce.py index 7e163d0d1..f0e715499 100644 --- a/PyRoute/DeltaDebug/DeltaReduce.py +++ b/PyRoute/DeltaDebug/DeltaReduce.py @@ -13,7 +13,6 @@ from PyRoute.DeltaDebug.DeltaLogicError import DeltaLogicError from PyRoute.DeltaDebug.DeltaDictionary import DeltaDictionary from PyRoute.DeltaDebug.DeltaGalaxy import DeltaGalaxy -from PyRoute.Outputs.HexMap import HexMap from PyRoute.DeltaPasses.AllegianceReducer import AllegianceReducer from PyRoute.DeltaPasses.AuxiliaryLineReduce import AuxiliaryLineReduce from PyRoute.DeltaPasses.Canonicalisation import Canonicalisation @@ -27,6 +26,7 @@ from PyRoute.DeltaPasses.WidenHoleReducer import WidenHoleReducer from PyRoute.SpeculativeTrade import SpeculativeTrade from PyRoute.StatCalculation import StatCalculation +from PyRoute.Outputs.PDFHexMap import PDFHexMap from PyRoute.Outputs.SubsectorMap2 import GraphicSubsectorMap @@ -172,7 +172,7 @@ def _check_interesting(args, sectors): stats.write_statistics(args.ally_count, args.ally_match, args.json_data) if args.maps: - pdfmap = HexMap(galaxy, args.routes, args.route_btn) + pdfmap = PDFHexMap(galaxy, args.routes, args.route_btn) pdfmap.write_maps() if args.subsectors: diff --git a/PyRoute/route.py b/PyRoute/route.py index 0725f9db9..231a9ad77 100755 --- a/PyRoute/route.py +++ b/PyRoute/route.py @@ -13,7 +13,7 @@ from PyRoute.DataClasses.ReadSectorOptions import ReadSectorOptions from PyRoute.Galaxy import Galaxy from PyRoute.SpeculativeTrade import SpeculativeTrade -from PyRoute.Outputs.HexMap import HexMap +from PyRoute.Outputs.PDFHexMap import PDFHexMap from PyRoute.Outputs.SubsectorMap2 import GraphicSubsectorMap from PyRoute.StatCalculation import StatCalculation @@ -152,7 +152,7 @@ def process(): stats.write_statistics(args.ally_count, args.ally_match, args.json_data) if args.maps: - pdfmap = HexMap(galaxy, args.routes, args.route_btn) + pdfmap = PDFHexMap(galaxy, args.routes, args.route_btn) pdfmap.write_maps() if args.subsectors: From 86052f447185e7611c249449d781f11f79d2dce9 Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Fri, 5 Jul 2024 18:23:24 +1000 Subject: [PATCH 44/45] Verify {PDF,}HexMap compression properties --- PyRoute/Outputs/HexMap.py | 2 ++ PyRoute/Outputs/PDFHexMap.py | 6 +++++- Tests/Outputs/testHexMap.py | 4 ++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/PyRoute/Outputs/HexMap.py b/PyRoute/Outputs/HexMap.py index f3a46a170..c2c662226 100644 --- a/PyRoute/Outputs/HexMap.py +++ b/PyRoute/Outputs/HexMap.py @@ -518,6 +518,8 @@ def clipping(self, startx, starty, endx, endy): @property def compression(self): + if self.writer is None: + return True return self.writer.session.compression diff --git a/PyRoute/Outputs/PDFHexMap.py b/PyRoute/Outputs/PDFHexMap.py index 54e35ec98..3e2c5e909 100644 --- a/PyRoute/Outputs/PDFHexMap.py +++ b/PyRoute/Outputs/PDFHexMap.py @@ -566,4 +566,8 @@ def clipping(self, startx, starty, endx, endy): @property def compression(self): - return self.writer.session.compression + if self.writer is None: + return True + if 'string' == self.writer._filename: + return False + return True diff --git a/Tests/Outputs/testHexMap.py b/Tests/Outputs/testHexMap.py index ab0d4b1f5..082b5703e 100644 --- a/Tests/Outputs/testHexMap.py +++ b/Tests/Outputs/testHexMap.py @@ -128,6 +128,7 @@ def test_verify_empty_sector_write(self): secname = 'Zao Kfeng Ig Grilokh' hexmap = HexMap(galaxy, 'trade') + self.assertTrue(hexmap.compression) oldtime = b'20230911163653' oldmd5 = b'8419949643701e6b438d6f3f93239cf7' @@ -136,6 +137,7 @@ def test_verify_empty_sector_write(self): expected_result = file.read() result = hexmap.write_sector_pdf_map(galaxy.sectors[secname], is_live=False) + self.assertFalse(hexmap.compression) self.assertIsNotNone(result) # rather than try to mock datetime.now(), patch the output result. # this also lets us check that there's only a single match @@ -173,9 +175,11 @@ def test_verify_empty_sector_write_pdf(self): secname = 'Zao Kfeng Ig Grilokh' hexmap = PDFHexMap(galaxy, 'trade') + self.assertTrue(hexmap.compression) targpath = os.path.abspath(args.output + '/Zao Kfeng Ig Grilokh Sector.pdf') result = hexmap.write_sector_pdf_map(galaxy.sectors[secname], is_live=True) + self.assertTrue(hexmap.compression) src_img = pymupdf.open(srcpdf) src_iter = src_img.pages(0) for page in src_iter: From cd9df9570ee86b2573016c394cd40e30dc24ac4f Mon Sep 17 00:00:00 2001 From: Alex Goodwin Date: Sun, 21 Jul 2024 10:34:20 +1000 Subject: [PATCH 45/45] Clean up residual PDFCursor use in PDFHexMap --- PyRoute/Outputs/PDFHexMap.py | 14 +------------- Tests/Outputs/testHexMap.py | 2 +- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/PyRoute/Outputs/PDFHexMap.py b/PyRoute/Outputs/PDFHexMap.py index 3e2c5e909..5296ccfad 100644 --- a/PyRoute/Outputs/PDFHexMap.py +++ b/PyRoute/Outputs/PDFHexMap.py @@ -6,7 +6,6 @@ import os import logging -from pypdflite import PDFCursor from reportlab.pdfgen.canvas import Canvas from PyRoute.Position.Hex import Hex @@ -21,8 +20,6 @@ class PDFHexMap(Map): def __init__(self, galaxy, routes, min_btn=8): super(PDFHexMap, self).__init__(galaxy, routes) - self.lineStart = PDFCursor(0, 0) - self.lineEnd = PDFCursor(0, 0) self.min_btn = min_btn self.writer = None @@ -293,18 +290,9 @@ def _lline_restart_y(self, x, llineStart, llineEnd): llineEnd[1] = self.y_start - 2 * self.ym def _rline(self, pdf, width, colorname): - rlineStart = PDFCursor(0, 0) - rlineStart.x = self.x_start + self.xm - rlineStart.dx = self.xm * 3 - rlineStart.dy = self.ym * 2 - rlineStart = [self.x_start + self.xm, 0] rlineStartStep = (self.xm * 3, self.ym * 2) - rlineEnd = PDFCursor(0, 0) - rlineEnd.x = self.x_start - rlineEnd.dx = self.xm * 3 - rlineEnd.dy = self.ym * 2 rlineEnd = [self.x_start, 0] rlineEndStep = (self.xm * 3, self.ym * 2) @@ -486,7 +474,7 @@ def document(self, sector, is_live: bool = True): subject = "Trade route map generated by PyRoute for Traveller" author = None keywords = None - creator = "PyPDFLite" + creator = "ReportLab" self.writer.setTitle(title) self.writer.setAuthor(author) self.writer.setSubject(subject) diff --git a/Tests/Outputs/testHexMap.py b/Tests/Outputs/testHexMap.py index 082b5703e..b4f1e96db 100644 --- a/Tests/Outputs/testHexMap.py +++ b/Tests/Outputs/testHexMap.py @@ -101,7 +101,7 @@ def test_document_object_pdf(self): # self.assertFalse(hexmap.compression, 'PDF writer compression set') self.assertEqual('Sector Zao Kfeng Ig Grilokh (-2,4)', document._doc.info.title) self.assertEqual('Trade route map generated by PyRoute for Traveller', document._doc.info.subject) - self.assertEqual('PyPDFLite', document._doc.info.creator) + self.assertEqual('ReportLab', document._doc.info.creator) self.assertEqual(expected_path, document._filename) def test_verify_empty_sector_write(self):