diff --git a/README.md b/README.md index aced89a..75bf750 100644 --- a/README.md +++ b/README.md @@ -5,14 +5,12 @@ A jacket pattern has around 50 A4 pages that must be glued or taped together. Th That's why I wrote nobubo: This tool assembles the pages of a digital pdf pattern and chops it into a desired output size, so that you can print it on any page size you want. -Nobubo has been developed and tested with several download patterns from Burda, Knipmode and other brands successfully. - -In the end, even though nobubo has been developed with sewing patterns in mind, it is basically a fancy n-up tool for PDFs in general. +Nobubo has been developed and tested with several download patterns from Burda, Knipmode and other brands successfully. Even though nobubo has been developed with sewing patterns in mind, it is basically a fancy n-up tool for PDFs in general. ## Prerequisites -* A digital pattern where each page is made to be printed on A4 or US letter size. **If you haven't purchased a digital pattern, Nobubo is useless**. +* A digital pattern where each page is made to be printed on A4 or US letter size. **If you haven't purchased a digital pattern, nobubo is useless**. * Each page is already cropped, so that only the bare pattern is visible (no white borders around the pattern). Nobubo is able to handle cropped pdfs, but you still have to do it yourself. -* At least one overview sheet that shows what the assembled pattern should look like in the end. Usually, the assembled pattern pages form a huge rectangle. Some vendors disregard this and the assembled pattern is of a weird "rectangle + 2 pages" shape. Nobubo can only handle rectangle shapes, so those leftover pages have to be assembled by hand. +* Usually, the assembled pattern pages form a huge rectangle. Some brands provide a handy overview how all the assembled pages are supposed to like like. Some brands, however, disregard this rectangle shape and the assembled pattern is of a weird "rectangle + 2 pages" shape. Nobubo can only handle rectangle shapes, so those leftover pages have to be assembled by hand. * Python >=3.7 * `pdflatex` [must be installed](https://tex.stackexchange.com/questions/49569/where-to-download-pdflatex-exe) @@ -27,13 +25,13 @@ In the end, even though nobubo has been developed with sewing patterns in mind, $ pip install . ``` -Check the installation with one of the mock patterns: +Check the installation with one of the mock patterns and run nobubo: ```bash -$ nobubo --il 1 8 4 --ol a0 "tests/testdata/mockpattern_oneoverview_8x4.pdf" "sample.pdf" +$ nobubo --il 2 8 4 --ol a0 "tests/testdata/mockpattern_oneoverview_8x4.pdf" "sample.pdf" ``` -**Installation without pip:** +**Installation requirements file:** ```bash $ pip install -r requirements.txt @@ -43,7 +41,7 @@ Check the installation with one of the mock patterns: ```bash -$ python -m nobubo --il 1 8 4 --ol a0 "tests/testdata/mockpattern_oneoverview_8x4.pdf" "sample.pdf" +$ python -m nobubo --il 2 8 4 --ol a0 "tests/testdata/mockpattern_oneoverview_8x4.pdf" "sample.pdf" ``` All further commands must then be run with `pytho -m nobubo`. @@ -59,7 +57,7 @@ $ nobubo --help Available commands: ```bash -$ nobubo --il OVERVIEW COLUMNS ROWS --ol {a0|us|mmxmm} {--reverse} {--margin mm} INPUTPATH OUTPUTPATH +$ nobubo --il FIRSTPAGE COLUMNS ROWS --ol {a0|us|mmxmm} {--reverse} {--margin mm} INPUTPATH OUTPUTPATH ``` Have a look at the mock patterns in the test folder. Use them with with the above commands and see how it works. @@ -69,54 +67,44 @@ Have a look at the mock patterns in the test folder. Use them with with the abov This example pattern has one overview sheet on page 1 with 6 columns and 5 rows (see also picture below) and is assembled from bottom left to top right: ```bash -$ nobubo --il 1 6 5 --ol a0 --reverse "home/alice/patterns/jacket.pdf" "home/alice/patterns/jacket_a0.pdf" +$ nobubo --il 2 6 5 --ol a0 --reverse "home/alice/patterns/jacket.pdf" "home/alice/patterns/jacket_a0.pdf" ``` * `--il ` (input layout) is required and followed by three numbers: - * `1`: the page on which the overview sheet is located. `0` if there is no overview. + * `2`: The first pattern page of all the pages that form the huge rectangle displayed on the overview sheet. * `6 5`: columns and rows you count on the overview sheet. * `--ol` (output layout) defines the size on which the pattern shall be printed. Currently supported: * `a0`: Output size is A0. - * `us`: Output size is "copyshop size" of 36 x 48 inches, also called ["Arch E / Arch 6" ](https://en.wikipedia.org/wiki/Paper_size#Architectural_sizes) + * `us`: Output size is "copyshop size" of 36 x 48 inches, also called "[Arch E / Arch 6 ](https://en.wikipedia.org/wiki/Paper_size#Architectural_sizes)". * `mmxmm`: use a custom output size in millimeters, e.g. `920x1187`. -* if `--ol` is omitted, Nobubo just prints a huge collage of all assembled pages without chopping them up into an output layout. +* if `--ol` is omitted, nobubo just prints a huge collage of all assembled pages without chopping them up into an output layout. * `--reverse`: as default, the pattern is assembled from top left to bottom right. Use the `--reverse` flag to assemble it from bottom left to top right, which is needed for Burda patterns for example. * `"home/alice/patterns/jacket.pdf"`: the path to the original pattern including filename. * `"home/alice/patterns/jacket_a0.pdf"`: the path where the collage should be saved, including filename. -The pdf has 6 columns and 5 rows, which means the final pdf collage will comprise four A0 pages to print, since 16 A4 pages fit on one A0 page. This is how the sample overview sheet might look like and how it will be split up: +The pdf has 6 columns and 5 rows, which means the final pdf collage will be four A0 pages to print, since 16 A4 pages fit on one A0 page. This is how the sample overview sheet might look like and how it will be split up: sample pattern Of course, you can still choose to print pages 2-4 on A4 from your original pattern and just page 1 on A0. -**The order of assembly differs between pattern companies. Burda assembles the pages from bottom left to top right, whereas others (Knipmode) assemble them from top left to bottom right. Please compare the order of the pdf pages in the pdf file itself to the overview to see in which way the pages are assembled.** +**The order of assembly differs between pattern companies. Burda assembles the pages from bottom left to top right, whereas others (Knipmode) assemble them from top left to bottom right. Please compare the order of the pdf pages in the pdf file itself to the overview to see in which way the pages are assembled. Rarely, some brands don't even provide an overview sheet (booh!), which means you have to figure it out yourself.** ### Example with two overview sheets ```bash -$ nobubo --il 1 8 4 -il 34 7 3 --ol a0 "home/alice/mypattern.pdf" "home/alice/results/mypattern_a0.pdf" +$ nobubo --il 2 8 4 -il 35 7 3 --ol a0 "home/alice/mypattern.pdf" "home/alice/results/mypattern_a0.pdf" ``` -The first overview sheet is on page 1 with 8 columns, 4 rows: `--il 1 8 4`. The second overview sheet is on page 34 with 7 columns, 3 rows: `--il 34 7 3`. The assembly is from top left to bottom right, the output on A0. +The first overview sheet is on page 1 with 8 columns, 4 rows, which means the pattern pages start on page `2`: `--il 2 8 4`. The second overview sheet is on page 34 with 7 columns, 3 rows, the pattern pages start on page `35`: `--il 35 7 3`. The assembly is from top left to bottom right, the output to be printed on A0. ### Example with a collage output ``` bash -$ nobubo --il 1 8 4 --il 34 7 3 "home/alice/mypattern.pdf" "home/alice/results/mypattern_a0.pdf" +$ nobubo --il 2 8 4 --il 35 7 3 "home/alice/mypattern.pdf" "home/alice/results/mypattern_a0.pdf" ``` This prints only two pdfs (=2 overview sheets) which contain each a huge collage. -### Example with no overview sheet and printing margin - -Some pattern companies provide the overview sheet separately, for example in the pdf together with the sewing instructions. Then, the pattern pdf really and only contains the A4 pages of the pattern. In this case, write `0` for the overview sheet. - -Use the optional flag `--margin 20` so that a printing border of 2 cm is included on every A0 page: - -```bash -$ nobubo --il 0 6 5 --ol a0 --margin 20 "home/alice/patterns/jacket.pdf" "home/alice/patterns/jacket_a0.pdf" -``` - ## Caveats * Please double-check and compare the overview sheet with the amount of pdf pages given (rows * columns = amount of pages needed). If the result is wrong, check if you counted the rows and columns correctly or if a second overview sheet hides in later pages. Burda for example includes several overview sheets and their corresponding pages in one pdf. * Check if the pattern must be assembled from top left to bottom right (default) or bottom left to top right (use `--reverse` flag) diff --git a/nobubo/assembly.py b/nobubo/assembly.py index 1a21698..856c350 100644 --- a/nobubo/assembly.py +++ b/nobubo/assembly.py @@ -51,16 +51,13 @@ def _assemble(input_properties: core.InputProperties, if input_properties.reverse_assembly: start, end, step = calc.pagerange_reverse(current_layout) - l = list(reversed([(x+1, x+current_layout.columns) for x in range(start, end, step)])) + l = list(reversed([(x, x+current_layout.columns-1) for x in range(start, end, step)])) tuples = ["-".join(map(str, i)) for i in l] page_range = ",".join(tuples) else: - if current_layout.overview == 0: # file has no overview page - page_range = f"1-{current_layout.columns*current_layout.rows}" - else: - begin = current_layout.overview + 1 - end = current_layout.overview + (current_layout.columns * current_layout.rows) - page_range = f"{begin}-{end}" + begin = current_layout.first_page + end = current_layout.first_page + (current_layout.columns * current_layout.rows) - 1 + page_range = f"{begin}-{end}" file_content = [ "\\batchmode\n", diff --git a/nobubo/calc.py b/nobubo/calc.py index 5af851a..d826fb9 100644 --- a/nobubo/calc.py +++ b/nobubo/calc.py @@ -54,7 +54,7 @@ def parse_cli_input(input_layout: (int, int, int), output_layout_cli: str, print def parse_input_layouts(input_layout: (int, int, int)) ->[core.Layout]: - return [core.Layout(overview=data[0], columns=data[1], rows=data[2]) for data in input_layout] + return [core.Layout(first_page=data[0], columns=data[1], rows=data[2]) for data in input_layout] def parse_output_layout(output_layout_cli: str, print_margin: int = None) -> [int]: @@ -131,7 +131,7 @@ def to_mm(output_layout: str) -> [int, int]: def pagerange_reverse(layout: core.Layout) -> (int, int, int): - return layout.overview, (layout.overview + (layout.columns * layout.rows)), layout.columns + return layout.first_page, layout.first_page + (layout.columns * layout.rows) - 1, layout.columns def new_outputpath(output_path: pathlib.Path, page_count: int): diff --git a/nobubo/core.py b/nobubo/core.py index 8629cba..b3330f8 100644 --- a/nobubo/core.py +++ b/nobubo/core.py @@ -35,8 +35,11 @@ class PageSize: class Layout: """ A Pattern layout. + + first_page: The number of the pdf page which marks the beginning of the pattern pages + that are covered by the columns and rows. """ - overview: int + first_page: int columns: int rows: int diff --git a/nobubo/nobubo.py b/nobubo/nobubo.py index 238a441..27ccfe3 100644 --- a/nobubo/nobubo.py +++ b/nobubo/nobubo.py @@ -25,8 +25,8 @@ @click.command() @click.option("--il", "input_layout_cli", nargs=3, type=click.INT, multiple=True, required=True, - help="Input layout of the pdf. Can be used multiple times if there is more than 1 overview sheet per pdf.", - metavar="OVERVIEW COLUMNS ROWS") + help="Input layout of the pdf. Can be used multiple times.", + metavar="FIRSTPAGE COLUMNS ROWS") @click.option("--ol", "output_layout_cli", nargs=1, type=click.STRING, callback=calc.validate_output_layout, help="Output layout. Supported formats: a0, us, custom. No output layout provided creates a huge collage.", @@ -43,8 +43,7 @@ def main(input_layout_cli, output_layout_cli, print_margin, reverse_assembly, in """ Creates a collage from digital pattern pages and then chops it up into a desired output layout. The collage is assembled according to one or several overview sheets. - These overviews are usually provided along with the pattern pages in the same pdf. - If no overview sheet is in the pattern pdf itself, write 0 in the arguments given: --il 0 8 4. + These overviews are usually provided along with the pattern pages in the same pdf or in the instructions pdf. Note: In order to use nobubo, you need the original pdf pattern. Create a backup of the original if you are afraid to have it damaged in any way. @@ -53,7 +52,7 @@ def main(input_layout_cli, output_layout_cli, print_margin, reverse_assembly, in Example: A digital pattern contains 2 overview sheets at page 1 and 34 with different layouts each. The output is to be printed on A0 paper: - $ nobubo --il 1 8 4 -il 34 7 3 --ol a0 "home/alice/mypattern.pdf" "home/alice/results/test_collage.pdf" + $ nobubo --il 2 8 4 -il 35 7 3 --ol a0 "path/to/pattern/mypattern.pdf" "test_collage.pdf" See the readme for further information: https://github.com/bytinbit/nobubo diff --git a/tests/conftest.py b/tests/conftest.py index d35ce5a..f88202e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -66,23 +66,23 @@ def pdfproperty() -> core.InputProperties: output_path=pathlib.Path("output_test.pdf"), number_of_pages=57, pagesize=core.PageSize(width=483.307, height=729.917), - layout=[core.Layout(overview=1, columns=8, rows=7)]) + layout=[core.Layout(first_page=2, columns=8, rows=7)]) # 8 cols, 7 rows + 1 overview page = 57 @pytest.fixture def one_overview_even() -> core.Layout: - return core.Layout(overview=1, columns=8, rows=7) + return core.Layout(first_page=2, columns=8, rows=7) @pytest.fixture def one_overview_uneven() -> core.Layout: - return core.Layout(overview=1, columns=9, rows=4) + return core.Layout(first_page=2, columns=9, rows=4) @pytest.fixture() def two_overviews() -> [core.Layout, core.Layout]: - first = core.Layout(overview=1, columns=5, rows=5) - second = core.Layout(overview=27, columns=5, rows=5) + first = core.Layout(first_page=2, columns=5, rows=5) + second = core.Layout(first_page=28, columns=5, rows=5) return [first, second] diff --git a/tests/test_blackbox.py b/tests/test_blackbox.py index 3868940..564b36b 100644 --- a/tests/test_blackbox.py +++ b/tests/test_blackbox.py @@ -7,7 +7,7 @@ def test_no_overview_normal_collage(testdata, tmp_path, pdftester): filepath = testdata / "mockpattern_nooverview_8x4.pdf" output_filepath = tmp_path / "mock.pdf" runner = CliRunner() - result = runner.invoke(nobubo.main, ["--il", "0", "8", "4", str(filepath), str(output_filepath)]) + result = runner.invoke(nobubo.main, ["--il", "1", "8", "4", str(filepath), str(output_filepath)]) print(result.output) assert result.exit_code == 0 assert pdftester.read() == ["mock_1.pdf"] @@ -23,7 +23,7 @@ def test_no_overview_reverse_collage(testdata, tmp_path, pdftester): filepath = testdata / "mockpattern_nooverview_8x4.pdf" output_filepath = tmp_path / "mock.pdf" runner = CliRunner() - result = runner.invoke(nobubo.main, ["--il", "0", "8", "4", "--reverse", str(filepath), str(output_filepath)]) + result = runner.invoke(nobubo.main, ["--il", "1", "8", "4", "--reverse", str(filepath), str(output_filepath)]) print(result.output) assert result.exit_code == 0 assert pdftester.read() == ["mock_1.pdf"] @@ -38,7 +38,7 @@ def test_one_overview_normal_collage(testdata, tmp_path, pdftester): filepath = testdata / "mockpattern_oneoverview_8x4.pdf" output_filepath = tmp_path / "mock.pdf" runner = CliRunner() - result = runner.invoke(nobubo.main, ["--il", "1", "8", "4", str(filepath), str(output_filepath)]) + result = runner.invoke(nobubo.main, ["--il", "2", "8", "4", str(filepath), str(output_filepath)]) print(result.output) assert result.exit_code == 0 assert pdftester.read() == ["mock_1.pdf"] @@ -54,7 +54,7 @@ def test_one_overview_reverse_collage(testdata, tmp_path, pdftester): filepath = testdata / "mockpattern_oneoverview_8x4.pdf" output_filepath = tmp_path / "mock.pdf" runner = CliRunner() - result = runner.invoke(nobubo.main, ["--il", "1", "8", "4", "--reverse", str(filepath), str(output_filepath)]) + result = runner.invoke(nobubo.main, ["--il", "2", "8", "4", "--reverse", str(filepath), str(output_filepath)]) print(result.output) assert result.exit_code == 0 assert pdftester.read() == ["mock_1.pdf"] @@ -70,7 +70,7 @@ def test_two_overviews_normal_collage(testdata, tmp_path, pdftester): filepath = testdata / "mockpattern_twooverviews_8x4_7x3.pdf" output_filepath = tmp_path / "mock.pdf" runner = CliRunner() - result = runner.invoke(nobubo.main, ["--il", "1", "8", "4", "--il", "34", "7", "3", str(filepath), str(output_filepath)]) + result = runner.invoke(nobubo.main, ["--il", "2", "8", "4", "--il", "35", "7", "3", str(filepath), str(output_filepath)]) print(result.output) assert result.exit_code == 0 assert pdftester.read() == ["mock_1.pdf", "mock_2.pdf"] @@ -89,7 +89,7 @@ def test_two_overviews_reverse_collage(testdata, tmp_path, pdftester): filepath = testdata / "mockpattern_twooverviews_8x4_7x3.pdf" output_filepath = tmp_path / "mock.pdf" runner = CliRunner() - result = runner.invoke(nobubo.main, ["--il", "1", "8", "4", "--il", "34", "7", "3", "--reverse", str(filepath), str(output_filepath)]) + result = runner.invoke(nobubo.main, ["--il", "2", "8", "4", "--il", "35", "7", "3", "--reverse", str(filepath), str(output_filepath)]) print(result.output) assert result.exit_code == 0 assert pdftester.read() == ["mock_1.pdf", "mock_2.pdf"] @@ -108,7 +108,7 @@ def test_one_overview_normal_a0(testdata, tmp_path, pdftester): filepath = testdata / "mockpattern_oneoverview_8x4.pdf" output_filepath = tmp_path / "mock.pdf" runner = CliRunner() - result = runner.invoke(nobubo.main, ["--il", "1", "8", "4", "--ol", "a0", str(filepath), str(output_filepath)]) + result = runner.invoke(nobubo.main, ["--il", "2", "8", "4", "--ol", "a0", str(filepath), str(output_filepath)]) print(result.output) assert result.exit_code == 0 assert pdftester.read() == ["mock_1.pdf"] @@ -122,10 +122,10 @@ def test_one_overview_normal_a0(testdata, tmp_path, pdftester): def test_one_overview_reverse_a0(testdata, tmp_path, pdftester): - filepath = testdata / "mockpattern_nooverview_8x4.pdf" + filepath = testdata / "mockpattern_oneoverview_8x4.pdf" output_filepath = tmp_path / "mock.pdf" runner = CliRunner() - result = runner.invoke(nobubo.main, ["--il", "0", "8", "4", "--ol", "a0", "--reverse", str(filepath), str(output_filepath)]) + result = runner.invoke(nobubo.main, ["--il", "2", "8", "4", "--ol", "a0", "--reverse", str(filepath), str(output_filepath)]) print(result.output) assert result.exit_code == 0 assert pdftester.read() == ["mock_1.pdf"] @@ -142,7 +142,7 @@ def test_one_overview_normal_custom(testdata, tmp_path, pdftester): filepath = testdata / "mockpattern_oneoverview_8x4.pdf" output_filepath = tmp_path / "mock.pdf" runner = CliRunner() - result = runner.invoke(nobubo.main, ["--il", "1", "8", "4", "--ol", "920x1187", str(filepath), str(output_filepath)]) + result = runner.invoke(nobubo.main, ["--il", "2", "8", "4", "--ol", "920x1187", str(filepath), str(output_filepath)]) print(result.output) assert result.exit_code == 0 assert pdftester.read() == ["mock_1.pdf"] @@ -160,7 +160,7 @@ def test_one_overview_normal_us(testdata, tmp_path, pdftester): filepath = testdata / "mockpattern_oneoverview_8x4.pdf" output_filepath = tmp_path / "mock.pdf" runner = CliRunner() - result = runner.invoke(nobubo.main, ["--il", "1", "8", "4", "--ol", "us", str(filepath), str(output_filepath)]) + result = runner.invoke(nobubo.main, ["--il", "2", "8", "4", "--ol", "us", str(filepath), str(output_filepath)]) print(result.output) assert result.exit_code == 0 assert pdftester.read() == ["mock_1.pdf"] @@ -177,7 +177,7 @@ def test_two_overviews_normal_a0(testdata, tmp_path, pdftester): filepath = testdata / "mockpattern_twooverviews_8x4_7x3.pdf" output_filepath = tmp_path / "mock.pdf" runner = CliRunner() - result = runner.invoke(nobubo.main, ["--il", "1", "8", "4", "--il", "34", "7", "3", "--ol", "a0", str(filepath), str(output_filepath)]) + result = runner.invoke(nobubo.main, ["--il", "2", "8", "4", "--il", "35", "7", "3", "--ol", "a0", str(filepath), str(output_filepath)]) print(result.output) assert result.exit_code == 0 assert pdftester.read() == ["mock_1.pdf", "mock_2.pdf"] @@ -198,7 +198,7 @@ def test_two_overviews_reverse_a0(testdata, tmp_path, pdftester): filepath = testdata / "mockpattern_twooverviews_8x4_7x3.pdf" output_filepath = tmp_path / "mock.pdf" runner = CliRunner() - result = runner.invoke(nobubo.main, ["--il", "1", "8", "4", "--il", "34", "7", "3", "--ol", "a0", "--reverse", str(filepath), str(output_filepath)]) + result = runner.invoke(nobubo.main, ["--il", "2", "8", "4", "--il", "35", "7", "3", "--ol", "a0", "--reverse", str(filepath), str(output_filepath)]) print(result.output) assert result.exit_code == 0 assert pdftester.read() == ["mock_1.pdf", "mock_2.pdf"]