510 lines
20 KiB
Python
510 lines
20 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Scribus script: Generate the B-Prize 2026 "Living Pipeline" A3 poster.
|
|
Creates a landscape A3 (420x297mm) document with all content.
|
|
|
|
Run via wrapper that sets sys.argv before exec.
|
|
"""
|
|
|
|
import sys
|
|
import os
|
|
|
|
def parse_args():
|
|
args = {}
|
|
argv = sys.argv[1:]
|
|
i = 0
|
|
while i < len(argv):
|
|
if argv[i] == "--output" and i + 1 < len(argv):
|
|
args["output"] = argv[i + 1]
|
|
i += 2
|
|
elif argv[i] == "--dpi" and i + 1 < len(argv):
|
|
args["dpi"] = int(argv[i + 1])
|
|
i += 2
|
|
else:
|
|
i += 1
|
|
return args
|
|
|
|
|
|
def define_colors(scribus):
|
|
scribus.defineColorRGB("DarkNavy", 12, 35, 64)
|
|
scribus.defineColorRGB("DeepTeal", 27, 79, 114)
|
|
scribus.defineColorRGB("ForestGreen", 26, 107, 74)
|
|
scribus.defineColorRGB("BrightGreen", 39, 174, 96)
|
|
scribus.defineColorRGB("LightGreen", 213, 245, 227)
|
|
scribus.defineColorRGB("AlertRed", 192, 57, 43)
|
|
scribus.defineColorRGB("BrightRed", 231, 76, 60)
|
|
scribus.defineColorRGB("AccentBlue", 52, 152, 219)
|
|
scribus.defineColorRGB("LightBlue", 212, 230, 241)
|
|
scribus.defineColorRGB("DarkText", 26, 35, 50)
|
|
scribus.defineColorRGB("MidGray", 100, 100, 100)
|
|
scribus.defineColorRGB("LightGray", 230, 230, 230)
|
|
scribus.defineColorRGB("VLightGray", 245, 245, 245)
|
|
scribus.defineColorRGB("PageBG", 250, 251, 247)
|
|
scribus.defineColorRGB("CardBG", 255, 255, 255)
|
|
scribus.defineColorRGB("EarthBrown", 139, 69, 19)
|
|
scribus.defineColorRGB("Purple", 142, 68, 173)
|
|
scribus.defineColorRGB("WarmBG", 253, 245, 237)
|
|
scribus.defineColorRGB("CoolBG", 236, 247, 236)
|
|
|
|
|
|
# Track unique name counter to avoid collisions
|
|
_name_counter = [0]
|
|
|
|
def uname(prefix="obj"):
|
|
_name_counter[0] += 1
|
|
return f"{prefix}_{_name_counter[0]}"
|
|
|
|
|
|
def rect(s, x, y, w, h, fill, line_color="None", line_w=0):
|
|
n = s.createRect(x, y, w, h, uname("r"))
|
|
s.setFillColor(fill, n)
|
|
if line_color != "None":
|
|
s.setLineColor(line_color, n)
|
|
s.setLineWidth(line_w, n)
|
|
else:
|
|
s.setLineColor("None", n)
|
|
s.setLineWidth(0, n)
|
|
return n
|
|
|
|
|
|
def txt(s, x, y, w, h, text, font="DejaVu Sans", size=10, color="DarkText", align=0):
|
|
n = s.createText(x, y, w, h, uname("t"))
|
|
s.setText(text, n)
|
|
try:
|
|
s.setFont(font, n)
|
|
except:
|
|
pass
|
|
s.setFontSize(size, n)
|
|
s.setTextColor(color, n)
|
|
s.setTextAlignment(align, n)
|
|
return n
|
|
|
|
|
|
def hline(s, x, y, length, color="LightGray", width=0.5):
|
|
n = s.createLine(x, y, x + length, y, uname("l"))
|
|
s.setLineColor(color, n)
|
|
s.setLineWidth(width, n)
|
|
return n
|
|
|
|
|
|
def vline(s, x1, y1, x2, y2, color="BrightGreen", width=0.75):
|
|
n = s.createLine(x1, y1, x2, y2, uname("vl"))
|
|
s.setLineColor(color, n)
|
|
s.setLineWidth(width, n)
|
|
return n
|
|
|
|
|
|
def main():
|
|
try:
|
|
import scribus
|
|
except ImportError:
|
|
print("ERROR: Must run inside Scribus")
|
|
sys.exit(1)
|
|
|
|
args = parse_args()
|
|
output_path = args.get("output", "/app/output/bprize-poster.pdf")
|
|
dpi = args.get("dpi", 300)
|
|
|
|
s = scribus # shorthand
|
|
|
|
# ═══ PAGE SETUP ═══
|
|
# A3 Landscape: pass dimensions as landscape directly
|
|
PW = 420.0
|
|
PH = 297.0
|
|
|
|
s.newDocument(
|
|
(PW, PH), # Already landscape dimensions
|
|
(6, 6, 6, 6),
|
|
s.PORTRAIT, # Don't double-swap with LANDSCAPE flag
|
|
1, s.UNIT_MILLIMETERS, s.PAGE_1, 0, 1,
|
|
)
|
|
|
|
define_colors(s)
|
|
|
|
# ═══ LAYOUT GRID ═══
|
|
HEADER_H = 26.0
|
|
GAP = 1.5
|
|
MARGIN = 4.0
|
|
COL_TOP = HEADER_H + GAP
|
|
COL_H = PH - COL_TOP - MARGIN
|
|
|
|
# Three columns with gaps
|
|
TOTAL_W = PW - MARGIN * 2
|
|
COL1_W = TOTAL_W * 0.30
|
|
COL2_W = TOTAL_W * 0.37
|
|
COL3_W = TOTAL_W * 0.33
|
|
|
|
COL1_X = MARGIN
|
|
COL2_X = COL1_X + COL1_W + GAP
|
|
COL3_X = COL2_X + COL2_W + GAP
|
|
|
|
B = "DejaVu Sans Bold"
|
|
R = "DejaVu Sans"
|
|
I = "DejaVu Sans Oblique"
|
|
|
|
# ═══════════════════════════════════════
|
|
# HEADER
|
|
# ═══════════════════════════════════════
|
|
rect(s, 0, 0, PW, HEADER_H, "DarkNavy")
|
|
rect(s, 0, HEADER_H - 0.8, PW, 0.8, "BrightGreen")
|
|
|
|
txt(s, 10, 2.5, 280, 11, "THE LIVING PIPELINE", B, 26, "White")
|
|
txt(s, 10, 13, 310, 5,
|
|
"A mycorrhizal network model for distributed water supply along the Collingwood\u2013Alliston corridor",
|
|
R, 8.5, "White")
|
|
txt(s, 10, 19, 310, 5,
|
|
"Instead of one $270M pipe, what if the landscape itself became the water system?",
|
|
I, 8, "White")
|
|
txt(s, PW - 80, 4, 74, 18,
|
|
"Biomimicry Commons\nB-Prize 2026\nCollingwood\u2013Alliston Corridor\nOntario, Canada",
|
|
R, 7, "White", 2)
|
|
|
|
# ═══════════════════════════════════════
|
|
# COLUMN BACKGROUNDS
|
|
# ═══════════════════════════════════════
|
|
rect(s, COL1_X, COL_TOP, COL1_W, COL_H, "PageBG")
|
|
rect(s, COL2_X, COL_TOP, COL2_W, COL_H, "CardBG")
|
|
rect(s, COL3_X, COL_TOP, COL3_W, COL_H, "PageBG")
|
|
|
|
# ═══════════════════════════════════════
|
|
# COLUMN 1 — THE PROBLEM
|
|
# ═══════════════════════════════════════
|
|
cx = COL1_X + 4
|
|
cw = COL1_W - 8
|
|
cy = COL_TOP + 3
|
|
|
|
txt(s, cx, cy, cw, 5, "THE CHALLENGE", B, 9.5, "AlertRed")
|
|
hline(s, cx, cy + 5, cw, "BrightRed", 0.6)
|
|
cy += 7
|
|
|
|
txt(s, cx, cy, cw, 24,
|
|
"In September 2023, the cost to expand Collingwood\u2019s Raymond A. Barker Water Treatment Plant doubled \u2014 from $121M to $270M \u2014 to pump Georgian Bay water 53 km uphill to Alliston via a single 600mm pipeline following the historic 1852 railway corridor.\n\nOne pipe. Five towns. Zero redundancy. A break at km 30 cuts off everyone downstream.",
|
|
R, 7.5, "DarkText")
|
|
cy += 26
|
|
|
|
# Stats row
|
|
sw = (cw - 4) / 3
|
|
stats = [("$270M", "Expansion cost"), ("53 km", "Single pipeline"), ("5", "Towns dependent")]
|
|
for i, (val, lab) in enumerate(stats):
|
|
sx = cx + i * (sw + 2)
|
|
rect(s, sx, cy, sw, 13, "CardBG", "LightGray", 0.25)
|
|
txt(s, sx, cy + 1, sw, 6, val, B, 13, "AlertRed", 1)
|
|
txt(s, sx, cy + 7.5, sw, 5, lab, R, 5, "MidGray", 1)
|
|
cy += 16
|
|
|
|
# Community table
|
|
txt(s, cx, cy, cw, 3.5, "COMMUNITY STATUS", B, 6.5, "MidGray")
|
|
cy += 4.5
|
|
|
|
rect(s, cx, cy, cw, 4, "DarkNavy")
|
|
txt(s, cx + 1, cy + 0.7, 30, 3, "Community", B, 5, "White")
|
|
txt(s, cx + 32, cy + 0.7, 38, 3, "Source", B, 5, "White")
|
|
txt(s, cx + 71, cy + 0.7, cw - 72, 3, "Status", B, 5, "White")
|
|
cy += 4.5
|
|
|
|
rows = [
|
|
("Collingwood", "Georgian Bay WTP", "$270M expansion", "MidGray"),
|
|
("Stayner", "4 groundwater wells", "AT CAPACITY", "AlertRed"),
|
|
("Angus", "6 wells + pipeline", "DEV FROZEN", "AlertRed"),
|
|
("Alliston", "Pipeline + wells", "6,400 HOMES REJECTED", "AlertRed"),
|
|
("Blue Mountains", "Pipeline only", "1,250 m\u00b3/day", "MidGray"),
|
|
]
|
|
for i, (c, src, st, sc) in enumerate(rows):
|
|
bg = "CardBG" if i % 2 == 0 else "VLightGray"
|
|
rect(s, cx, cy, cw, 3.8, bg)
|
|
txt(s, cx + 1, cy + 0.5, 30, 3, c, B, 5.5, "DarkText")
|
|
txt(s, cx + 32, cy + 0.5, 38, 3, src, R, 5, "MidGray")
|
|
txt(s, cx + 71, cy + 0.5, cw - 72, 3, st, B, 4.5, sc)
|
|
cy += 4
|
|
cy += 4
|
|
|
|
# Nature's Model
|
|
txt(s, cx, cy, cw, 4.5, "NATURE\u2019S MODEL", B, 9, "ForestGreen")
|
|
hline(s, cx, cy + 5, cw, "BrightGreen", 0.5)
|
|
cy += 7
|
|
|
|
bw = (cw - 3) / 2
|
|
|
|
# Problem box
|
|
rect(s, cx, cy, bw, 36, "WarmBG", "AlertRed", 0.3)
|
|
txt(s, cx + 2, cy + 2, bw - 4, 4, "CURRENT SYSTEM", B, 6.5, "AlertRed", 1)
|
|
txt(s, cx + 2, cy + 8, bw - 4, 26,
|
|
"One trunk, one root.\nCut the trunk \u2192 all die.\n\nSingle point of failure.\nNo redundancy.\nNo local capacity.\n\n53 km of pumping\nuphill from Georgian Bay.",
|
|
R, 6.5, "DarkText", 1)
|
|
|
|
# Solution box
|
|
sx = cx + bw + 3
|
|
rect(s, sx, cy, bw, 36, "CoolBG", "BrightGreen", 0.3)
|
|
txt(s, sx + 2, cy + 2, bw - 4, 4, "MYCORRHIZAL FOREST", B, 6.5, "ForestGreen", 1)
|
|
txt(s, sx + 2, cy + 8, bw - 4, 26,
|
|
"Many roots, connected\nunderground. Resources\nflow to where needed.\n\nHub trees share water\nvia fungal networks.\nModular = resilient.\n\nNo single point of failure.",
|
|
R, 6.5, "DarkText", 1)
|
|
cy += 39
|
|
|
|
# Principle box
|
|
rect(s, cx, cy, cw, 28, "CoolBG")
|
|
rect(s, cx, cy, 1, 28, "BrightGreen")
|
|
txt(s, cx + 3, cy + 2, cw - 5, 24,
|
|
"In Ontario forests, 90% of rainfall events produce zero runoff. Every point along water\u2019s journey is a collection point, a storage node, and a treatment system. There is no \u201cend of pipe.\u201d\n\nDecentralized modular networks improve infrastructure resilience by a minimum of 3\u00d7 (Springer, 2024). Hydraulic redistribution through fungal hyphae increases shallow soil water by 28\u2013102% (Egerton-Warburton et al., J. Exp. Botany).\n\nDesign principle: Distribute collection, treatment, and storage across the network. Every node both gives and receives. Use the landscape as infrastructure.",
|
|
I, 6.5, "DarkText")
|
|
|
|
# ═══════════════════════════════════════
|
|
# COLUMN 2 — THE SOLUTION
|
|
# ═══════════════════════════════════════
|
|
cx = COL2_X + 4
|
|
cw = COL2_W - 8
|
|
cy = COL_TOP + 3
|
|
|
|
txt(s, cx, cy, cw, 5, "THE LIVING PIPELINE", B, 9.5, "ForestGreen")
|
|
hline(s, cx, cy + 5, cw, "BrightGreen", 0.6)
|
|
cy += 7
|
|
|
|
txt(s, cx, cy, cw, 9,
|
|
"Instead of $270M for one bigger plant, distribute capacity across the corridor \u2014 turning the landscape into a living water system where each community both gives and receives.",
|
|
R, 7, "DarkText")
|
|
cy += 11
|
|
|
|
# Solution Map
|
|
map_h = 58
|
|
rect(s, cx, cy, cw, map_h, "LightBlue", "AccentBlue", 0.25)
|
|
|
|
# Bay label
|
|
txt(s, cx + 2, cy + 2, 30, 3.5, "Georgian Bay", B, 6, "DeepTeal")
|
|
|
|
# Backbone line
|
|
mx = cx + cw * 0.42
|
|
vline(s, mx, cy + 8, mx, cy + map_h - 6, "BrightGreen", 0.75)
|
|
|
|
# Nodes
|
|
nodes = [
|
|
(cy + 10, "Collingwood WTP", "(reduced expansion)", True),
|
|
(cy + 22, "Stayner Node", "3,000 m\u00b3/day", False),
|
|
(cy + 34, "Angus Node", "5,000 m\u00b3/day", True),
|
|
(cy + 46, "Alliston Node", "3,000 m\u00b3/day", False),
|
|
]
|
|
for ny, label, sub, right_side in nodes:
|
|
rect(s, mx - 2, ny - 2, 4, 4, "BrightGreen", "White", 0.3)
|
|
lx = mx + 6 if right_side else cx + 3
|
|
txt(s, lx, ny - 2, 55, 7, f"{label}\n{sub}", R, 5.5, "DarkText")
|
|
|
|
# Flow arrows
|
|
for ay in [cy + 17, cy + 29, cy + 41]:
|
|
txt(s, mx + 3, ay, 6, 4, "\u2195", B, 7, "BrightGreen")
|
|
|
|
# MAR zone
|
|
rect(s, cx + cw - 40, cy + map_h - 20, 37, 16, "LightBlue", "AccentBlue", 0.2)
|
|
txt(s, cx + cw - 39, cy + map_h - 18, 35, 12, "MAR Zone\nAlliston Sand Plain", B, 5.5, "DeepTeal", 1)
|
|
|
|
# Wetland patches
|
|
for wy in [cy + 18, cy + 30, cy + 42]:
|
|
rect(s, cx + 2, wy, 22, 7, "LightGreen", "BrightGreen", 0.2)
|
|
txt(s, cx + 3, wy + 1.5, 20, 4, "Wetland", R, 5, "ForestGreen", 1)
|
|
|
|
# Legend
|
|
txt(s, cx + 2, cy + map_h - 5, cw - 4, 3.5,
|
|
"\u25cf Treatment Node \u25a2 MAR Zone \u25a2 Constructed Wetland \u2195 Bidirectional Flow",
|
|
R, 4.5, "MidGray")
|
|
cy += map_h + 3
|
|
|
|
# Strategy Cards
|
|
strategies = [
|
|
("1", "SATELLITE TREATMENT NODES", "EarthBrown",
|
|
"Each tree\u2019s own root system",
|
|
"3\u20134 modular membrane + UV units at existing well sites. Canadian manufacturers (H2O Innovation, Trojan Technologies). $2\u20138M each, deployable in 12\u201324 months. Reduces pipeline demand by 30\u201350%. Capacity tracks demand \u2014 no $270M upfront commitment."),
|
|
("2", "MANAGED AQUIFER RECHARGE", "AccentBlue",
|
|
"Forest floor + beaver dam storage",
|
|
"Alliston Sand Plain \u2014 Ontario\u2019s best MAR candidate (CFB Borden, one of the world\u2019s most studied aquifer sites). Infiltration basins (May\u2013Nov) + ASR wells (year-round). 1 ha of basin = water for 15\u201320K people. Precedent: Turku, Finland serves 300K on identical glaciofluvial geology."),
|
|
("3", "CONSTRUCTED TREATMENT WETLANDS", "BrightGreen",
|
|
"Riparian buffer zones",
|
|
"Hybrid subsurface-flow wetlands proven in Ontario winters (Fleming College CAWT, Lindsay ON). O&M costs 75% cheaper than conventional mechanical treatment. Non-potable reuse of treated greywater cuts potable demand 30\u201340% per household. Creates habitat corridors along rail trail."),
|
|
("4", "MYCORRHIZAL BACKBONE", "Purple",
|
|
"Common mycorrhizal network",
|
|
"Existing 600mm pipeline becomes smart balancing network \u2014 SCADA/IoT sensors, bidirectional flow, real-time optimization. Any node can supply neighbours during shortage. Precedent: SEQ Water Grid (Australia) \u2014 12 dams + 5 plants managed as one distributed system."),
|
|
]
|
|
|
|
card_h = 27
|
|
for num, title, color, bio, desc in strategies:
|
|
rect(s, cx, cy, cw, card_h, "CardBG", "LightGray", 0.15)
|
|
rect(s, cx, cy, 1.2, card_h, color)
|
|
# Number
|
|
rect(s, cx + 3, cy + 1.5, 5, 5, color)
|
|
txt(s, cx + 3, cy + 1.8, 5, 4, num, B, 7, "White", 1)
|
|
# Title
|
|
txt(s, cx + 10, cy + 1.5, cw - 14, 4, title, B, 7, "DarkText")
|
|
# Bio
|
|
txt(s, cx + 10, cy + 5.5, cw - 14, 3, f"Biomimicry: {bio}", I, 5, "MidGray")
|
|
# Desc
|
|
txt(s, cx + 3, cy + 9.5, cw - 6, 13, desc, R, 6, "DarkText")
|
|
cy += card_h + 1.5
|
|
|
|
# ═══════════════════════════════════════
|
|
# COLUMN 3 — FEASIBILITY
|
|
# ═══════════════════════════════════════
|
|
cx = COL3_X + 4
|
|
cw = COL3_W - 8
|
|
cy = COL_TOP + 3
|
|
|
|
txt(s, cx, cy, cw, 5, "FEASIBILITY & IMPACT", B, 9.5, "DeepTeal")
|
|
hline(s, cx, cy + 5, cw, "AccentBlue", 0.6)
|
|
cy += 7.5
|
|
|
|
# ── Cost Bars ──
|
|
txt(s, cx, cy, cw, 3, "FINANCIAL COMPARISON", B, 6.5, "MidGray")
|
|
cy += 4
|
|
|
|
bar_w = (cw - 16) / 2
|
|
bar_base_y = cy + 32
|
|
|
|
# Red bar
|
|
bh_red = 28
|
|
rect(s, cx + 2, bar_base_y - bh_red, bar_w, bh_red, "BrightRed")
|
|
txt(s, cx + 2, bar_base_y - bh_red + 4, bar_w, 8, "$270M", B, 14, "White", 1)
|
|
txt(s, cx + 2, bar_base_y + 1, bar_w, 5, "Centralized\n(Status Quo)", B, 5.5, "MidGray", 1)
|
|
|
|
# Green bar
|
|
bh_green = 16
|
|
gx = cx + bar_w + 14
|
|
rect(s, gx, bar_base_y - bh_green, bar_w, bh_green, "BrightGreen")
|
|
txt(s, gx, bar_base_y - bh_green + 2, bar_w, 7, "$118\u2013170M", B, 10, "White", 1)
|
|
txt(s, gx, bar_base_y + 1, bar_w, 5, "Living Pipeline\n(Distributed)", B, 5.5, "MidGray", 1)
|
|
|
|
cy = bar_base_y + 8
|
|
|
|
# Cost breakdown
|
|
cost_items = [
|
|
("WTP expansion (smaller Phase 1)", "$80\u2013100M"),
|
|
("Satellite nodes (3\u20134)", "$15\u201330M"),
|
|
("MAR infrastructure", "$8\u201315M"),
|
|
("Constructed wetlands (4 sites)", "$12\u201320M"),
|
|
("Smart network integration", "$3\u20135M"),
|
|
]
|
|
rect(s, cx, cy, cw, 3.5, "DarkNavy")
|
|
txt(s, cx + 1, cy + 0.5, cw * 0.6, 2.5, "Component", B, 5, "White")
|
|
txt(s, cx + cw * 0.6, cy + 0.5, cw * 0.4 - 1, 2.5, "Cost (CAD)", B, 5, "White", 2)
|
|
cy += 4
|
|
|
|
for i, (comp, cost) in enumerate(cost_items):
|
|
bg = "CardBG" if i % 2 == 0 else "VLightGray"
|
|
rect(s, cx, cy, cw, 3.2, bg)
|
|
txt(s, cx + 1, cy + 0.4, cw * 0.6, 2.5, comp, R, 5, "DarkText")
|
|
txt(s, cx + cw * 0.6, cy + 0.4, cw * 0.4 - 1, 2.5, cost, R, 5, "DarkText", 2)
|
|
cy += 3.3
|
|
|
|
# Savings row
|
|
rect(s, cx, cy, cw, 3.8, "LightGreen")
|
|
txt(s, cx + 1, cy + 0.6, cw * 0.5, 3, "SAVINGS", B, 6, "ForestGreen")
|
|
txt(s, cx + cw * 0.4, cy + 0.6, cw * 0.6 - 1, 3, "$100\u2013150M (37\u201356%)", B, 6, "ForestGreen", 2)
|
|
cy += 6
|
|
|
|
# ── Timeline ──
|
|
txt(s, cx, cy, cw, 3, "TIMELINE ADVANTAGE", B, 6.5, "MidGray")
|
|
cy += 4
|
|
|
|
# Centralized bar
|
|
txt(s, cx, cy, 22, 3, "Centralized", B, 5, "MidGray")
|
|
tbar_x = cx + 23
|
|
tbar_w = cw - 23
|
|
rect(s, tbar_x, cy, tbar_w, 3.5, "LightGray")
|
|
rect(s, tbar_x + tbar_w * 0.5, cy, tbar_w * 0.5, 3.5, "BrightRed")
|
|
txt(s, tbar_x, cy + 0.4, tbar_w, 2.5, "First water: 2029", B, 5, "White", 2)
|
|
cy += 4.5
|
|
|
|
# Living Pipeline bar
|
|
txt(s, cx, cy, 22, 3, "Living Pipeline", B, 5, "MidGray")
|
|
rect(s, tbar_x, cy, tbar_w, 3.5, "LightGray")
|
|
rect(s, tbar_x + tbar_w * 0.15, cy, tbar_w * 0.45, 3.5, "BrightGreen")
|
|
txt(s, tbar_x, cy + 0.4, tbar_w, 2.5, "First water: 2027", B, 5, "White", 2)
|
|
cy += 4.5
|
|
|
|
txt(s, cx, cy, cw, 3,
|
|
"\u25b2 2 years faster \u2014 unblocks ~3,000\u20135,000 housing units sooner",
|
|
B, 5.5, "BrightGreen", 1)
|
|
cy += 5
|
|
|
|
# ── Resilience ──
|
|
txt(s, cx, cy, cw, 3, "RESILIENCE", B, 6.5, "MidGray")
|
|
cy += 4
|
|
|
|
rc = [cw * 0.24, cw * 0.38, cw * 0.38]
|
|
|
|
rect(s, cx, cy, cw, 3.5, "DarkNavy")
|
|
txt(s, cx + 1, cy + 0.5, rc[0], 2.5, "Risk", B, 4.5, "White")
|
|
txt(s, cx + rc[0], cy + 0.5, rc[1], 2.5, "Centralized", B, 4.5, "White")
|
|
txt(s, cx + rc[0] + rc[1], cy + 0.5, rc[2], 2.5, "Living Pipeline", B, 4.5, "White")
|
|
cy += 4
|
|
|
|
res = [
|
|
("WTP failure", "All towns lose supply", "One node; others compensate"),
|
|
("Pipeline break", "Downstream cut off", "Nodes self-sufficient"),
|
|
("Drought", "Entire system stressed", "Aquifers buffer demand"),
|
|
("Cost escalation", "$121M\u2192$270M (+123%)", "Phased, no mega-risk"),
|
|
]
|
|
for i, (risk, cent, liv) in enumerate(res):
|
|
bg = "CardBG" if i % 2 == 0 else "VLightGray"
|
|
rect(s, cx, cy, cw, 3.5, bg)
|
|
txt(s, cx + 1, cy + 0.4, rc[0] - 1, 2.5, risk, R, 4.5, "DarkText")
|
|
txt(s, cx + rc[0], cy + 0.4, rc[1] - 1, 2.5, cent, R, 4.5, "AlertRed")
|
|
txt(s, cx + rc[0] + rc[1], cy + 0.4, rc[2] - 1, 2.5, liv, R, 4.5, "BrightGreen")
|
|
cy += 3.8
|
|
cy += 3
|
|
|
|
# ── Co-Benefits ──
|
|
txt(s, cx, cy, cw, 3, "CO-BENEFITS", B, 6.5, "MidGray")
|
|
cy += 4
|
|
|
|
coben = [
|
|
("Ecological:", "10\u201320 ha new habitat along rail corridor, integrating with NVCA restoration (78K trees, 2024)"),
|
|
("Economic:", "Unblocks development 2+ years sooner. 3,000 homes \u00d7 $400K = $1.2B housing construction"),
|
|
("Indigenous:", "Working with the watershed aligns with Saugeen Ojibway Nation water stewardship principles"),
|
|
("Energy:", "Local treatment uses 40\u201355% less energy than pumping 53 km. Savings ~$90\u2013130K/node/year"),
|
|
]
|
|
for label, text in coben:
|
|
txt(s, cx, cy, cw, 5,
|
|
f"{label} {text}", R, 5, "DarkText")
|
|
cy += 5.5
|
|
cy += 2
|
|
|
|
# ── Biomimicry Spiral ──
|
|
txt(s, cx, cy, cw, 3, "BIOMIMICRY DESIGN SPIRAL", B, 6.5, "MidGray")
|
|
cy += 4
|
|
|
|
steps = [
|
|
("Define", "Supply 5 towns\ncost-effectively"),
|
|
("Biologize", "How does nature\ndistribute?"),
|
|
("Discover", "Mycorrhizal nets,\nbeavers, wetlands"),
|
|
("Abstract", "Distributed nodes,\nlandscape as infra"),
|
|
("Emulate", "Satellite plants,\nMAR, backbone"),
|
|
("Evaluate", "37\u201356% savings,\n3\u00d7 resilience"),
|
|
]
|
|
stw = (cw - 5) / 6
|
|
for i, (title, desc) in enumerate(steps):
|
|
sx = cx + i * (stw + 1)
|
|
rect(s, sx, cy, stw, 11, "CardBG", "LightGray", 0.15)
|
|
txt(s, sx, cy + 0.5, stw, 2.5, title, B, 4.5, "ForestGreen", 1)
|
|
txt(s, sx, cy + 3.5, stw, 7, desc, R, 4, "MidGray", 1)
|
|
cy += 14
|
|
|
|
# ── Sources ──
|
|
hline(s, cx, cy, cw, "LightGray", 0.3)
|
|
cy += 1.5
|
|
txt(s, cx, cy, cw, 12,
|
|
"Key Sources: Collingwood WTP Class EA (2022); NVCA IWMP (2019); New Tecumseth Master Plan (2016); CFB Borden aquifer studies (U of Waterloo); Region of Waterloo ASR; Fleming College CAWT; SEQ Water Grid (QLD, Australia); Turku Finland MAR; Egerton-Warburton et al., J. Exp. Botany (2007); Ontario Stormwater Mgmt Manual; Biomimicry Institute Design Spiral; BC Wildlife Federation 10,000 Wetlands.",
|
|
R, 4.5, "MidGray")
|
|
|
|
# ═══ EXPORT ═══
|
|
pdf = s.PDFfile()
|
|
pdf.file = output_path
|
|
pdf.quality = 0
|
|
pdf.resolution = dpi
|
|
pdf.version = 14
|
|
pdf.compress = True
|
|
pdf.compressmtd = 0
|
|
pdf.save()
|
|
|
|
s.closeDoc()
|
|
print(f"Exported: {output_path}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|