-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbend.py
More file actions
170 lines (131 loc) · 5.54 KB
/
bend.py
File metadata and controls
170 lines (131 loc) · 5.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
#!/usr/bin/env python3
"""
Bend Module
===========
Bends/warps a pattern by mapping Cartesian coordinates to polar coordinates.
This transforms a flat pattern into a curved one:
- Input X coordinate → angle along the arc
- Input Y coordinate → radial distance from arc center
Think of it like wrapping a flat sheet around a cylinder.
Example: A horizontal line from (0,0) to (100,0) bent with sweep_angle=90
becomes a 90° arc segment.
"""
import numpy as np
from fractions import Fraction
from math import pi
from main import TransformModule
class BendModule(TransformModule):
"""
Bend transformation: maps X→angle, Y→radius.
Configuration:
radius: Base radius of the bend (distance from center to where Y=0 maps)
start_angle: Starting angle in degrees (where X=0 maps)
sweep_angle: Angular range to map X coordinates into (degrees)
x_range: The X range of input to map (default: auto-fit)
center_x, center_y: Center point of the bend arc
direction: 1 = bend upward (convex), -1 = bend downward (concave)
The transformation:
- x_input in [0, x_range] → angle in [start_angle, start_angle + sweep_angle]
- y_input → radius offset from base radius
"""
def _load_config(self):
"""Load bend configuration."""
self.radius = self._getfloat('radius', 200.0)
self.start_angle = self._getfloat('start_angle', 0.0)
self.sweep_angle = self._getfloat('sweep_angle', 90.0)
self.x_range = self._getfloat('x_range', 0.0) # 0 = auto
self.center_x = self._getfloat('center_x', 0.0)
self.center_y = self._getfloat('center_y', 0.0)
self.direction = self._getint('direction', 1)
# Convert to radians
self.start_rad = self.start_angle * pi / 180
self.sweep_rad = self.sweep_angle * pi / 180
# Center as complex
self.center = self.center_x + 1j * self.center_y
# If x_range not specified, compute from radius and sweep
# Arc length = radius * angle_in_radians
if self.x_range <= 0:
self.x_range = self.radius * abs(self.sweep_rad)
def transform(self, z: complex, t: float) -> complex:
"""
Bend the input coordinates.
Maps (x, y) → polar coordinates centered on the arc.
"""
x = z.real
y = z.imag
# Map x to angle
# x=0 → start_angle, x=x_range → start_angle + sweep_angle
if self.x_range != 0:
normalized_x = x / self.x_range
else:
normalized_x = 0
angle = self.start_rad + normalized_x * self.sweep_rad
# Map y to radius offset
# y=0 → base radius, y>0 → further from center (if direction=1)
r = self.radius + self.direction * y
# Convert to Cartesian
result = self.center + r * np.exp(1j * angle)
return result
@property
def natural_period(self) -> Fraction:
"""Bend doesn't affect period."""
return Fraction(1, 1)
@property
def is_generator(self) -> bool:
return False
def __repr__(self):
return f"BendModule(r={self.radius}, sweep={self.sweep_angle}°)"
class BendVerticalModule(TransformModule):
"""
Vertical bend: maps Y→angle, X→radius.
Like BendModule but rotated 90° - bends vertical patterns into arcs.
Configuration:
radius: Base radius of the bend
start_angle: Starting angle in degrees
sweep_angle: Angular range to map Y coordinates into
y_range: The Y range of input to map (default: auto-fit)
center_x, center_y: Center point of the bend arc
direction: 1 = bend rightward, -1 = bend leftward
"""
def _load_config(self):
"""Load bend configuration."""
self.radius = self._getfloat('radius', 200.0)
self.start_angle = self._getfloat('start_angle', -90.0) # Start pointing down
self.sweep_angle = self._getfloat('sweep_angle', 90.0)
self.y_range = self._getfloat('y_range', 0.0) # 0 = auto
self.center_x = self._getfloat('center_x', 0.0)
self.center_y = self._getfloat('center_y', 0.0)
self.direction = self._getint('direction', 1)
# Convert to radians
self.start_rad = self.start_angle * pi / 180
self.sweep_rad = self.sweep_angle * pi / 180
# Center as complex
self.center = self.center_x + 1j * self.center_y
# If y_range not specified, compute from radius and sweep
if self.y_range <= 0:
self.y_range = self.radius * abs(self.sweep_rad)
def transform(self, z: complex, t: float) -> complex:
"""
Bend the input coordinates (vertical version).
"""
x = z.real
y = z.imag
# Map y to angle
if self.y_range != 0:
normalized_y = y / self.y_range
else:
normalized_y = 0
angle = self.start_rad + normalized_y * self.sweep_rad
# Map x to radius offset
r = self.radius + self.direction * x
# Convert to Cartesian
result = self.center + r * np.exp(1j * angle)
return result
@property
def natural_period(self) -> Fraction:
return Fraction(1, 1)
@property
def is_generator(self) -> bool:
return False
def __repr__(self):
return f"BendVerticalModule(r={self.radius}, sweep={self.sweep_angle}°)"