diff --git a/assets/images/gears/png/filled/chainring_05_filled.png b/assets/images/gears/png/filled/chainring_05_filled.png
new file mode 100644
index 0000000..c3fe5cf
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_05_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_06_filled.png b/assets/images/gears/png/filled/chainring_06_filled.png
new file mode 100644
index 0000000..a195c73
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_06_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_07_filled.png b/assets/images/gears/png/filled/chainring_07_filled.png
new file mode 100644
index 0000000..a6252e3
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_07_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_08_filled.png b/assets/images/gears/png/filled/chainring_08_filled.png
new file mode 100644
index 0000000..b6e1be8
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_08_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_09_filled.png b/assets/images/gears/png/filled/chainring_09_filled.png
new file mode 100644
index 0000000..79871d8
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_09_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_10_filled.png b/assets/images/gears/png/filled/chainring_10_filled.png
new file mode 100644
index 0000000..f262127
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_10_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_11_filled.png b/assets/images/gears/png/filled/chainring_11_filled.png
new file mode 100644
index 0000000..6d966f4
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_11_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_12_filled.png b/assets/images/gears/png/filled/chainring_12_filled.png
new file mode 100644
index 0000000..bc70da4
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_12_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_13_filled.png b/assets/images/gears/png/filled/chainring_13_filled.png
new file mode 100644
index 0000000..d493ee7
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_13_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_14_filled.png b/assets/images/gears/png/filled/chainring_14_filled.png
new file mode 100644
index 0000000..90ff845
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_14_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_15_filled.png b/assets/images/gears/png/filled/chainring_15_filled.png
new file mode 100644
index 0000000..7e0f921
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_15_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_16_filled.png b/assets/images/gears/png/filled/chainring_16_filled.png
new file mode 100644
index 0000000..9bca209
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_16_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_17_filled.png b/assets/images/gears/png/filled/chainring_17_filled.png
new file mode 100644
index 0000000..a64a3a4
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_17_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_18_filled.png b/assets/images/gears/png/filled/chainring_18_filled.png
new file mode 100644
index 0000000..7f1ac5a
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_18_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_19_filled.png b/assets/images/gears/png/filled/chainring_19_filled.png
new file mode 100644
index 0000000..754d64f
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_19_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_20_filled.png b/assets/images/gears/png/filled/chainring_20_filled.png
new file mode 100644
index 0000000..bba642f
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_20_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_21_filled.png b/assets/images/gears/png/filled/chainring_21_filled.png
new file mode 100644
index 0000000..7753924
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_21_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_22_filled.png b/assets/images/gears/png/filled/chainring_22_filled.png
new file mode 100644
index 0000000..8d4dfd1
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_22_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_23_filled.png b/assets/images/gears/png/filled/chainring_23_filled.png
new file mode 100644
index 0000000..ac081b2
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_23_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_24_filled.png b/assets/images/gears/png/filled/chainring_24_filled.png
new file mode 100644
index 0000000..da45571
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_24_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_25_filled.png b/assets/images/gears/png/filled/chainring_25_filled.png
new file mode 100644
index 0000000..2dc89ba
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_25_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_26_filled.png b/assets/images/gears/png/filled/chainring_26_filled.png
new file mode 100644
index 0000000..d321eb9
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_26_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_27_filled.png b/assets/images/gears/png/filled/chainring_27_filled.png
new file mode 100644
index 0000000..8c1a707
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_27_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_28_filled.png b/assets/images/gears/png/filled/chainring_28_filled.png
new file mode 100644
index 0000000..51dbe07
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_28_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_29_filled.png b/assets/images/gears/png/filled/chainring_29_filled.png
new file mode 100644
index 0000000..09a224d
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_29_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_30_filled.png b/assets/images/gears/png/filled/chainring_30_filled.png
new file mode 100644
index 0000000..352ab3c
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_30_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_31_filled.png b/assets/images/gears/png/filled/chainring_31_filled.png
new file mode 100644
index 0000000..68f011b
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_31_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_32_filled.png b/assets/images/gears/png/filled/chainring_32_filled.png
new file mode 100644
index 0000000..d9bc324
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_32_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_33_filled.png b/assets/images/gears/png/filled/chainring_33_filled.png
new file mode 100644
index 0000000..fd3ab33
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_33_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_34_filled.png b/assets/images/gears/png/filled/chainring_34_filled.png
new file mode 100644
index 0000000..0e6005a
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_34_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_35_filled.png b/assets/images/gears/png/filled/chainring_35_filled.png
new file mode 100644
index 0000000..cb54427
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_35_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_36_filled.png b/assets/images/gears/png/filled/chainring_36_filled.png
new file mode 100644
index 0000000..1c4d49c
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_36_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_37_filled.png b/assets/images/gears/png/filled/chainring_37_filled.png
new file mode 100644
index 0000000..f91c893
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_37_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_38_filled.png b/assets/images/gears/png/filled/chainring_38_filled.png
new file mode 100644
index 0000000..8f2264d
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_38_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_39_filled.png b/assets/images/gears/png/filled/chainring_39_filled.png
new file mode 100644
index 0000000..286dff9
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_39_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_40_filled.png b/assets/images/gears/png/filled/chainring_40_filled.png
new file mode 100644
index 0000000..bf5a792
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_40_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_41_filled.png b/assets/images/gears/png/filled/chainring_41_filled.png
new file mode 100644
index 0000000..076dfac
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_41_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_42_filled.png b/assets/images/gears/png/filled/chainring_42_filled.png
new file mode 100644
index 0000000..6a16e15
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_42_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_43_filled.png b/assets/images/gears/png/filled/chainring_43_filled.png
new file mode 100644
index 0000000..efcb22b
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_43_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_44_filled.png b/assets/images/gears/png/filled/chainring_44_filled.png
new file mode 100644
index 0000000..271b9c3
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_44_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_45_filled.png b/assets/images/gears/png/filled/chainring_45_filled.png
new file mode 100644
index 0000000..aebbc68
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_45_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_46_filled.png b/assets/images/gears/png/filled/chainring_46_filled.png
new file mode 100644
index 0000000..b10a502
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_46_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_47_filled.png b/assets/images/gears/png/filled/chainring_47_filled.png
new file mode 100644
index 0000000..b24c821
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_47_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_48_filled.png b/assets/images/gears/png/filled/chainring_48_filled.png
new file mode 100644
index 0000000..27174a0
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_48_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_49_filled.png b/assets/images/gears/png/filled/chainring_49_filled.png
new file mode 100644
index 0000000..e18b686
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_49_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_50_filled.png b/assets/images/gears/png/filled/chainring_50_filled.png
new file mode 100644
index 0000000..5a4457e
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_50_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_51_filled.png b/assets/images/gears/png/filled/chainring_51_filled.png
new file mode 100644
index 0000000..30b189d
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_51_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_52_filled.png b/assets/images/gears/png/filled/chainring_52_filled.png
new file mode 100644
index 0000000..c3fa000
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_52_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_53_filled.png b/assets/images/gears/png/filled/chainring_53_filled.png
new file mode 100644
index 0000000..af75472
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_53_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_54_filled.png b/assets/images/gears/png/filled/chainring_54_filled.png
new file mode 100644
index 0000000..affd4bf
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_54_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_55_filled.png b/assets/images/gears/png/filled/chainring_55_filled.png
new file mode 100644
index 0000000..2dd2463
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_55_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_56_filled.png b/assets/images/gears/png/filled/chainring_56_filled.png
new file mode 100644
index 0000000..ee151d1
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_56_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_57_filled.png b/assets/images/gears/png/filled/chainring_57_filled.png
new file mode 100644
index 0000000..08adfcb
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_57_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_58_filled.png b/assets/images/gears/png/filled/chainring_58_filled.png
new file mode 100644
index 0000000..b37a7bf
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_58_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_59_filled.png b/assets/images/gears/png/filled/chainring_59_filled.png
new file mode 100644
index 0000000..05aabc4
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_59_filled.png differ
diff --git a/assets/images/gears/png/filled/chainring_60_filled.png b/assets/images/gears/png/filled/chainring_60_filled.png
new file mode 100644
index 0000000..7730191
Binary files /dev/null and b/assets/images/gears/png/filled/chainring_60_filled.png differ
diff --git a/chainring_generator/generate_chainrings.py b/chainring_generator/generate_chainrings.py
new file mode 100644
index 0000000..72ea707
--- /dev/null
+++ b/chainring_generator/generate_chainrings.py
@@ -0,0 +1,492 @@
+from __future__ import annotations
+
+import argparse
+import csv
+import math
+import shutil
+import subprocess
+from dataclasses import dataclass
+from pathlib import Path
+from tempfile import TemporaryDirectory
+
+import numpy as np
+from PIL import Image, ImageOps
+
+
+@dataclass(frozen=True)
+class Canvas:
+ width: int = 588
+ height: int = 495
+ cx: float = 304.0
+ cy: float = 256.0
+
+
+@dataclass(frozen=True)
+class Style:
+ background: str = "#3d3d3d"
+ material: str = "#eeeeee"
+ outline: str = "#f4f4f4"
+ outline_width: float = 3.0
+
+
+@dataclass(frozen=True)
+class Geometry:
+ root_radius: float = 213.0
+ tooth_depth: float = 17.5
+ reference_teeth: int = 60
+ pitch_radius_offset: float = 8.0
+ tooth_sharpness: float = 1.32
+ low_tooth_threshold: int = 25
+ low_tooth_half_width: float = 0.30
+ low_tooth_cusp: float = 0.55
+ central_hole_radius: float = 46.0
+ bolt_hole_radius: float = 16.5
+ material_margin: float = 8.0
+ bolt_hole_min_teeth: int = 25
+ decorative_cutout_min_scale: float = 0.62
+ samples_per_tooth: int = 28
+
+
+@dataclass(frozen=True)
+class SprocketSizing:
+ pitch_radius: float
+ calculated_root_radius: float
+ root_radius: float
+ tooth_depth: float
+ outer_radius: float
+ decorative_scale: float
+ bolt_scale: float
+ include_bolt_holes: bool
+ include_decorative_cutouts: bool
+
+
+@dataclass(frozen=True)
+class Cutout:
+ kind: str
+ cx: float
+ cy: float
+ rx: float
+ ry: float | None = None
+ rotation: float = 0.0
+
+
+CANVAS = Canvas()
+STYLE = Style()
+GEOMETRY = Geometry()
+
+
+def fmt(value: float) -> str:
+ return f"{value:.3f}".rstrip("0").rstrip(".")
+
+
+def polygon_path(points: list[tuple[float, float]]) -> str:
+ start = points[0]
+ rest = points[1:]
+ commands = [f"M {fmt(start[0])} {fmt(start[1])}"]
+ commands.extend(f"L {fmt(x)} {fmt(y)}" for x, y in rest)
+ commands.append("Z")
+ return " ".join(commands)
+
+
+def circle_points(cx: float, cy: float, radius: float, samples: int = 144) -> list[tuple[float, float]]:
+ return [
+ (cx + math.cos(t) * radius, cy + math.sin(t) * radius)
+ for t in np.linspace(0.0, 2.0 * math.pi, samples, endpoint=False)
+ ]
+
+
+def ellipse_points(
+ cx: float,
+ cy: float,
+ rx: float,
+ ry: float,
+ rotation_deg: float = 0.0,
+ samples: int = 192,
+) -> list[tuple[float, float]]:
+ rotation = math.radians(rotation_deg)
+ cos_r = math.cos(rotation)
+ sin_r = math.sin(rotation)
+ points = []
+ for t in np.linspace(0.0, 2.0 * math.pi, samples, endpoint=False):
+ x = math.cos(t) * rx
+ y = math.sin(t) * ry
+ points.append((cx + x * cos_r - y * sin_r, cy + x * sin_r + y * cos_r))
+ return points
+
+
+DECORATIVE_CUTOUT_SPECS = (
+ ("ellipse", 0.0, -141.0, 121.0, 41.0, 0.0),
+ ("ellipse", -109.0, 58.0, 106.0, 41.0, 63.0),
+ ("ellipse", 109.0, 58.0, 106.0, 41.0, -63.0),
+)
+
+BOLT_HOLE_OFFSETS = (
+ (-134.0, -76.0),
+ (134.0, -76.0),
+ (0.0, 157.0),
+)
+
+REFERENCE_BOLT_OFFSET_RADIUS = max(math.hypot(dx, dy) for dx, dy in BOLT_HOLE_OFFSETS)
+
+
+def decorative_reference_extent() -> float:
+ extent = 0.0
+ for _, dx, dy, rx, ry, rotation in DECORATIVE_CUTOUT_SPECS:
+ points = ellipse_points(dx, dy, rx, ry, rotation, 240)
+ extent = max(extent, *(math.hypot(x, y) for x, y in points))
+ return extent
+
+
+DECORATIVE_REFERENCE_EXTENT = decorative_reference_extent()
+
+
+def clamp(value: float, minimum: float, maximum: float) -> float:
+ return max(minimum, min(maximum, value))
+
+
+def calculated_pitch_radius(teeth: int, geometry: Geometry = GEOMETRY) -> float:
+ reference_pitch_radius = geometry.root_radius + geometry.pitch_radius_offset
+ return reference_pitch_radius * math.sin(math.pi / geometry.reference_teeth) / math.sin(math.pi / teeth)
+
+
+def sprocket_sizing(teeth: int, geometry: Geometry = GEOMETRY) -> SprocketSizing:
+ pitch_radius = calculated_pitch_radius(teeth, geometry)
+ calculated_root_radius = pitch_radius - geometry.pitch_radius_offset
+ central_min_root_radius = geometry.central_hole_radius + geometry.material_margin
+
+ include_bolt_holes = teeth >= geometry.bolt_hole_min_teeth
+ if include_bolt_holes:
+ bolt_min_radius = geometry.central_hole_radius + geometry.bolt_hole_radius + geometry.material_margin
+ bolt_min_root_radius = bolt_min_radius + geometry.bolt_hole_radius + geometry.material_margin
+ min_root_radius = max(central_min_root_radius, bolt_min_root_radius)
+ else:
+ min_root_radius = central_min_root_radius
+
+ root_radius = max(calculated_root_radius, min_root_radius)
+ outer_radius = root_radius + geometry.tooth_depth
+
+ max_bolt_offset = max(0.0, root_radius - geometry.bolt_hole_radius - geometry.material_margin)
+ if include_bolt_holes:
+ min_bolt_offset = geometry.central_hole_radius + geometry.bolt_hole_radius + geometry.material_margin
+ bolt_offset = clamp(max_bolt_offset, min_bolt_offset, REFERENCE_BOLT_OFFSET_RADIUS)
+ bolt_scale = bolt_offset / REFERENCE_BOLT_OFFSET_RADIUS
+ else:
+ bolt_scale = 0.0
+
+ decorative_scale = clamp((root_radius - geometry.material_margin) / DECORATIVE_REFERENCE_EXTENT, 0.0, 1.0)
+ include_decorative_cutouts = decorative_scale >= geometry.decorative_cutout_min_scale
+
+ return SprocketSizing(
+ pitch_radius=pitch_radius,
+ calculated_root_radius=calculated_root_radius,
+ root_radius=root_radius,
+ tooth_depth=geometry.tooth_depth,
+ outer_radius=outer_radius,
+ decorative_scale=decorative_scale,
+ bolt_scale=bolt_scale,
+ include_bolt_holes=include_bolt_holes,
+ include_decorative_cutouts=include_decorative_cutouts,
+ )
+
+
+def outer_points(
+ teeth: int,
+ geometry: Geometry = GEOMETRY,
+ canvas: Canvas = CANVAS,
+ radius_delta: float = 0.0,
+) -> list[tuple[float, float]]:
+ samples = teeth * geometry.samples_per_tooth
+ sizing = sprocket_sizing(teeth, geometry)
+ points = []
+ phase_offset = -math.pi / 2.0
+
+ for index in range(samples):
+ theta = phase_offset + 2.0 * math.pi * index / samples
+ tooth_phase = (index % geometry.samples_per_tooth) / geometry.samples_per_tooth
+ radius = sizing.root_radius + sizing.tooth_depth * tooth_profile(teeth, tooth_phase, geometry) + radius_delta
+ points.append((canvas.cx + math.cos(theta) * radius, canvas.cy + math.sin(theta) * radius))
+
+ return points
+
+
+def tooth_profile(teeth: int, phase: float, geometry: Geometry = GEOMETRY) -> float:
+ if teeth < geometry.low_tooth_threshold:
+ distance_from_peak = abs(phase - 0.5)
+ if distance_from_peak >= geometry.low_tooth_half_width:
+ return 0.0
+ return 1.0 - (distance_from_peak / geometry.low_tooth_half_width) ** geometry.low_tooth_cusp
+
+ smooth_profile = 0.5 - 0.5 * math.cos(2.0 * math.pi * phase)
+ return smooth_profile**geometry.tooth_sharpness
+
+
+def offset_cutout(
+ kind: str,
+ dx: float,
+ dy: float,
+ rx: float,
+ ry: float | None,
+ rotation: float,
+ center_scale: float,
+ radius_scale: float,
+ canvas: Canvas = CANVAS,
+) -> Cutout:
+ scaled_ry = None if ry is None else ry * radius_scale
+ return Cutout(
+ kind,
+ canvas.cx + dx * center_scale,
+ canvas.cy + dy * center_scale,
+ rx * radius_scale,
+ scaled_ry,
+ rotation,
+ )
+
+
+def cutouts_for_teeth(teeth: int, canvas: Canvas = CANVAS, geometry: Geometry = GEOMETRY) -> list[Cutout]:
+ sizing = sprocket_sizing(teeth, geometry)
+ return [
+ *(
+ offset_cutout(kind, dx, dy, rx, ry, rotation, sizing.decorative_scale, sizing.decorative_scale, canvas)
+ for kind, dx, dy, rx, ry, rotation in DECORATIVE_CUTOUT_SPECS
+ if sizing.include_decorative_cutouts
+ ),
+ offset_cutout("circle", 0.0, 0.0, geometry.central_hole_radius, None, 0.0, 1.0, 1.0, canvas),
+ *(
+ offset_cutout("circle", dx, dy, geometry.bolt_hole_radius, None, 0.0, sizing.bolt_scale, 1.0, canvas)
+ for dx, dy in BOLT_HOLE_OFFSETS
+ if sizing.include_bolt_holes
+ ),
+ ]
+
+
+def cutout_path(cutout: Cutout, delta: float = 0.0, reverse: bool = False) -> str:
+ if cutout.kind == "circle":
+ points = circle_points(cutout.cx, cutout.cy, cutout.rx + delta, 160)
+ elif cutout.kind == "ellipse":
+ assert cutout.ry is not None
+ points = ellipse_points(
+ cutout.cx,
+ cutout.cy,
+ cutout.rx + delta,
+ cutout.ry + delta,
+ cutout.rotation,
+ 240,
+ )
+ else:
+ raise ValueError(f"Unsupported cutout kind: {cutout.kind}")
+
+ if reverse:
+ points = list(reversed(points))
+ return polygon_path(points)
+
+
+def cutout_paths_for_teeth(teeth: int, canvas: Canvas = CANVAS) -> list[str]:
+ return [cutout_path(cutout) for cutout in cutouts_for_teeth(teeth, canvas)]
+
+
+def filled_svg(teeth: int, canvas: Canvas = CANVAS, style: Style = STYLE) -> str:
+ outer = polygon_path(outer_points(teeth))
+ cutouts = " ".join(cutout_paths_for_teeth(teeth, canvas))
+ return f"""
+"""
+
+
+def outline_svg(teeth: int, canvas: Canvas = CANVAS, style: Style = STYLE) -> str:
+ half_width = style.outline_width / 2.0
+ outer = polygon_path(outer_points(teeth, radius_delta=half_width))
+ inner = polygon_path(list(reversed(outer_points(teeth, radius_delta=-half_width))))
+ paths = [f"{outer} {inner}"]
+ for cutout in cutouts_for_teeth(teeth, canvas):
+ paths.append(f"{cutout_path(cutout, half_width)} {cutout_path(cutout, -half_width, reverse=True)}")
+
+ path_elements = "\n ".join(
+ f''
+ for path in paths
+ )
+ return f"""
+"""
+
+
+def render_png(svg_path: Path, png_path: Path) -> None:
+ magick = shutil.which("magick") or shutil.which("convert")
+ if magick is None:
+ raise RuntimeError("ImageMagick is required for SVG-to-PNG conversion, but neither 'magick' nor 'convert' is on PATH.")
+
+ png_path.parent.mkdir(parents=True, exist_ok=True)
+ command = [
+ magick,
+ "-background",
+ "none",
+ "-density",
+ "192",
+ str(svg_path),
+ "-resize",
+ f"{CANVAS.width}x{CANVAS.height}!",
+ str(png_path),
+ ]
+ subprocess.run(command, check=True)
+
+
+def write_asset(teeth: int, variant: str, svg_root: Path, png_root: Path, png: bool = True) -> tuple[Path, Path | None]:
+ if variant == "filled":
+ svg_text = filled_svg(teeth)
+ elif variant == "outline":
+ svg_text = outline_svg(teeth)
+ else:
+ raise ValueError(f"Unsupported variant: {variant}")
+
+ stem = f"chainring_{teeth:02d}_{variant}"
+ svg_path = svg_root / variant / f"{stem}.svg"
+ png_path = png_root / variant / f"{stem}.png"
+ svg_path.parent.mkdir(parents=True, exist_ok=True)
+ svg_path.write_text(svg_text, encoding="utf-8")
+
+ if png:
+ render_png(svg_path, png_path)
+ return svg_path, png_path
+
+ return svg_path, None
+
+
+def generate_all(min_teeth: int, max_teeth: int, output_dir: Path, png: bool = True) -> list[Path]:
+ svg_root = output_dir / "svg"
+ png_root = output_dir / "png"
+ png_paths: list[Path] = []
+
+ for teeth in range(min_teeth, max_teeth + 1):
+ for variant in ("filled", "outline"):
+ _, png_path = write_asset(teeth, variant, svg_root, png_root, png=png)
+ if png_path is not None:
+ png_paths.append(png_path)
+
+ return png_paths
+
+
+def write_radii_manifest(min_teeth: int, max_teeth: int, output_path: Path) -> None:
+ output_path.parent.mkdir(parents=True, exist_ok=True)
+ fields = [
+ "teeth",
+ "pitch_radius_px",
+ "calculated_root_radius_px",
+ "root_radius_px",
+ "tooth_depth_px",
+ "outer_radius_px",
+ "bolt_scale",
+ "decorative_scale",
+ "bolt_holes",
+ "decorative_cutouts",
+ ]
+ with output_path.open("w", newline="", encoding="utf-8") as handle:
+ writer = csv.DictWriter(handle, fieldnames=fields)
+ writer.writeheader()
+ for teeth in range(min_teeth, max_teeth + 1):
+ sizing = sprocket_sizing(teeth)
+ writer.writerow(
+ {
+ "teeth": teeth,
+ "pitch_radius_px": f"{sizing.pitch_radius:.3f}",
+ "calculated_root_radius_px": f"{sizing.calculated_root_radius:.3f}",
+ "root_radius_px": f"{sizing.root_radius:.3f}",
+ "tooth_depth_px": f"{sizing.tooth_depth:.3f}",
+ "outer_radius_px": f"{sizing.outer_radius:.3f}",
+ "bolt_scale": f"{sizing.bolt_scale:.3f}",
+ "decorative_scale": f"{sizing.decorative_scale:.3f}",
+ "bolt_holes": int(sizing.include_bolt_holes),
+ "decorative_cutouts": int(sizing.include_decorative_cutouts),
+ }
+ )
+
+
+def make_contact_sheet(png_paths: list[Path], output_path: Path, columns: int = 6) -> None:
+ if not png_paths:
+ return
+
+ thumbs = []
+ thumb_size = (196, 165)
+ for path in png_paths:
+ image = Image.open(path).convert("RGBA")
+ image.thumbnail(thumb_size, Image.Resampling.LANCZOS)
+ tile = Image.new("RGBA", thumb_size, (61, 61, 61, 255))
+ x = (thumb_size[0] - image.width) // 2
+ y = (thumb_size[1] - image.height) // 2
+ tile.alpha_composite(image, (x, y))
+ thumbs.append(tile)
+
+ rows = math.ceil(len(thumbs) / columns)
+ sheet = Image.new("RGBA", (columns * thumb_size[0], rows * thumb_size[1]), (36, 36, 36, 255))
+ for index, thumb in enumerate(thumbs):
+ x = (index % columns) * thumb_size[0]
+ y = (index // columns) * thumb_size[1]
+ sheet.alpha_composite(thumb, (x, y))
+
+ output_path.parent.mkdir(parents=True, exist_ok=True)
+ sheet.save(output_path)
+
+
+def compare_to_reference(candidate_path: Path, reference_path: Path) -> dict[str, float]:
+ candidate = Image.open(candidate_path).convert("L")
+ reference = Image.open(reference_path).convert("L").resize(candidate.size, Image.Resampling.LANCZOS)
+
+ candidate_arr = np.asarray(ImageOps.autocontrast(candidate), dtype=np.float32) / 255.0
+ reference_arr = np.asarray(ImageOps.autocontrast(reference), dtype=np.float32) / 255.0
+ diff = candidate_arr - reference_arr
+
+ candidate_edges = np.abs(np.gradient(candidate_arr)[0]) + np.abs(np.gradient(candidate_arr)[1])
+ reference_edges = np.abs(np.gradient(reference_arr)[0]) + np.abs(np.gradient(reference_arr)[1])
+ edge_diff = candidate_edges - reference_edges
+
+ return {
+ "mse": float(np.mean(diff**2)),
+ "mae": float(np.mean(np.abs(diff))),
+ "edge_mae": float(np.mean(np.abs(edge_diff))),
+ }
+
+
+def parse_args() -> argparse.Namespace:
+ parser = argparse.ArgumentParser(description="Generate chainring SVG and PNG assets with fixed reference-like inner cutouts.")
+ parser.add_argument("--min-teeth", type=int, default=5)
+ parser.add_argument("--max-teeth", type=int, default=60)
+ parser.add_argument("--output", type=Path, default=Path("out"))
+ parser.add_argument("--no-png", action="store_true")
+ parser.add_argument("--contact-sheet", action="store_true")
+ parser.add_argument("--reference", type=Path, help="Optional local reference image for reporting similarity metrics.")
+ parser.add_argument("--reference-teeth", type=int, default=44)
+ return parser.parse_args()
+
+
+def main() -> None:
+ args = parse_args()
+ if args.min_teeth < 3:
+ raise ValueError("--min-teeth must be at least 3.")
+ if args.max_teeth < args.min_teeth:
+ raise ValueError("--max-teeth must be greater than or equal to --min-teeth.")
+
+ png_paths = generate_all(args.min_teeth, args.max_teeth, args.output, png=not args.no_png)
+ write_radii_manifest(args.min_teeth, args.max_teeth, args.output / "radii.csv")
+
+ if args.contact_sheet and png_paths:
+ filled_samples = [path for path in png_paths if path.parent.name == "filled"]
+ outline_samples = [path for path in png_paths if path.parent.name == "outline"]
+ make_contact_sheet(filled_samples, args.output / "contact_sheet_filled.png")
+ make_contact_sheet(outline_samples, args.output / "contact_sheet_outline.png")
+
+ if args.reference and not args.no_png:
+ with TemporaryDirectory() as tmp:
+ tmp_root = Path(tmp)
+ _, candidate = write_asset(args.reference_teeth, "filled", tmp_root / "svg", tmp_root / "png", png=True)
+ assert candidate is not None
+ metrics = compare_to_reference(candidate, args.reference)
+ print(
+ "reference_metrics "
+ + " ".join(f"{name}={value:.6f}" for name, value in metrics.items())
+ )
+
+ print(f"generated teeth={args.min_teeth}..{args.max_teeth} output={args.output}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/chainring_generator/out/contact_sheet_filled.png b/chainring_generator/out/contact_sheet_filled.png
new file mode 100644
index 0000000..eab5182
Binary files /dev/null and b/chainring_generator/out/contact_sheet_filled.png differ
diff --git a/chainring_generator/out/contact_sheet_outline.png b/chainring_generator/out/contact_sheet_outline.png
new file mode 100644
index 0000000..f8a745c
Binary files /dev/null and b/chainring_generator/out/contact_sheet_outline.png differ
diff --git a/chainring_generator/out/png/filled/chainring_05_filled.png b/chainring_generator/out/png/filled/chainring_05_filled.png
new file mode 100644
index 0000000..573eef4
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_05_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_06_filled.png b/chainring_generator/out/png/filled/chainring_06_filled.png
new file mode 100644
index 0000000..8345b4a
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_06_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_07_filled.png b/chainring_generator/out/png/filled/chainring_07_filled.png
new file mode 100644
index 0000000..f0e1ec6
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_07_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_08_filled.png b/chainring_generator/out/png/filled/chainring_08_filled.png
new file mode 100644
index 0000000..c6896fc
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_08_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_09_filled.png b/chainring_generator/out/png/filled/chainring_09_filled.png
new file mode 100644
index 0000000..911ac0f
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_09_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_10_filled.png b/chainring_generator/out/png/filled/chainring_10_filled.png
new file mode 100644
index 0000000..9457e80
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_10_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_11_filled.png b/chainring_generator/out/png/filled/chainring_11_filled.png
new file mode 100644
index 0000000..bb2a86a
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_11_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_12_filled.png b/chainring_generator/out/png/filled/chainring_12_filled.png
new file mode 100644
index 0000000..b59a140
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_12_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_13_filled.png b/chainring_generator/out/png/filled/chainring_13_filled.png
new file mode 100644
index 0000000..27c81fb
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_13_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_14_filled.png b/chainring_generator/out/png/filled/chainring_14_filled.png
new file mode 100644
index 0000000..36ccf96
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_14_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_15_filled.png b/chainring_generator/out/png/filled/chainring_15_filled.png
new file mode 100644
index 0000000..2192367
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_15_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_16_filled.png b/chainring_generator/out/png/filled/chainring_16_filled.png
new file mode 100644
index 0000000..532d53d
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_16_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_17_filled.png b/chainring_generator/out/png/filled/chainring_17_filled.png
new file mode 100644
index 0000000..c079f66
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_17_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_18_filled.png b/chainring_generator/out/png/filled/chainring_18_filled.png
new file mode 100644
index 0000000..0eade72
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_18_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_19_filled.png b/chainring_generator/out/png/filled/chainring_19_filled.png
new file mode 100644
index 0000000..0362994
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_19_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_20_filled.png b/chainring_generator/out/png/filled/chainring_20_filled.png
new file mode 100644
index 0000000..d8a2806
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_20_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_21_filled.png b/chainring_generator/out/png/filled/chainring_21_filled.png
new file mode 100644
index 0000000..bebc538
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_21_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_22_filled.png b/chainring_generator/out/png/filled/chainring_22_filled.png
new file mode 100644
index 0000000..3428441
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_22_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_23_filled.png b/chainring_generator/out/png/filled/chainring_23_filled.png
new file mode 100644
index 0000000..78fb9a9
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_23_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_24_filled.png b/chainring_generator/out/png/filled/chainring_24_filled.png
new file mode 100644
index 0000000..51c95b0
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_24_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_25_filled.png b/chainring_generator/out/png/filled/chainring_25_filled.png
new file mode 100644
index 0000000..fb72398
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_25_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_26_filled.png b/chainring_generator/out/png/filled/chainring_26_filled.png
new file mode 100644
index 0000000..b00f74b
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_26_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_27_filled.png b/chainring_generator/out/png/filled/chainring_27_filled.png
new file mode 100644
index 0000000..4cf31eb
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_27_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_28_filled.png b/chainring_generator/out/png/filled/chainring_28_filled.png
new file mode 100644
index 0000000..17a2f01
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_28_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_29_filled.png b/chainring_generator/out/png/filled/chainring_29_filled.png
new file mode 100644
index 0000000..58d3a2a
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_29_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_30_filled.png b/chainring_generator/out/png/filled/chainring_30_filled.png
new file mode 100644
index 0000000..988162e
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_30_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_31_filled.png b/chainring_generator/out/png/filled/chainring_31_filled.png
new file mode 100644
index 0000000..11dd068
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_31_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_32_filled.png b/chainring_generator/out/png/filled/chainring_32_filled.png
new file mode 100644
index 0000000..60dfa1e
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_32_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_33_filled.png b/chainring_generator/out/png/filled/chainring_33_filled.png
new file mode 100644
index 0000000..5f39870
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_33_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_34_filled.png b/chainring_generator/out/png/filled/chainring_34_filled.png
new file mode 100644
index 0000000..c85fe93
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_34_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_35_filled.png b/chainring_generator/out/png/filled/chainring_35_filled.png
new file mode 100644
index 0000000..e62a73f
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_35_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_36_filled.png b/chainring_generator/out/png/filled/chainring_36_filled.png
new file mode 100644
index 0000000..13d035a
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_36_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_37_filled.png b/chainring_generator/out/png/filled/chainring_37_filled.png
new file mode 100644
index 0000000..324bdb6
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_37_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_38_filled.png b/chainring_generator/out/png/filled/chainring_38_filled.png
new file mode 100644
index 0000000..0375d28
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_38_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_39_filled.png b/chainring_generator/out/png/filled/chainring_39_filled.png
new file mode 100644
index 0000000..e52784b
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_39_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_40_filled.png b/chainring_generator/out/png/filled/chainring_40_filled.png
new file mode 100644
index 0000000..bd824a8
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_40_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_41_filled.png b/chainring_generator/out/png/filled/chainring_41_filled.png
new file mode 100644
index 0000000..584850e
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_41_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_42_filled.png b/chainring_generator/out/png/filled/chainring_42_filled.png
new file mode 100644
index 0000000..c91343f
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_42_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_43_filled.png b/chainring_generator/out/png/filled/chainring_43_filled.png
new file mode 100644
index 0000000..676c16c
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_43_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_44_filled.png b/chainring_generator/out/png/filled/chainring_44_filled.png
new file mode 100644
index 0000000..5004874
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_44_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_45_filled.png b/chainring_generator/out/png/filled/chainring_45_filled.png
new file mode 100644
index 0000000..8601530
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_45_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_46_filled.png b/chainring_generator/out/png/filled/chainring_46_filled.png
new file mode 100644
index 0000000..da80297
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_46_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_47_filled.png b/chainring_generator/out/png/filled/chainring_47_filled.png
new file mode 100644
index 0000000..c76b09c
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_47_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_48_filled.png b/chainring_generator/out/png/filled/chainring_48_filled.png
new file mode 100644
index 0000000..6cab4cb
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_48_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_49_filled.png b/chainring_generator/out/png/filled/chainring_49_filled.png
new file mode 100644
index 0000000..d705937
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_49_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_50_filled.png b/chainring_generator/out/png/filled/chainring_50_filled.png
new file mode 100644
index 0000000..d27fbc8
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_50_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_51_filled.png b/chainring_generator/out/png/filled/chainring_51_filled.png
new file mode 100644
index 0000000..45866a4
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_51_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_52_filled.png b/chainring_generator/out/png/filled/chainring_52_filled.png
new file mode 100644
index 0000000..93df2ad
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_52_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_53_filled.png b/chainring_generator/out/png/filled/chainring_53_filled.png
new file mode 100644
index 0000000..0d70bcd
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_53_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_54_filled.png b/chainring_generator/out/png/filled/chainring_54_filled.png
new file mode 100644
index 0000000..3d5f9d3
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_54_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_55_filled.png b/chainring_generator/out/png/filled/chainring_55_filled.png
new file mode 100644
index 0000000..58b42ef
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_55_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_56_filled.png b/chainring_generator/out/png/filled/chainring_56_filled.png
new file mode 100644
index 0000000..2538ba3
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_56_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_57_filled.png b/chainring_generator/out/png/filled/chainring_57_filled.png
new file mode 100644
index 0000000..708e86b
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_57_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_58_filled.png b/chainring_generator/out/png/filled/chainring_58_filled.png
new file mode 100644
index 0000000..07e1d5c
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_58_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_59_filled.png b/chainring_generator/out/png/filled/chainring_59_filled.png
new file mode 100644
index 0000000..a648722
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_59_filled.png differ
diff --git a/chainring_generator/out/png/filled/chainring_60_filled.png b/chainring_generator/out/png/filled/chainring_60_filled.png
new file mode 100644
index 0000000..3c3c7b1
Binary files /dev/null and b/chainring_generator/out/png/filled/chainring_60_filled.png differ
diff --git a/chainring_generator/out/png/outline/chainring_05_outline.png b/chainring_generator/out/png/outline/chainring_05_outline.png
new file mode 100644
index 0000000..1d8250e
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_05_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_06_outline.png b/chainring_generator/out/png/outline/chainring_06_outline.png
new file mode 100644
index 0000000..7cda1e8
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_06_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_07_outline.png b/chainring_generator/out/png/outline/chainring_07_outline.png
new file mode 100644
index 0000000..31ab514
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_07_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_08_outline.png b/chainring_generator/out/png/outline/chainring_08_outline.png
new file mode 100644
index 0000000..d22c9ec
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_08_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_09_outline.png b/chainring_generator/out/png/outline/chainring_09_outline.png
new file mode 100644
index 0000000..fffa82e
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_09_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_10_outline.png b/chainring_generator/out/png/outline/chainring_10_outline.png
new file mode 100644
index 0000000..06a27ab
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_10_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_11_outline.png b/chainring_generator/out/png/outline/chainring_11_outline.png
new file mode 100644
index 0000000..3aad9a3
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_11_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_12_outline.png b/chainring_generator/out/png/outline/chainring_12_outline.png
new file mode 100644
index 0000000..ed22034
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_12_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_13_outline.png b/chainring_generator/out/png/outline/chainring_13_outline.png
new file mode 100644
index 0000000..b4eab59
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_13_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_14_outline.png b/chainring_generator/out/png/outline/chainring_14_outline.png
new file mode 100644
index 0000000..f3d8927
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_14_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_15_outline.png b/chainring_generator/out/png/outline/chainring_15_outline.png
new file mode 100644
index 0000000..6b32e1b
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_15_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_16_outline.png b/chainring_generator/out/png/outline/chainring_16_outline.png
new file mode 100644
index 0000000..9ad324d
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_16_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_17_outline.png b/chainring_generator/out/png/outline/chainring_17_outline.png
new file mode 100644
index 0000000..4bd1f31
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_17_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_18_outline.png b/chainring_generator/out/png/outline/chainring_18_outline.png
new file mode 100644
index 0000000..f5110cf
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_18_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_19_outline.png b/chainring_generator/out/png/outline/chainring_19_outline.png
new file mode 100644
index 0000000..3b95be6
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_19_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_20_outline.png b/chainring_generator/out/png/outline/chainring_20_outline.png
new file mode 100644
index 0000000..79b1317
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_20_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_21_outline.png b/chainring_generator/out/png/outline/chainring_21_outline.png
new file mode 100644
index 0000000..05608a8
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_21_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_22_outline.png b/chainring_generator/out/png/outline/chainring_22_outline.png
new file mode 100644
index 0000000..911e0fe
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_22_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_23_outline.png b/chainring_generator/out/png/outline/chainring_23_outline.png
new file mode 100644
index 0000000..1fb6cf6
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_23_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_24_outline.png b/chainring_generator/out/png/outline/chainring_24_outline.png
new file mode 100644
index 0000000..5285305
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_24_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_25_outline.png b/chainring_generator/out/png/outline/chainring_25_outline.png
new file mode 100644
index 0000000..5ddb784
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_25_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_26_outline.png b/chainring_generator/out/png/outline/chainring_26_outline.png
new file mode 100644
index 0000000..4e08b88
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_26_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_27_outline.png b/chainring_generator/out/png/outline/chainring_27_outline.png
new file mode 100644
index 0000000..2f8d191
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_27_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_28_outline.png b/chainring_generator/out/png/outline/chainring_28_outline.png
new file mode 100644
index 0000000..c9b3fd3
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_28_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_29_outline.png b/chainring_generator/out/png/outline/chainring_29_outline.png
new file mode 100644
index 0000000..7c04686
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_29_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_30_outline.png b/chainring_generator/out/png/outline/chainring_30_outline.png
new file mode 100644
index 0000000..4366899
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_30_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_31_outline.png b/chainring_generator/out/png/outline/chainring_31_outline.png
new file mode 100644
index 0000000..28afc05
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_31_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_32_outline.png b/chainring_generator/out/png/outline/chainring_32_outline.png
new file mode 100644
index 0000000..196ec28
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_32_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_33_outline.png b/chainring_generator/out/png/outline/chainring_33_outline.png
new file mode 100644
index 0000000..36b595d
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_33_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_34_outline.png b/chainring_generator/out/png/outline/chainring_34_outline.png
new file mode 100644
index 0000000..15bad48
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_34_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_35_outline.png b/chainring_generator/out/png/outline/chainring_35_outline.png
new file mode 100644
index 0000000..d34e652
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_35_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_36_outline.png b/chainring_generator/out/png/outline/chainring_36_outline.png
new file mode 100644
index 0000000..fd7b16f
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_36_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_37_outline.png b/chainring_generator/out/png/outline/chainring_37_outline.png
new file mode 100644
index 0000000..6e33c33
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_37_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_38_outline.png b/chainring_generator/out/png/outline/chainring_38_outline.png
new file mode 100644
index 0000000..72030d6
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_38_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_39_outline.png b/chainring_generator/out/png/outline/chainring_39_outline.png
new file mode 100644
index 0000000..fdeae4a
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_39_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_40_outline.png b/chainring_generator/out/png/outline/chainring_40_outline.png
new file mode 100644
index 0000000..19c1d0a
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_40_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_41_outline.png b/chainring_generator/out/png/outline/chainring_41_outline.png
new file mode 100644
index 0000000..4370a70
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_41_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_42_outline.png b/chainring_generator/out/png/outline/chainring_42_outline.png
new file mode 100644
index 0000000..457ea26
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_42_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_43_outline.png b/chainring_generator/out/png/outline/chainring_43_outline.png
new file mode 100644
index 0000000..d403860
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_43_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_44_outline.png b/chainring_generator/out/png/outline/chainring_44_outline.png
new file mode 100644
index 0000000..b63929d
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_44_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_45_outline.png b/chainring_generator/out/png/outline/chainring_45_outline.png
new file mode 100644
index 0000000..fe317de
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_45_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_46_outline.png b/chainring_generator/out/png/outline/chainring_46_outline.png
new file mode 100644
index 0000000..beb99bd
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_46_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_47_outline.png b/chainring_generator/out/png/outline/chainring_47_outline.png
new file mode 100644
index 0000000..6bd1d6e
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_47_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_48_outline.png b/chainring_generator/out/png/outline/chainring_48_outline.png
new file mode 100644
index 0000000..8de7e64
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_48_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_49_outline.png b/chainring_generator/out/png/outline/chainring_49_outline.png
new file mode 100644
index 0000000..49ec37e
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_49_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_50_outline.png b/chainring_generator/out/png/outline/chainring_50_outline.png
new file mode 100644
index 0000000..1ba0ebc
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_50_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_51_outline.png b/chainring_generator/out/png/outline/chainring_51_outline.png
new file mode 100644
index 0000000..389483a
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_51_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_52_outline.png b/chainring_generator/out/png/outline/chainring_52_outline.png
new file mode 100644
index 0000000..9ea77d6
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_52_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_53_outline.png b/chainring_generator/out/png/outline/chainring_53_outline.png
new file mode 100644
index 0000000..5a1f15e
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_53_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_54_outline.png b/chainring_generator/out/png/outline/chainring_54_outline.png
new file mode 100644
index 0000000..549a2c7
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_54_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_55_outline.png b/chainring_generator/out/png/outline/chainring_55_outline.png
new file mode 100644
index 0000000..b34bdfd
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_55_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_56_outline.png b/chainring_generator/out/png/outline/chainring_56_outline.png
new file mode 100644
index 0000000..3355b10
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_56_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_57_outline.png b/chainring_generator/out/png/outline/chainring_57_outline.png
new file mode 100644
index 0000000..ab9b095
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_57_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_58_outline.png b/chainring_generator/out/png/outline/chainring_58_outline.png
new file mode 100644
index 0000000..868b1af
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_58_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_59_outline.png b/chainring_generator/out/png/outline/chainring_59_outline.png
new file mode 100644
index 0000000..2848d15
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_59_outline.png differ
diff --git a/chainring_generator/out/png/outline/chainring_60_outline.png b/chainring_generator/out/png/outline/chainring_60_outline.png
new file mode 100644
index 0000000..a722040
Binary files /dev/null and b/chainring_generator/out/png/outline/chainring_60_outline.png differ
diff --git a/chainring_generator/out/radii.csv b/chainring_generator/out/radii.csv
new file mode 100644
index 0000000..25096aa
--- /dev/null
+++ b/chainring_generator/out/radii.csv
@@ -0,0 +1,57 @@
+teeth,pitch_radius_px,calculated_root_radius_px,root_radius_px,tooth_depth_px,outer_radius_px,bolt_scale,decorative_scale,bolt_holes,decorative_cutouts
+5,19.678,11.678,54.000,17.500,71.500,0.000,0.239,0,0
+6,23.132,15.132,54.000,17.500,71.500,0.000,0.239,0,0
+7,26.657,18.657,54.000,17.500,71.500,0.000,0.239,0,0
+8,30.224,22.224,54.000,17.500,71.500,0.000,0.239,0,0
+9,33.817,25.817,54.000,17.500,71.500,0.000,0.239,0,0
+10,37.429,29.429,54.000,17.500,71.500,0.000,0.239,0,0
+11,41.054,33.054,54.000,17.500,71.500,0.000,0.239,0,0
+12,44.689,36.689,54.000,17.500,71.500,0.000,0.239,0,0
+13,48.331,40.331,54.000,17.500,71.500,0.000,0.239,0,0
+14,51.978,43.978,54.000,17.500,71.500,0.000,0.239,0,0
+15,55.631,47.631,54.000,17.500,71.500,0.000,0.239,0,0
+16,59.287,51.287,54.000,17.500,71.500,0.000,0.239,0,0
+17,62.946,54.946,54.946,17.500,72.446,0.000,0.244,0,0
+18,66.607,58.607,58.607,17.500,76.107,0.000,0.263,0,0
+19,70.271,62.271,62.271,17.500,79.771,0.000,0.282,0,0
+20,73.937,65.937,65.937,17.500,83.437,0.000,0.301,0,0
+21,77.604,69.604,69.604,17.500,87.104,0.000,0.320,0,0
+22,81.272,73.272,73.272,17.500,90.772,0.000,0.339,0,0
+23,84.942,76.942,76.942,17.500,94.442,0.000,0.358,0,0
+24,88.612,80.612,80.612,17.500,98.112,0.000,0.377,0,0
+25,92.284,84.284,95.000,17.500,112.500,0.449,0.452,1,0
+26,95.956,87.956,95.000,17.500,112.500,0.449,0.452,1,0
+27,99.629,91.629,95.000,17.500,112.500,0.449,0.452,1,0
+28,103.303,95.303,95.303,17.500,112.803,0.451,0.453,1,0
+29,106.977,98.977,98.977,17.500,116.477,0.474,0.472,1,0
+30,110.652,102.652,102.652,17.500,120.152,0.498,0.491,1,0
+31,114.327,106.327,106.327,17.500,123.827,0.521,0.510,1,0
+32,118.002,110.002,110.002,17.500,127.502,0.545,0.530,1,0
+33,121.678,113.678,113.678,17.500,131.178,0.568,0.549,1,0
+34,125.354,117.354,117.354,17.500,134.854,0.591,0.568,1,0
+35,129.031,121.031,121.031,17.500,138.531,0.615,0.587,1,0
+36,132.708,124.708,124.708,17.500,142.208,0.638,0.606,1,0
+37,136.385,128.385,128.385,17.500,145.885,0.662,0.625,1,1
+38,140.062,132.062,132.062,17.500,149.562,0.685,0.644,1,1
+39,143.740,135.740,135.740,17.500,153.240,0.709,0.663,1,1
+40,147.418,139.418,139.418,17.500,156.918,0.732,0.682,1,1
+41,151.095,143.095,143.095,17.500,160.595,0.755,0.701,1,1
+42,154.774,146.774,146.774,17.500,164.274,0.779,0.720,1,1
+43,158.452,150.452,150.452,17.500,167.952,0.802,0.740,1,1
+44,162.130,154.130,154.130,17.500,171.630,0.826,0.759,1,1
+45,165.809,157.809,157.809,17.500,175.309,0.849,0.778,1,1
+46,169.488,161.488,161.488,17.500,178.988,0.873,0.797,1,1
+47,173.166,165.166,165.166,17.500,182.666,0.896,0.816,1,1
+48,176.845,168.845,168.845,17.500,186.345,0.919,0.835,1,1
+49,180.525,172.525,172.525,17.500,190.025,0.943,0.854,1,1
+50,184.204,176.204,176.204,17.500,193.704,0.966,0.873,1,1
+51,187.883,179.883,179.883,17.500,197.383,0.990,0.892,1,1
+52,191.562,183.562,183.562,17.500,201.062,1.000,0.911,1,1
+53,195.242,187.242,187.242,17.500,204.742,1.000,0.931,1,1
+54,198.921,190.921,190.921,17.500,208.421,1.000,0.950,1,1
+55,202.601,194.601,194.601,17.500,212.101,1.000,0.969,1,1
+56,206.281,198.281,198.281,17.500,215.781,1.000,0.988,1,1
+57,209.960,201.960,201.960,17.500,219.460,1.000,1.000,1,1
+58,213.640,205.640,205.640,17.500,223.140,1.000,1.000,1,1
+59,217.320,209.320,209.320,17.500,226.820,1.000,1.000,1,1
+60,221.000,213.000,213.000,17.500,230.500,1.000,1.000,1,1
diff --git a/chainring_generator/out/svg/filled/chainring_05_filled.svg b/chainring_generator/out/svg/filled/chainring_05_filled.svg
new file mode 100644
index 0000000..90452db
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_05_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_06_filled.svg b/chainring_generator/out/svg/filled/chainring_06_filled.svg
new file mode 100644
index 0000000..b66f2e8
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_06_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_07_filled.svg b/chainring_generator/out/svg/filled/chainring_07_filled.svg
new file mode 100644
index 0000000..1d04a02
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_07_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_08_filled.svg b/chainring_generator/out/svg/filled/chainring_08_filled.svg
new file mode 100644
index 0000000..d8d08e9
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_08_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_09_filled.svg b/chainring_generator/out/svg/filled/chainring_09_filled.svg
new file mode 100644
index 0000000..73f04cc
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_09_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_10_filled.svg b/chainring_generator/out/svg/filled/chainring_10_filled.svg
new file mode 100644
index 0000000..086bb82
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_10_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_11_filled.svg b/chainring_generator/out/svg/filled/chainring_11_filled.svg
new file mode 100644
index 0000000..b963792
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_11_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_12_filled.svg b/chainring_generator/out/svg/filled/chainring_12_filled.svg
new file mode 100644
index 0000000..ef22746
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_12_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_13_filled.svg b/chainring_generator/out/svg/filled/chainring_13_filled.svg
new file mode 100644
index 0000000..fd9ef20
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_13_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_14_filled.svg b/chainring_generator/out/svg/filled/chainring_14_filled.svg
new file mode 100644
index 0000000..4575b06
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_14_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_15_filled.svg b/chainring_generator/out/svg/filled/chainring_15_filled.svg
new file mode 100644
index 0000000..4fb2343
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_15_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_16_filled.svg b/chainring_generator/out/svg/filled/chainring_16_filled.svg
new file mode 100644
index 0000000..dd76c9e
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_16_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_17_filled.svg b/chainring_generator/out/svg/filled/chainring_17_filled.svg
new file mode 100644
index 0000000..2280e88
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_17_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_18_filled.svg b/chainring_generator/out/svg/filled/chainring_18_filled.svg
new file mode 100644
index 0000000..bac4bf5
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_18_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_19_filled.svg b/chainring_generator/out/svg/filled/chainring_19_filled.svg
new file mode 100644
index 0000000..536e154
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_19_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_20_filled.svg b/chainring_generator/out/svg/filled/chainring_20_filled.svg
new file mode 100644
index 0000000..51999c8
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_20_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_21_filled.svg b/chainring_generator/out/svg/filled/chainring_21_filled.svg
new file mode 100644
index 0000000..5e2137c
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_21_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_22_filled.svg b/chainring_generator/out/svg/filled/chainring_22_filled.svg
new file mode 100644
index 0000000..b742094
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_22_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_23_filled.svg b/chainring_generator/out/svg/filled/chainring_23_filled.svg
new file mode 100644
index 0000000..8c9a510
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_23_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_24_filled.svg b/chainring_generator/out/svg/filled/chainring_24_filled.svg
new file mode 100644
index 0000000..e8cb767
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_24_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_25_filled.svg b/chainring_generator/out/svg/filled/chainring_25_filled.svg
new file mode 100644
index 0000000..9ec3162
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_25_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_26_filled.svg b/chainring_generator/out/svg/filled/chainring_26_filled.svg
new file mode 100644
index 0000000..fbd6ab7
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_26_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_27_filled.svg b/chainring_generator/out/svg/filled/chainring_27_filled.svg
new file mode 100644
index 0000000..c0ecd8c
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_27_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_28_filled.svg b/chainring_generator/out/svg/filled/chainring_28_filled.svg
new file mode 100644
index 0000000..c54441c
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_28_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_29_filled.svg b/chainring_generator/out/svg/filled/chainring_29_filled.svg
new file mode 100644
index 0000000..43bf601
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_29_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_30_filled.svg b/chainring_generator/out/svg/filled/chainring_30_filled.svg
new file mode 100644
index 0000000..d93e3d3
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_30_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_31_filled.svg b/chainring_generator/out/svg/filled/chainring_31_filled.svg
new file mode 100644
index 0000000..4c3282c
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_31_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_32_filled.svg b/chainring_generator/out/svg/filled/chainring_32_filled.svg
new file mode 100644
index 0000000..f6040d7
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_32_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_33_filled.svg b/chainring_generator/out/svg/filled/chainring_33_filled.svg
new file mode 100644
index 0000000..4f87c37
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_33_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_34_filled.svg b/chainring_generator/out/svg/filled/chainring_34_filled.svg
new file mode 100644
index 0000000..8fb6c8b
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_34_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_35_filled.svg b/chainring_generator/out/svg/filled/chainring_35_filled.svg
new file mode 100644
index 0000000..bc41428
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_35_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_36_filled.svg b/chainring_generator/out/svg/filled/chainring_36_filled.svg
new file mode 100644
index 0000000..00dbd24
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_36_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_37_filled.svg b/chainring_generator/out/svg/filled/chainring_37_filled.svg
new file mode 100644
index 0000000..0f95b0d
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_37_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_38_filled.svg b/chainring_generator/out/svg/filled/chainring_38_filled.svg
new file mode 100644
index 0000000..5e0f894
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_38_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_39_filled.svg b/chainring_generator/out/svg/filled/chainring_39_filled.svg
new file mode 100644
index 0000000..1a3d642
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_39_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_40_filled.svg b/chainring_generator/out/svg/filled/chainring_40_filled.svg
new file mode 100644
index 0000000..e074cde
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_40_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_41_filled.svg b/chainring_generator/out/svg/filled/chainring_41_filled.svg
new file mode 100644
index 0000000..5848c1d
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_41_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_42_filled.svg b/chainring_generator/out/svg/filled/chainring_42_filled.svg
new file mode 100644
index 0000000..a1f1e3c
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_42_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_43_filled.svg b/chainring_generator/out/svg/filled/chainring_43_filled.svg
new file mode 100644
index 0000000..6f57270
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_43_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_44_filled.svg b/chainring_generator/out/svg/filled/chainring_44_filled.svg
new file mode 100644
index 0000000..8449395
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_44_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_45_filled.svg b/chainring_generator/out/svg/filled/chainring_45_filled.svg
new file mode 100644
index 0000000..8812de3
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_45_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_46_filled.svg b/chainring_generator/out/svg/filled/chainring_46_filled.svg
new file mode 100644
index 0000000..cd86439
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_46_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_47_filled.svg b/chainring_generator/out/svg/filled/chainring_47_filled.svg
new file mode 100644
index 0000000..8c18c57
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_47_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_48_filled.svg b/chainring_generator/out/svg/filled/chainring_48_filled.svg
new file mode 100644
index 0000000..4be68df
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_48_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_49_filled.svg b/chainring_generator/out/svg/filled/chainring_49_filled.svg
new file mode 100644
index 0000000..24d66fa
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_49_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_50_filled.svg b/chainring_generator/out/svg/filled/chainring_50_filled.svg
new file mode 100644
index 0000000..697759e
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_50_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_51_filled.svg b/chainring_generator/out/svg/filled/chainring_51_filled.svg
new file mode 100644
index 0000000..2d3f1cb
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_51_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_52_filled.svg b/chainring_generator/out/svg/filled/chainring_52_filled.svg
new file mode 100644
index 0000000..dfc4c46
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_52_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_53_filled.svg b/chainring_generator/out/svg/filled/chainring_53_filled.svg
new file mode 100644
index 0000000..c6c92f7
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_53_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_54_filled.svg b/chainring_generator/out/svg/filled/chainring_54_filled.svg
new file mode 100644
index 0000000..34b820a
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_54_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_55_filled.svg b/chainring_generator/out/svg/filled/chainring_55_filled.svg
new file mode 100644
index 0000000..f92398b
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_55_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_56_filled.svg b/chainring_generator/out/svg/filled/chainring_56_filled.svg
new file mode 100644
index 0000000..a789977
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_56_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_57_filled.svg b/chainring_generator/out/svg/filled/chainring_57_filled.svg
new file mode 100644
index 0000000..cfa63de
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_57_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_58_filled.svg b/chainring_generator/out/svg/filled/chainring_58_filled.svg
new file mode 100644
index 0000000..f90bbeb
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_58_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_59_filled.svg b/chainring_generator/out/svg/filled/chainring_59_filled.svg
new file mode 100644
index 0000000..fb852cc
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_59_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/filled/chainring_60_filled.svg b/chainring_generator/out/svg/filled/chainring_60_filled.svg
new file mode 100644
index 0000000..d1ac6eb
--- /dev/null
+++ b/chainring_generator/out/svg/filled/chainring_60_filled.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_05_outline.svg b/chainring_generator/out/svg/outline/chainring_05_outline.svg
new file mode 100644
index 0000000..cc39651
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_05_outline.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_06_outline.svg b/chainring_generator/out/svg/outline/chainring_06_outline.svg
new file mode 100644
index 0000000..7783a60
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_06_outline.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_07_outline.svg b/chainring_generator/out/svg/outline/chainring_07_outline.svg
new file mode 100644
index 0000000..ae16d43
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_07_outline.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_08_outline.svg b/chainring_generator/out/svg/outline/chainring_08_outline.svg
new file mode 100644
index 0000000..5c4dfac
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_08_outline.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_09_outline.svg b/chainring_generator/out/svg/outline/chainring_09_outline.svg
new file mode 100644
index 0000000..5d3b947
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_09_outline.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_10_outline.svg b/chainring_generator/out/svg/outline/chainring_10_outline.svg
new file mode 100644
index 0000000..5a7433b
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_10_outline.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_11_outline.svg b/chainring_generator/out/svg/outline/chainring_11_outline.svg
new file mode 100644
index 0000000..d291e6a
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_11_outline.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_12_outline.svg b/chainring_generator/out/svg/outline/chainring_12_outline.svg
new file mode 100644
index 0000000..c53dde8
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_12_outline.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_13_outline.svg b/chainring_generator/out/svg/outline/chainring_13_outline.svg
new file mode 100644
index 0000000..9e1b0e0
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_13_outline.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_14_outline.svg b/chainring_generator/out/svg/outline/chainring_14_outline.svg
new file mode 100644
index 0000000..080c411
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_14_outline.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_15_outline.svg b/chainring_generator/out/svg/outline/chainring_15_outline.svg
new file mode 100644
index 0000000..32501bc
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_15_outline.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_16_outline.svg b/chainring_generator/out/svg/outline/chainring_16_outline.svg
new file mode 100644
index 0000000..b31020c
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_16_outline.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_17_outline.svg b/chainring_generator/out/svg/outline/chainring_17_outline.svg
new file mode 100644
index 0000000..5a813f8
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_17_outline.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_18_outline.svg b/chainring_generator/out/svg/outline/chainring_18_outline.svg
new file mode 100644
index 0000000..e898743
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_18_outline.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_19_outline.svg b/chainring_generator/out/svg/outline/chainring_19_outline.svg
new file mode 100644
index 0000000..28d05f0
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_19_outline.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_20_outline.svg b/chainring_generator/out/svg/outline/chainring_20_outline.svg
new file mode 100644
index 0000000..813417d
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_20_outline.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_21_outline.svg b/chainring_generator/out/svg/outline/chainring_21_outline.svg
new file mode 100644
index 0000000..4e5ac92
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_21_outline.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_22_outline.svg b/chainring_generator/out/svg/outline/chainring_22_outline.svg
new file mode 100644
index 0000000..3396a8c
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_22_outline.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_23_outline.svg b/chainring_generator/out/svg/outline/chainring_23_outline.svg
new file mode 100644
index 0000000..946ead6
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_23_outline.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_24_outline.svg b/chainring_generator/out/svg/outline/chainring_24_outline.svg
new file mode 100644
index 0000000..740a956
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_24_outline.svg
@@ -0,0 +1,4 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_25_outline.svg b/chainring_generator/out/svg/outline/chainring_25_outline.svg
new file mode 100644
index 0000000..88c17b7
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_25_outline.svg
@@ -0,0 +1,7 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_26_outline.svg b/chainring_generator/out/svg/outline/chainring_26_outline.svg
new file mode 100644
index 0000000..2a60b14
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_26_outline.svg
@@ -0,0 +1,7 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_27_outline.svg b/chainring_generator/out/svg/outline/chainring_27_outline.svg
new file mode 100644
index 0000000..0a0f0a9
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_27_outline.svg
@@ -0,0 +1,7 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_28_outline.svg b/chainring_generator/out/svg/outline/chainring_28_outline.svg
new file mode 100644
index 0000000..b58d965
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_28_outline.svg
@@ -0,0 +1,7 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_29_outline.svg b/chainring_generator/out/svg/outline/chainring_29_outline.svg
new file mode 100644
index 0000000..b6d6da0
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_29_outline.svg
@@ -0,0 +1,7 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_30_outline.svg b/chainring_generator/out/svg/outline/chainring_30_outline.svg
new file mode 100644
index 0000000..6a257b5
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_30_outline.svg
@@ -0,0 +1,7 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_31_outline.svg b/chainring_generator/out/svg/outline/chainring_31_outline.svg
new file mode 100644
index 0000000..8c0f9e8
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_31_outline.svg
@@ -0,0 +1,7 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_32_outline.svg b/chainring_generator/out/svg/outline/chainring_32_outline.svg
new file mode 100644
index 0000000..91ff946
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_32_outline.svg
@@ -0,0 +1,7 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_33_outline.svg b/chainring_generator/out/svg/outline/chainring_33_outline.svg
new file mode 100644
index 0000000..d4bc309
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_33_outline.svg
@@ -0,0 +1,7 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_34_outline.svg b/chainring_generator/out/svg/outline/chainring_34_outline.svg
new file mode 100644
index 0000000..5e24fd8
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_34_outline.svg
@@ -0,0 +1,7 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_35_outline.svg b/chainring_generator/out/svg/outline/chainring_35_outline.svg
new file mode 100644
index 0000000..ff90c42
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_35_outline.svg
@@ -0,0 +1,7 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_36_outline.svg b/chainring_generator/out/svg/outline/chainring_36_outline.svg
new file mode 100644
index 0000000..d1f5e2a
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_36_outline.svg
@@ -0,0 +1,7 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_37_outline.svg b/chainring_generator/out/svg/outline/chainring_37_outline.svg
new file mode 100644
index 0000000..dcc331e
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_37_outline.svg
@@ -0,0 +1,10 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_38_outline.svg b/chainring_generator/out/svg/outline/chainring_38_outline.svg
new file mode 100644
index 0000000..9072e52
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_38_outline.svg
@@ -0,0 +1,10 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_39_outline.svg b/chainring_generator/out/svg/outline/chainring_39_outline.svg
new file mode 100644
index 0000000..594ee74
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_39_outline.svg
@@ -0,0 +1,10 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_40_outline.svg b/chainring_generator/out/svg/outline/chainring_40_outline.svg
new file mode 100644
index 0000000..e05d280
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_40_outline.svg
@@ -0,0 +1,10 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_41_outline.svg b/chainring_generator/out/svg/outline/chainring_41_outline.svg
new file mode 100644
index 0000000..43d974b
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_41_outline.svg
@@ -0,0 +1,10 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_42_outline.svg b/chainring_generator/out/svg/outline/chainring_42_outline.svg
new file mode 100644
index 0000000..961e903
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_42_outline.svg
@@ -0,0 +1,10 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_43_outline.svg b/chainring_generator/out/svg/outline/chainring_43_outline.svg
new file mode 100644
index 0000000..82997af
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_43_outline.svg
@@ -0,0 +1,10 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_44_outline.svg b/chainring_generator/out/svg/outline/chainring_44_outline.svg
new file mode 100644
index 0000000..7b46b00
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_44_outline.svg
@@ -0,0 +1,10 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_45_outline.svg b/chainring_generator/out/svg/outline/chainring_45_outline.svg
new file mode 100644
index 0000000..daccd42
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_45_outline.svg
@@ -0,0 +1,10 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_46_outline.svg b/chainring_generator/out/svg/outline/chainring_46_outline.svg
new file mode 100644
index 0000000..6265f4d
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_46_outline.svg
@@ -0,0 +1,10 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_47_outline.svg b/chainring_generator/out/svg/outline/chainring_47_outline.svg
new file mode 100644
index 0000000..59fc677
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_47_outline.svg
@@ -0,0 +1,10 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_48_outline.svg b/chainring_generator/out/svg/outline/chainring_48_outline.svg
new file mode 100644
index 0000000..3e2920b
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_48_outline.svg
@@ -0,0 +1,10 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_49_outline.svg b/chainring_generator/out/svg/outline/chainring_49_outline.svg
new file mode 100644
index 0000000..ab30034
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_49_outline.svg
@@ -0,0 +1,10 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_50_outline.svg b/chainring_generator/out/svg/outline/chainring_50_outline.svg
new file mode 100644
index 0000000..a36759c
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_50_outline.svg
@@ -0,0 +1,10 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_51_outline.svg b/chainring_generator/out/svg/outline/chainring_51_outline.svg
new file mode 100644
index 0000000..567e24c
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_51_outline.svg
@@ -0,0 +1,10 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_52_outline.svg b/chainring_generator/out/svg/outline/chainring_52_outline.svg
new file mode 100644
index 0000000..9a59037
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_52_outline.svg
@@ -0,0 +1,10 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_53_outline.svg b/chainring_generator/out/svg/outline/chainring_53_outline.svg
new file mode 100644
index 0000000..936a983
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_53_outline.svg
@@ -0,0 +1,10 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_54_outline.svg b/chainring_generator/out/svg/outline/chainring_54_outline.svg
new file mode 100644
index 0000000..243e299
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_54_outline.svg
@@ -0,0 +1,10 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_55_outline.svg b/chainring_generator/out/svg/outline/chainring_55_outline.svg
new file mode 100644
index 0000000..98a626b
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_55_outline.svg
@@ -0,0 +1,10 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_56_outline.svg b/chainring_generator/out/svg/outline/chainring_56_outline.svg
new file mode 100644
index 0000000..0af77ef
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_56_outline.svg
@@ -0,0 +1,10 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_57_outline.svg b/chainring_generator/out/svg/outline/chainring_57_outline.svg
new file mode 100644
index 0000000..8c77a82
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_57_outline.svg
@@ -0,0 +1,10 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_58_outline.svg b/chainring_generator/out/svg/outline/chainring_58_outline.svg
new file mode 100644
index 0000000..af1bf62
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_58_outline.svg
@@ -0,0 +1,10 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_59_outline.svg b/chainring_generator/out/svg/outline/chainring_59_outline.svg
new file mode 100644
index 0000000..246fdd5
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_59_outline.svg
@@ -0,0 +1,10 @@
+
diff --git a/chainring_generator/out/svg/outline/chainring_60_outline.svg b/chainring_generator/out/svg/outline/chainring_60_outline.svg
new file mode 100644
index 0000000..f5e43f9
--- /dev/null
+++ b/chainring_generator/out/svg/outline/chainring_60_outline.svg
@@ -0,0 +1,10 @@
+
diff --git a/chainring_generator/pyproject.toml b/chainring_generator/pyproject.toml
new file mode 100644
index 0000000..97e4577
--- /dev/null
+++ b/chainring_generator/pyproject.toml
@@ -0,0 +1,8 @@
+[project]
+name = "chainring-generator"
+version = "0.1.0"
+requires-python = ">=3.13"
+dependencies = [
+ "numpy>=2.4.4",
+ "pillow>=12.2.0",
+]
diff --git a/chainring_generator/uv.lock b/chainring_generator/uv.lock
new file mode 100644
index 0000000..2ab8f68
--- /dev/null
+++ b/chainring_generator/uv.lock
@@ -0,0 +1,126 @@
+version = 1
+revision = 3
+requires-python = ">=3.13"
+
+[[package]]
+name = "chainring-generator"
+version = "0.1.0"
+source = { virtual = "." }
+dependencies = [
+ { name = "numpy" },
+ { name = "pillow" },
+]
+
+[package.metadata]
+requires-dist = [
+ { name = "numpy", specifier = ">=2.4.4" },
+ { name = "pillow", specifier = ">=12.2.0" },
+]
+
+[[package]]
+name = "numpy"
+version = "2.4.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d7/9f/b8cef5bffa569759033adda9481211426f12f53299629b410340795c2514/numpy-2.4.4.tar.gz", hash = "sha256:2d390634c5182175533585cc89f3608a4682ccb173cc9bb940b2881c8d6f8fa0", size = 20731587, upload-time = "2026-03-29T13:22:01.298Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/14/1d/d0a583ce4fefcc3308806a749a536c201ed6b5ad6e1322e227ee4848979d/numpy-2.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:08f2e31ed5e6f04b118e49821397f12767934cfdd12a1ce86a058f91e004ee50", size = 16684933, upload-time = "2026-03-29T13:19:22.47Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/62/2b7a48fbb745d344742c0277f01286dead15f3f68e4f359fbfcf7b48f70f/numpy-2.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e823b8b6edc81e747526f70f71a9c0a07ac4e7ad13020aa736bb7c9d67196115", size = 14694532, upload-time = "2026-03-29T13:19:25.581Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/87/499737bfba066b4a3bebff24a8f1c5b2dee410b209bc6668c9be692580f0/numpy-2.4.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4a19d9dba1a76618dd86b164d608566f393f8ec6ac7c44f0cc879011c45e65af", size = 5199661, upload-time = "2026-03-29T13:19:28.31Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/da/464d551604320d1491bc345efed99b4b7034143a85787aab78d5691d5a0e/numpy-2.4.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d2a8490669bfe99a233298348acc2d824d496dee0e66e31b66a6022c2ad74a5c", size = 6547539, upload-time = "2026-03-29T13:19:30.97Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/90/8d23e3b0dafd024bf31bdec225b3bb5c2dbfa6912f8a53b8659f21216cbf/numpy-2.4.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45dbed2ab436a9e826e302fcdcbe9133f9b0006e5af7168afb8963a6520da103", size = 15668806, upload-time = "2026-03-29T13:19:33.887Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/73/a9d864e42a01896bb5974475438f16086be9ba1f0d19d0bb7a07427c4a8b/numpy-2.4.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c901b15172510173f5cb310eae652908340f8dede90fff9e3bf6c0d8dfd92f83", size = 16632682, upload-time = "2026-03-29T13:19:37.336Z" },
+ { url = "https://files.pythonhosted.org/packages/34/fb/14570d65c3bde4e202a031210475ae9cde9b7686a2e7dc97ee67d2833b35/numpy-2.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:99d838547ace2c4aace6c4f76e879ddfe02bb58a80c1549928477862b7a6d6ed", size = 17019810, upload-time = "2026-03-29T13:19:40.963Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/77/2ba9d87081fd41f6d640c83f26fb7351e536b7ce6dd9061b6af5904e8e46/numpy-2.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0aec54fd785890ecca25a6003fd9a5aed47ad607bbac5cd64f836ad8666f4959", size = 18357394, upload-time = "2026-03-29T13:19:44.859Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/23/52666c9a41708b0853fa3b1a12c90da38c507a3074883823126d4e9d5b30/numpy-2.4.4-cp313-cp313-win32.whl", hash = "sha256:07077278157d02f65c43b1b26a3886bce886f95d20aabd11f87932750dfb14ed", size = 5959556, upload-time = "2026-03-29T13:19:47.661Z" },
+ { url = "https://files.pythonhosted.org/packages/57/fb/48649b4971cde70d817cf97a2a2fdc0b4d8308569f1dd2f2611959d2e0cf/numpy-2.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:5c70f1cc1c4efbe316a572e2d8b9b9cc44e89b95f79ca3331553fbb63716e2bf", size = 12317311, upload-time = "2026-03-29T13:19:50.67Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/d8/11490cddd564eb4de97b4579ef6bfe6a736cc07e94c1598590ae25415e01/numpy-2.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:ef4059d6e5152fa1a39f888e344c73fdc926e1b2dd58c771d67b0acfbf2aa67d", size = 10222060, upload-time = "2026-03-29T13:19:54.229Z" },
+ { url = "https://files.pythonhosted.org/packages/99/5d/dab4339177a905aad3e2221c915b35202f1ec30d750dd2e5e9d9a72b804b/numpy-2.4.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4bbc7f303d125971f60ec0aaad5e12c62d0d2c925f0ab1273debd0e4ba37aba5", size = 14822302, upload-time = "2026-03-29T13:19:57.585Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/e4/0564a65e7d3d97562ed6f9b0fd0fb0a6f559ee444092f105938b50043876/numpy-2.4.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:4d6d57903571f86180eb98f8f0c839fa9ebbfb031356d87f1361be91e433f5b7", size = 5327407, upload-time = "2026-03-29T13:20:00.601Z" },
+ { url = "https://files.pythonhosted.org/packages/29/8d/35a3a6ce5ad371afa58b4700f1c820f8f279948cca32524e0a695b0ded83/numpy-2.4.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:4636de7fd195197b7535f231b5de9e4b36d2c440b6e566d2e4e4746e6af0ca93", size = 6647631, upload-time = "2026-03-29T13:20:02.855Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/da/477731acbd5a58a946c736edfdabb2ac5b34c3d08d1ba1a7b437fa0884df/numpy-2.4.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ad2e2ef14e0b04e544ea2fa0a36463f847f113d314aa02e5b402fdf910ef309e", size = 15727691, upload-time = "2026-03-29T13:20:06.004Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/db/338535d9b152beabeb511579598418ba0212ce77cf9718edd70262cc4370/numpy-2.4.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a285b3b96f951841799528cd1f4f01cd70e7e0204b4abebac9463eecfcf2a40", size = 16681241, upload-time = "2026-03-29T13:20:09.417Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/a9/ad248e8f58beb7a0219b413c9c7d8151c5d285f7f946c3e26695bdbbe2df/numpy-2.4.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f8474c4241bc18b750be2abea9d7a9ec84f46ef861dbacf86a4f6e043401f79e", size = 17085767, upload-time = "2026-03-29T13:20:13.126Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/1a/3b88ccd3694681356f70da841630e4725a7264d6a885c8d442a697e1146b/numpy-2.4.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4e874c976154687c1f71715b034739b45c7711bec81db01914770373d125e392", size = 18403169, upload-time = "2026-03-29T13:20:17.096Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/c9/fcfd5d0639222c6eac7f304829b04892ef51c96a75d479214d77e3ce6e33/numpy-2.4.4-cp313-cp313t-win32.whl", hash = "sha256:9c585a1790d5436a5374bac930dad6ed244c046ed91b2b2a3634eb2971d21008", size = 6083477, upload-time = "2026-03-29T13:20:20.195Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/e3/3938a61d1c538aaec8ed6fd6323f57b0c2d2d2219512434c5c878db76553/numpy-2.4.4-cp313-cp313t-win_amd64.whl", hash = "sha256:93e15038125dc1e5345d9b5b68aa7f996ec33b98118d18c6ca0d0b7d6198b7e8", size = 12457487, upload-time = "2026-03-29T13:20:22.946Z" },
+ { url = "https://files.pythonhosted.org/packages/97/6a/7e345032cc60501721ef94e0e30b60f6b0bd601f9174ebd36389a2b86d40/numpy-2.4.4-cp313-cp313t-win_arm64.whl", hash = "sha256:0dfd3f9d3adbe2920b68b5cd3d51444e13a10792ec7154cd0a2f6e74d4ab3233", size = 10292002, upload-time = "2026-03-29T13:20:25.909Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/06/c54062f85f673dd5c04cbe2f14c3acb8c8b95e3384869bb8cc9bff8cb9df/numpy-2.4.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f169b9a863d34f5d11b8698ead99febeaa17a13ca044961aa8e2662a6c7766a0", size = 16684353, upload-time = "2026-03-29T13:20:29.504Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/39/8a320264a84404c74cc7e79715de85d6130fa07a0898f67fb5cd5bd79908/numpy-2.4.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2483e4584a1cb3092da4470b38866634bafb223cbcd551ee047633fd2584599a", size = 14704914, upload-time = "2026-03-29T13:20:33.547Z" },
+ { url = "https://files.pythonhosted.org/packages/91/fb/287076b2614e1d1044235f50f03748f31fa287e3dbe6abeb35cdfa351eca/numpy-2.4.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:2d19e6e2095506d1736b7d80595e0f252d76b89f5e715c35e06e937679ea7d7a", size = 5210005, upload-time = "2026-03-29T13:20:36.45Z" },
+ { url = "https://files.pythonhosted.org/packages/63/eb/fcc338595309910de6ecabfcef2419a9ce24399680bfb149421fa2df1280/numpy-2.4.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:6a246d5914aa1c820c9443ddcee9c02bec3e203b0c080349533fae17727dfd1b", size = 6544974, upload-time = "2026-03-29T13:20:39.014Z" },
+ { url = "https://files.pythonhosted.org/packages/44/5d/e7e9044032a716cdfaa3fba27a8e874bf1c5f1912a1ddd4ed071bf8a14a6/numpy-2.4.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:989824e9faf85f96ec9c7761cd8d29c531ad857bfa1daa930cba85baaecf1a9a", size = 15684591, upload-time = "2026-03-29T13:20:42.146Z" },
+ { url = "https://files.pythonhosted.org/packages/98/7c/21252050676612625449b4807d6b695b9ce8a7c9e1c197ee6216c8a65c7c/numpy-2.4.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:27a8d92cd10f1382a67d7cf4db7ce18341b66438bdd9f691d7b0e48d104c2a9d", size = 16637700, upload-time = "2026-03-29T13:20:46.204Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/29/56d2bbef9465db24ef25393383d761a1af4f446a1df9b8cded4fe3a5a5d7/numpy-2.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e44319a2953c738205bf3354537979eaa3998ed673395b964c1176083dd46252", size = 17035781, upload-time = "2026-03-29T13:20:50.242Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/2b/a35a6d7589d21f44cea7d0a98de5ddcbb3d421b2622a5c96b1edf18707c3/numpy-2.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e892aff75639bbef0d2a2cfd55535510df26ff92f63c92cd84ef8d4ba5a5557f", size = 18362959, upload-time = "2026-03-29T13:20:54.019Z" },
+ { url = "https://files.pythonhosted.org/packages/64/c9/d52ec581f2390e0f5f85cbfd80fb83d965fc15e9f0e1aec2195faa142cde/numpy-2.4.4-cp314-cp314-win32.whl", hash = "sha256:1378871da56ca8943c2ba674530924bb8ca40cd228358a3b5f302ad60cf875fc", size = 6008768, upload-time = "2026-03-29T13:20:56.912Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/22/4cc31a62a6c7b74a8730e31a4274c5dc80e005751e277a2ce38e675e4923/numpy-2.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:715d1c092715954784bc79e1174fc2a90093dc4dc84ea15eb14dad8abdcdeb74", size = 12449181, upload-time = "2026-03-29T13:20:59.548Z" },
+ { url = "https://files.pythonhosted.org/packages/70/2e/14cda6f4d8e396c612d1bf97f22958e92148801d7e4f110cabebdc0eef4b/numpy-2.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:2c194dd721e54ecad9ad387c1d35e63dce5c4450c6dc7dd5611283dda239aabb", size = 10496035, upload-time = "2026-03-29T13:21:02.524Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/e8/8fed8c8d848d7ecea092dc3469643f9d10bc3a134a815a3b033da1d2039b/numpy-2.4.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2aa0613a5177c264ff5921051a5719d20095ea586ca88cc802c5c218d1c67d3e", size = 14824958, upload-time = "2026-03-29T13:21:05.671Z" },
+ { url = "https://files.pythonhosted.org/packages/05/1a/d8007a5138c179c2bf33ef44503e83d70434d2642877ee8fbb230e7c0548/numpy-2.4.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:42c16925aa5a02362f986765f9ebabf20de75cdefdca827d14315c568dcab113", size = 5330020, upload-time = "2026-03-29T13:21:08.635Z" },
+ { url = "https://files.pythonhosted.org/packages/99/64/ffb99ac6ae93faf117bcbd5c7ba48a7f45364a33e8e458545d3633615dda/numpy-2.4.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:874f200b2a981c647340f841730fc3a2b54c9d940566a3c4149099591e2c4c3d", size = 6650758, upload-time = "2026-03-29T13:21:10.949Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/6e/795cc078b78a384052e73b2f6281ff7a700e9bf53bcce2ee579d4f6dd879/numpy-2.4.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9b39d38a9bd2ae1becd7eac1303d031c5c110ad31f2b319c6e7d98b135c934d", size = 15729948, upload-time = "2026-03-29T13:21:14.047Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/86/2acbda8cc2af5f3d7bfc791192863b9e3e19674da7b5e533fded124d1299/numpy-2.4.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b268594bccac7d7cf5844c7732e3f20c50921d94e36d7ec9b79e9857694b1b2f", size = 16679325, upload-time = "2026-03-29T13:21:17.561Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/59/cafd83018f4aa55e0ac6fa92aa066c0a1877b77a615ceff1711c260ffae8/numpy-2.4.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ac6b31e35612a26483e20750126d30d0941f949426974cace8e6b5c58a3657b0", size = 17084883, upload-time = "2026-03-29T13:21:21.106Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/85/a42548db84e65ece46ab2caea3d3f78b416a47af387fcbb47ec28e660dc2/numpy-2.4.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8e3ed142f2728df44263aaf5fb1f5b0b99f4070c553a0d7f033be65338329150", size = 18403474, upload-time = "2026-03-29T13:21:24.828Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/ad/483d9e262f4b831000062e5d8a45e342166ec8aaa1195264982bca267e62/numpy-2.4.4-cp314-cp314t-win32.whl", hash = "sha256:dddbbd259598d7240b18c9d87c56a9d2fb3b02fe266f49a7c101532e78c1d871", size = 6155500, upload-time = "2026-03-29T13:21:28.205Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/03/2fc4e14c7bd4ff2964b74ba90ecb8552540b6315f201df70f137faa5c589/numpy-2.4.4-cp314-cp314t-win_amd64.whl", hash = "sha256:a7164afb23be6e37ad90b2f10426149fd75aee07ca55653d2aa41e66c4ef697e", size = 12637755, upload-time = "2026-03-29T13:21:31.107Z" },
+ { url = "https://files.pythonhosted.org/packages/58/78/548fb8e07b1a341746bfbecb32f2c268470f45fa028aacdbd10d9bc73aab/numpy-2.4.4-cp314-cp314t-win_arm64.whl", hash = "sha256:ba203255017337d39f89bdd58417f03c4426f12beed0440cfd933cb15f8669c7", size = 10566643, upload-time = "2026-03-29T13:21:34.339Z" },
+]
+
+[[package]]
+name = "pillow"
+version = "12.2.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/8c/21/c2bcdd5906101a30244eaffc1b6e6ce71a31bd0742a01eb89e660ebfac2d/pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5", size = 46987819, upload-time = "2026-04-01T14:46:17.687Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/4a/01/53d10cf0dbad820a8db274d259a37ba50b88b24768ddccec07355382d5ad/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8297651f5b5679c19968abefd6bb84d95fe30ef712eb1b2d9b2d31ca61267f4c", size = 4100837, upload-time = "2026-04-01T14:43:41.506Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/98/f3a6657ecb698c937f6c76ee564882945f29b79bad496abcba0e84659ec5/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:50d8520da2a6ce0af445fa6d648c4273c3eeefbc32d7ce049f22e8b5c3daecc2", size = 4176528, upload-time = "2026-04-01T14:43:43.773Z" },
+ { url = "https://files.pythonhosted.org/packages/69/bc/8986948f05e3ea490b8442ea1c1d4d990b24a7e43d8a51b2c7d8b1dced36/pillow-12.2.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:766cef22385fa1091258ad7e6216792b156dc16d8d3fa607e7545b2b72061f1c", size = 3640401, upload-time = "2026-04-01T14:43:45.87Z" },
+ { url = "https://files.pythonhosted.org/packages/34/46/6c717baadcd62bc8ed51d238d521ab651eaa74838291bda1f86fe1f864c9/pillow-12.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5d2fd0fa6b5d9d1de415060363433f28da8b1526c1c129020435e186794b3795", size = 5308094, upload-time = "2026-04-01T14:43:48.438Z" },
+ { url = "https://files.pythonhosted.org/packages/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b25336f502b6ed02e889f4ece894a72612fe885889a6e8c4c80239ff6e5f5f", size = 4695402, upload-time = "2026-04-01T14:43:51.292Z" },
+ { url = "https://files.pythonhosted.org/packages/73/dd/42107efcb777b16fa0393317eac58f5b5cf30e8392e266e76e51cff28c3d/pillow-12.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f1c943e96e85df3d3478f7b691f229887e143f81fedab9b20205349ab04d73ed", size = 6280005, upload-time = "2026-04-01T14:43:54.242Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/68/b93e09e5e8549019e61acf49f65b1a8530765a7f812c77a7461bca7e4494/pillow-12.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:03f6fab9219220f041c74aeaa2939ff0062bd5c364ba9ce037197f4c6d498cd9", size = 8090669, upload-time = "2026-04-01T14:43:57.335Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/6e/3ccb54ce8ec4ddd1accd2d89004308b7b0b21c4ac3d20fa70af4760a4330/pillow-12.2.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdfebd752ec52bf5bb4e35d9c64b40826bc5b40a13df7c3cda20a2c03a0f5ed", size = 6395194, upload-time = "2026-04-01T14:43:59.864Z" },
+ { url = "https://files.pythonhosted.org/packages/67/ee/21d4e8536afd1a328f01b359b4d3997b291ffd35a237c877b331c1c3b71c/pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eedf4b74eda2b5a4b2b2fb4c006d6295df3bf29e459e198c90ea48e130dc75c3", size = 7082423, upload-time = "2026-04-01T14:44:02.74Z" },
+ { url = "https://files.pythonhosted.org/packages/78/5f/e9f86ab0146464e8c133fe85df987ed9e77e08b29d8d35f9f9f4d6f917ba/pillow-12.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:00a2865911330191c0b818c59103b58a5e697cae67042366970a6b6f1b20b7f9", size = 6505667, upload-time = "2026-04-01T14:44:05.381Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/1e/409007f56a2fdce61584fd3acbc2bbc259857d555196cedcadc68c015c82/pillow-12.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e1757442ed87f4912397c6d35a0db6a7b52592156014706f17658ff58bbf795", size = 7208580, upload-time = "2026-04-01T14:44:08.39Z" },
+ { url = "https://files.pythonhosted.org/packages/23/c4/7349421080b12fb35414607b8871e9534546c128a11965fd4a7002ccfbee/pillow-12.2.0-cp313-cp313-win32.whl", hash = "sha256:144748b3af2d1b358d41286056d0003f47cb339b8c43a9ea42f5fea4d8c66b6e", size = 6375896, upload-time = "2026-04-01T14:44:11.197Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/82/8a3739a5e470b3c6cbb1d21d315800d8e16bff503d1f16b03a4ec3212786/pillow-12.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:390ede346628ccc626e5730107cde16c42d3836b89662a115a921f28440e6a3b", size = 7081266, upload-time = "2026-04-01T14:44:13.947Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/25/f968f618a062574294592f668218f8af564830ccebdd1fa6200f598e65c5/pillow-12.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:8023abc91fba39036dbce14a7d6535632f99c0b857807cbbbf21ecc9f4717f06", size = 2463508, upload-time = "2026-04-01T14:44:16.312Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/a4/b342930964e3cb4dce5038ae34b0eab4653334995336cd486c5a8c25a00c/pillow-12.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:042db20a421b9bafecc4b84a8b6e444686bd9d836c7fd24542db3e7df7baad9b", size = 5309927, upload-time = "2026-04-01T14:44:18.89Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/de/23198e0a65a9cf06123f5435a5d95cea62a635697f8f03d134d3f3a96151/pillow-12.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd025009355c926a84a612fecf58bb315a3f6814b17ead51a8e48d3823d9087f", size = 4698624, upload-time = "2026-04-01T14:44:21.115Z" },
+ { url = "https://files.pythonhosted.org/packages/01/a6/1265e977f17d93ea37aa28aa81bad4fa597933879fac2520d24e021c8da3/pillow-12.2.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88ddbc66737e277852913bd1e07c150cc7bb124539f94c4e2df5344494e0a612", size = 6321252, upload-time = "2026-04-01T14:44:23.663Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/83/5982eb4a285967baa70340320be9f88e57665a387e3a53a7f0db8231a0cd/pillow-12.2.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d362d1878f00c142b7e1a16e6e5e780f02be8195123f164edf7eddd911eefe7c", size = 8126550, upload-time = "2026-04-01T14:44:26.772Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/48/6ffc514adce69f6050d0753b1a18fd920fce8cac87620d5a31231b04bfc5/pillow-12.2.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c727a6d53cb0018aadd8018c2b938376af27914a68a492f59dfcaca650d5eea", size = 6433114, upload-time = "2026-04-01T14:44:29.615Z" },
+ { url = "https://files.pythonhosted.org/packages/36/a3/f9a77144231fb8d40ee27107b4463e205fa4677e2ca2548e14da5cf18dce/pillow-12.2.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd8c21c98c5cc60653bcb311bef2ce0401642b7ce9d09e03a7da87c878289d4", size = 7115667, upload-time = "2026-04-01T14:44:32.773Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/fc/ac4ee3041e7d5a565e1c4fd72a113f03b6394cc72ab7089d27608f8aaccb/pillow-12.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f08483a632889536b8139663db60f6724bfcb443c96f1b18855860d7d5c0fd4", size = 6538966, upload-time = "2026-04-01T14:44:35.252Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/a8/27fb307055087f3668f6d0a8ccb636e7431d56ed0750e07a60547b1e083e/pillow-12.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dac8d77255a37e81a2efcbd1fc05f1c15ee82200e6c240d7e127e25e365c39ea", size = 7238241, upload-time = "2026-04-01T14:44:37.875Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/4b/926ab182c07fccae9fcb120043464e1ff1564775ec8864f21a0ebce6ac25/pillow-12.2.0-cp313-cp313t-win32.whl", hash = "sha256:ee3120ae9dff32f121610bb08e4313be87e03efeadfc6c0d18f89127e24d0c24", size = 6379592, upload-time = "2026-04-01T14:44:40.336Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/c4/f9e476451a098181b30050cc4c9a3556b64c02cf6497ea421ac047e89e4b/pillow-12.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:325ca0528c6788d2a6c3d40e3568639398137346c3d6e66bb61db96b96511c98", size = 7085542, upload-time = "2026-04-01T14:44:43.251Z" },
+ { url = "https://files.pythonhosted.org/packages/00/a4/285f12aeacbe2d6dc36c407dfbbe9e96d4a80b0fb710a337f6d2ad978c75/pillow-12.2.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2e5a76d03a6c6dcef67edabda7a52494afa4035021a79c8558e14af25313d453", size = 2465765, upload-time = "2026-04-01T14:44:45.996Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/98/4595daa2365416a86cb0d495248a393dfc84e96d62ad080c8546256cb9c0/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:3adc9215e8be0448ed6e814966ecf3d9952f0ea40eb14e89a102b87f450660d8", size = 4100848, upload-time = "2026-04-01T14:44:48.48Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/79/40184d464cf89f6663e18dfcf7ca21aae2491fff1a16127681bf1fa9b8cf/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:6a9adfc6d24b10f89588096364cc726174118c62130c817c2837c60cf08a392b", size = 4176515, upload-time = "2026-04-01T14:44:51.353Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/63/703f86fd4c422a9cf722833670f4f71418fb116b2853ff7da722ea43f184/pillow-12.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:6a6e67ea2e6feda684ed370f9a1c52e7a243631c025ba42149a2cc5934dec295", size = 3640159, upload-time = "2026-04-01T14:44:53.588Z" },
+ { url = "https://files.pythonhosted.org/packages/71/e0/fb22f797187d0be2270f83500aab851536101b254bfa1eae10795709d283/pillow-12.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2bb4a8d594eacdfc59d9e5ad972aa8afdd48d584ffd5f13a937a664c3e7db0ed", size = 5312185, upload-time = "2026-04-01T14:44:56.039Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/8c/1a9e46228571de18f8e28f16fabdfc20212a5d019f3e3303452b3f0a580d/pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:80b2da48193b2f33ed0c32c38140f9d3186583ce7d516526d462645fd98660ae", size = 4695386, upload-time = "2026-04-01T14:44:58.663Z" },
+ { url = "https://files.pythonhosted.org/packages/70/62/98f6b7f0c88b9addd0e87c217ded307b36be024d4ff8869a812b241d1345/pillow-12.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601", size = 6280384, upload-time = "2026-04-01T14:45:01.5Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/03/688747d2e91cfbe0e64f316cd2e8005698f76ada3130d0194664174fa5de/pillow-12.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be", size = 8091599, upload-time = "2026-04-01T14:45:04.5Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/35/577e22b936fcdd66537329b33af0b4ccfefaeabd8aec04b266528cddb33c/pillow-12.2.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f", size = 6396021, upload-time = "2026-04-01T14:45:07.117Z" },
+ { url = "https://files.pythonhosted.org/packages/11/8d/d2532ad2a603ca2b93ad9f5135732124e57811d0168155852f37fbce2458/pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286", size = 7083360, upload-time = "2026-04-01T14:45:09.763Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/26/d325f9f56c7e039034897e7380e9cc202b1e368bfd04d4cbe6a441f02885/pillow-12.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9aba9a17b623ef750a4d11b742cbafffeb48a869821252b30ee21b5e91392c50", size = 6507628, upload-time = "2026-04-01T14:45:12.378Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/f7/769d5632ffb0988f1c5e7660b3e731e30f7f8ec4318e94d0a5d674eb65a4/pillow-12.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:deede7c263feb25dba4e82ea23058a235dcc2fe1f6021025dc71f2b618e26104", size = 7209321, upload-time = "2026-04-01T14:45:15.122Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/7a/c253e3c645cd47f1aceea6a8bacdba9991bf45bb7dfe927f7c893e89c93c/pillow-12.2.0-cp314-cp314-win32.whl", hash = "sha256:632ff19b2778e43162304d50da0181ce24ac5bb8180122cbe1bf4673428328c7", size = 6479723, upload-time = "2026-04-01T14:45:17.797Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/8b/601e6566b957ca50e28725cb6c355c59c2c8609751efbecd980db44e0349/pillow-12.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:4e6c62e9d237e9b65fac06857d511e90d8461a32adcc1b9065ea0c0fa3a28150", size = 7217400, upload-time = "2026-04-01T14:45:20.529Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/94/220e46c73065c3e2951bb91c11a1fb636c8c9ad427ac3ce7d7f3359b9b2f/pillow-12.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:b1c1fbd8a5a1af3412a0810d060a78b5136ec0836c8a4ef9aa11807f2a22f4e1", size = 2554835, upload-time = "2026-04-01T14:45:23.162Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/ab/1b426a3974cb0e7da5c29ccff4807871d48110933a57207b5a676cccc155/pillow-12.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:57850958fe9c751670e49b2cecf6294acc99e562531f4bd317fa5ddee2068463", size = 5314225, upload-time = "2026-04-01T14:45:25.637Z" },
+ { url = "https://files.pythonhosted.org/packages/19/1e/dce46f371be2438eecfee2a1960ee2a243bbe5e961890146d2dee1ff0f12/pillow-12.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d5d38f1411c0ed9f97bcb49b7bd59b6b7c314e0e27420e34d99d844b9ce3b6f3", size = 4698541, upload-time = "2026-04-01T14:45:28.355Z" },
+ { url = "https://files.pythonhosted.org/packages/55/c3/7fbecf70adb3a0c33b77a300dc52e424dc22ad8cdc06557a2e49523b703d/pillow-12.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c0a9f29ca8e79f09de89293f82fc9b0270bb4af1d58bc98f540cc4aedf03166", size = 6322251, upload-time = "2026-04-01T14:45:30.924Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/3c/7fbc17cfb7e4fe0ef1642e0abc17fc6c94c9f7a16be41498e12e2ba60408/pillow-12.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1610dd6c61621ae1cf811bef44d77e149ce3f7b95afe66a4512f8c59f25d9ebe", size = 8127807, upload-time = "2026-04-01T14:45:33.908Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/c3/a8ae14d6defd2e448493ff512fae903b1e9bd40b72efb6ec55ce0048c8ce/pillow-12.2.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a34329707af4f73cf1782a36cd2289c0368880654a2c11f027bcee9052d35dd", size = 6433935, upload-time = "2026-04-01T14:45:36.623Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/32/2880fb3a074847ac159d8f902cb43278a61e85f681661e7419e6596803ed/pillow-12.2.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e9c4f5b3c546fa3458a29ab22646c1c6c787ea8f5ef51300e5a60300736905e", size = 7116720, upload-time = "2026-04-01T14:45:39.258Z" },
+ { url = "https://files.pythonhosted.org/packages/46/87/495cc9c30e0129501643f24d320076f4cc54f718341df18cc70ec94c44e1/pillow-12.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06", size = 6540498, upload-time = "2026-04-01T14:45:41.879Z" },
+ { url = "https://files.pythonhosted.org/packages/18/53/773f5edca692009d883a72211b60fdaf8871cbef075eaa9d577f0a2f989e/pillow-12.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f278f034eb75b4e8a13a54a876cc4a5ab39173d2cdd93a638e1b467fc545ac43", size = 7239413, upload-time = "2026-04-01T14:45:44.705Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/e4/4b64a97d71b2a83158134abbb2f5bd3f8a2ea691361282f010998f339ec7/pillow-12.2.0-cp314-cp314t-win32.whl", hash = "sha256:6bb77b2dcb06b20f9f4b4a8454caa581cd4dd0643a08bacf821216a16d9c8354", size = 6482084, upload-time = "2026-04-01T14:45:47.568Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/13/306d275efd3a3453f72114b7431c877d10b1154014c1ebbedd067770d629/pillow-12.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6562ace0d3fb5f20ed7290f1f929cae41b25ae29528f2af1722966a0a02e2aa1", size = 7225152, upload-time = "2026-04-01T14:45:50.032Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/6e/cf826fae916b8658848d7b9f38d88da6396895c676e8086fc0988073aaf8/pillow-12.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb", size = 2556579, upload-time = "2026-04-01T14:45:52.529Z" },
+]
diff --git a/lib/controller/shifter_device_telemetry.dart b/lib/controller/shifter_device_telemetry.dart
new file mode 100644
index 0000000..6492c6e
--- /dev/null
+++ b/lib/controller/shifter_device_telemetry.dart
@@ -0,0 +1,20 @@
+import 'package:abawo_bt_app/model/shifter_types.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+
+final shifterDeviceTelemetryCacheProvider = StateNotifierProvider<
+ ShifterDeviceTelemetryCache, Map>(
+ (ref) => ShifterDeviceTelemetryCache(),
+);
+
+class ShifterDeviceTelemetryCache
+ extends StateNotifier