-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathimposition.py
More file actions
131 lines (101 loc) · 4.07 KB
/
Copy pathimposition.py
File metadata and controls
131 lines (101 loc) · 4.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
"""Pure imposition rules used by GreasePress.
This module intentionally has no bpy dependency so ordering rules can be
checked independently from Blender's scene and UI state.
"""
from dataclasses import dataclass
from typing import List, Optional, Sequence, Tuple
PageNumber = Optional[int]
@dataclass(frozen=True)
class SheetSide:
sheet: int
side: str
left: PageNumber
right: PageNumber
@dataclass(frozen=True)
class BookletPlan:
source_page_count: int
imposed_page_count: int
blank_page_count: int
sides: Tuple[SheetSide, ...]
@dataclass(frozen=True)
class BrochureSide:
side: str # OUTSIDE or INSIDE
panels: Tuple[PageNumber, ...]
widths: Tuple[float, ...] # relative panel widths
@dataclass(frozen=True)
class BrochurePlan:
kind: str
required_page_count: int
sides: Tuple[BrochureSide, ...]
def padded_page_count(page_count: int) -> int:
if page_count < 1:
raise ValueError("page_count must be at least 1")
return ((page_count + 3) // 4) * 4
def _page_or_blank(number: int, source_page_count: int) -> PageNumber:
return number if number <= source_page_count else None
def impose_booklet(page_count: int) -> BookletPlan:
total = padded_page_count(page_count)
sheet_count = total // 4
sides: List[SheetSide] = []
for zero_based_sheet in range(sheet_count):
sheet = zero_based_sheet + 1
low = 1 + zero_based_sheet * 2
high = total - zero_based_sheet * 2
sides.append(SheetSide(sheet, "FRONT", _page_or_blank(high, page_count), _page_or_blank(low, page_count)))
sides.append(SheetSide(sheet, "BACK", _page_or_blank(low + 1, page_count), _page_or_blank(high - 1, page_count)))
return BookletPlan(page_count, total, total - page_count, tuple(sides))
def impose_bifold() -> BrochurePlan:
return BrochurePlan(
"BIFOLD", 4,
(
BrochureSide("OUTSIDE", (4, 1), (1.0, 1.0)),
BrochureSide("INSIDE", (2, 3), (1.0, 1.0)),
),
)
def impose_trifold_accordion() -> BrochurePlan:
return BrochurePlan(
"TRIFOLD_ACCORDION", 6,
(
BrochureSide("OUTSIDE", (6, 1, 2), (1.0, 1.0, 1.0)),
BrochureSide("INSIDE", (3, 4, 5), (1.0, 1.0, 1.0)),
),
)
def impose_trifold_roll(inset_ratio: float = 0.97) -> BrochurePlan:
inset_ratio = min(max(float(inset_ratio), 0.5), 1.0)
return BrochurePlan(
"TRIFOLD_ROLL", 6,
(
BrochureSide("OUTSIDE", (5, 6, 1), (inset_ratio, 1.0, 1.0)),
BrochureSide("INSIDE", (2, 3, 4), (1.0, 1.0, inset_ratio)),
),
)
def brochure_plan(kind: str, inset_ratio: float = 0.97) -> BrochurePlan:
if kind == 'BIFOLD':
return impose_bifold()
if kind == 'TRIFOLD_ACCORDION':
return impose_trifold_accordion()
if kind == 'TRIFOLD_ROLL':
return impose_trifold_roll(inset_ratio)
raise ValueError(f"Unsupported brochure type: {kind}")
def page_label(page: PageNumber) -> str:
return "Blank" if page is None else str(page)
def validate_plan(plan: BookletPlan) -> Sequence[str]:
errors: List[str] = []
actual = [p for side in plan.sides for p in (side.left, side.right) if p is not None]
expected = list(range(1, plan.source_page_count + 1))
if sorted(actual) != expected:
errors.append("The source pages do not appear exactly once")
if len(plan.sides) != plan.imposed_page_count // 2:
errors.append("Inconsistent number of sides")
if plan.imposed_page_count % 4 != 0:
errors.append("The imposed total is not divisible by four")
return errors
def validate_brochure_plan(plan: BrochurePlan, page_count: int) -> Sequence[str]:
errors: List[str] = []
if page_count != plan.required_page_count:
errors.append(f"This mode requires exactly {plan.required_page_count} pages")
actual = [p for side in plan.sides for p in side.panels if p is not None]
expected = list(range(1, plan.required_page_count + 1))
if sorted(actual) != expected:
errors.append("The panels do not contain each page exactly once")
return errors