Пожаловаться
![[Laser cut and engrave] Lampada circolare a cerchi concentrici con illuminazione a LED](https://storage-public.atomm.com/xtool/community/attachment/xtool-community/23c9b242-bb66-4468-8e84-12eefd37f8cf.jpeg?x-oss-process=image/resize,m_fill,w_1280,h_960,limit_0/interlace,1/format,webp/auto-orient,1)
![[Laser cut and engrave] Lampada circolare a cerchi concentrici con illuminazione a LED](https://storage-public.atomm.com/xtool/community/attachment/xtool-community/23c9b242-bb66-4468-8e84-12eefd37f8cf.jpeg?x-oss-process=image/resize,m_fill,w_160,h_120,limit_0/interlace,1/format,webp/auto-orient,1)
![[Laser cut and engrave] Lampada circolare a cerchi concentrici con illuminazione a LED](https://storage-public.atomm.com/xtool/community/attachment/xtool-community/818224dc-ae3d-4197-b638-31c72fcca1ba.jpeg?x-oss-process=image/resize,m_fill,w_160,h_120,limit_0/interlace,1/format,webp/auto-orient,1)
![[Laser cut and engrave] Lampada circolare a cerchi concentrici con illuminazione a LED](https://storage-public.atomm.com/xtool/community/attachment/xtool-community/abe142eb-23ee-4937-a66b-8daeac73cb74.jpeg?x-oss-process=image/resize,m_fill,w_160,h_120,limit_0/interlace,1/format,webp/auto-orient,1)
Ripple Lamp — деревянная капля воды, вырезанная лазером

disc
.xs
Показать ещё 7
Сценарий применения
Лазерная обработка
Машина и модуль
xTool M2
10WИспользуемый материал
фанера
Светодиодная лента
6
529
Информация
Фанерный диск толщиной 300 мм, вырезанный с одним непрерывным спиральным прорезом, чтобы его можно было драпировать
более двенадцати формированных рёбер, принимающих форму рябь, распространяющихся от
Падающая капля. Светодиодная лента внутри базового кольца освещает её с края.
Профиль не рисуется вручную. Это функция Бесселя, то есть
фактическое решение для капли, приземленного на круглую мембрану, то есть интервал между
Кольца и то, как они исчезают наружу, исходят из физики.
Интересная проблема здесь в том, что плоский диск математически не может стать
волнистая поверхность, не меняющая собственной окружности.
Концентрические слоты не могут этого сделать, потому что замкнутое кольцо не может
Изменить его окружность. Спираль может, потому что она оказывается сама на себе, как
Пружина часов. Вот почему узор выглядит именно так.
Вырезали на диодном лазере мощностью 10 Вт из фанеры тополя толщиной 3,5 мм. Вся геометрия — это
генерируется четырьмя короткими скриптами на Python, чтобы можно было изменить диаметр,
Амплитуда или высота тона слота и регенерировать файлы.
Я опубликовал это в Facebook:
https://www.facebook.com/groups/lasermaterials/permalink/1338253281623420/
Я выложил это на Reddit:
https://www.reddit.com/r/xToolOfficial/s/fd0xtk4Syw
____
Этот проект — мой проект, созданный через несколько этапов тестирования и сборки. Любое сходство с другими лампами или проектами, которые вы могли видеть, — совпадение, я искал перед началом работы и не нашёл ничего подобного, но если вы знаете о предыдущих подобных работах, мне бы очень хотелось это увидеть.
Инструкция
1
Шаг1: Получите материалы + ссылку на социальные сети
- Фанера 3,5 мм, примерно 300 x 900
мм - светодиодная лента COB 24 V, тёплый белый, около 900
мм — древесный
клей — мелкая наждачная бумага, зернистость
240 Я опубликовал это в Facebook:
240 Я опубликовал это в Facebook:
https://www.facebook.com/groups/lasermaterials/permalink/1338253281623420/
Я выложил это на Reddit:
https://www.reddit.com/r/xToolOfficial/s/fd0xtk4Syw
2
Шаг2: Измерьте фанеру
Не пропускай это. Мой лист продавался как 4 мм, а суппорты — 3,5. Хуже того,
он даже не был постоянным: между углами одного листа было около 0,3 мм
.Первая ступица, которую я вырезал, использовала зазор 0,15 мм. Восемь рёбер прошли без проблем,
а последние четыре были раздавлены, пытаясь их заставить. Слоты в файле теперь
увеличены на 0,5 мм, что звучит небрежно, но именно это работает, когда добавляешь
вариацию листа к лазерному угольнику.
3
Шаг3: Сначала отрежьте расчёску для теста — ПО ЖЕЛАНИЮ
Сделайте этот шаг, если хотите максимально точно регенерировать SVG:
hub.py также даёт небольшой гребень с шестью слотами — от 3,7 до 4,2 мм за
шаг 0,1 мм. Вырежьте только это, а потом попробуйте вставить клочок фанеры в
каждый слот
.Вам нужен тот, где обрезок скользит с лёгким давлением большим пальцем и
не выпадает сам по себе. Это число попадает в хаб. Две минуты здесь
спасают целую испорченную часть.
4
Шаг4: Запустить скрипты — ПО ЖЕЛАНИЮ
Сделайте этот шаг, если хотите максимально точно сгенерировать svg:
python3 disc.py
python3 rib.py
python3 hub.py
python3 ring.py
Каждый из них печатает резюме перед записью SVG, чтобы можно было проверить
цифры, не открывая ничего. Диск должен показывать примерно 15 оборотов и
ленту длиной 6,6 м.
5
Шаг5: Настройте монтаж
Перед тем, как зафиксировать, вырежьте небольшой квадрат спирального узора на вырезе.
Мои настройки на диоде 10 Вт в 3,5 мм тополе были 100% мощности, 4 мм/с,
1 проход, воздушная помощь включена.
6
Шаг6: Всё вырезать

Предпросмотр

Предпросмотр
Предпросмотр

Предпросмотр
- Диск: 1
- Ребра: 12
- Втулка: 2 (склеиваются)
- Кольцо: 3 полоски и 3 скрепительные пластины
7
Шаг7: Разберитесь, что это за рёбра, прежде чем что-то клеить

Предпросмотр

Предпросмотр
Рёбра — это не плоские пластины, которые вы складываете друг к другу. Они стоят на краю, как
рамы внутри корпуса лодки, и именно форма их верхнего края удерживает
волну. Диск лежит на всех двенадцати одновременно.
Высота составляет 12,4 мм на внутреннем крае, до 22,6 мм на первом гребне,
до 16,3 мм в впадине, а на втором гребне — до 21,2 мм. Эта
кривая — профиль Бесселя, вырезанный в дереве.
8
Шаг8: Постройте хаб

Предпросмотр

Предпросмотр
Склейте два диска втулки лицом к лицу, выровняя слоты. Надёжный способ:
сложить их стопкой, протолкнуть два отрезанных ребра через противоположные прорези для выравнивания,
затем склеить и дать высохнуть вот так.
Удвоение даёт 7 мм сцепления на каждом ребре вместо 3,5 мм.
Затем вставляйте рёбра
Затем вставляйте рёбра
9
Шаг9: Освободите диск

Предпросмотр
Вдоль одного радиуса расположено 14 крошечных вкладок размером 0,6 мм, которые удерживают спираль в
регистре. Без них всё расслабляется в тот момент, когда ты поднимаешь её с
кровати.
Положите диск ровно и разрежьте каждый из них острым ножом. Все из них.
Теперь это лента длиной 6,6 м, закрепленная только посередине и на ободе. Неси его ровно
на листе карты. Не поднимай её за край
10
Шаг10: Форма и клей

Предпросмотр
Положите диск поверх паука, центрируйте его, а затем склеивайте наружу по ходу дела.
Утяжелите — одной только гравитации недостаточно, чтобы ленты закручивались на рёбрах.
11
Шаг11: Базовое кольцо и освещение
Диск в итоге достигает диаметра 293 мм после изогнутости, а не 300, потому что материал движется вдоль волны, а не прямо. Кольцо зависит от этого.
Приклеить кольцо к нижней стороне диска и утяжелить его.
Затем приклеить COB-полоску на внутреннюю сторону, на 8–10 мм ниже верхнего края, направленную внутрь.
12
Шаг12: КОД PYTHON
"""
Ripple lamp - rib generator.
One rib. I cut 12 of these and stand them on edge in the hub like the
spokes of a wheel. The wavy top edge is what the disc lies on, the straight
bottom edge sits flat on the table, that's what sets the height, not the hub.
"""
import math
# ---- same wave as the disc, don't touch these separately ----
R = 150.0
A0 = 9.0
KZ = 13.3237
K = KZ / R
RIB_R0 = 12.0 # inner end, any closer and 12 ribs collide at the centre
CLEAR = 10.0 # air under the lowest point of the wave, room for the hub
MARGIN = 10.0 # pull the outer end back so the rib can't poke past the disc
OUT = "rib.svg"
def bessel(n, x, steps=1500):
total = 0.0
for i in range(steps + 1):
t = math.pi * i / steps
w = 1.0 if 0 < i < steps else 0.5
total += w * math.cos(n * t - x * math.sin(t))
return total / steps
N = 30000
STEP = R / N
HEIGHT = [-A0 * bessel(0, K * STEP * i) for i in range(N + 1)]
RIM = HEIGHT[N]
HEIGHT = [h - RIM for h in HEIGHT] # measure everything from the rim
def height(r):
return HEIGHT[max(0, min(N, int(round(r / STEP))))]
# ---- how far the disc actually reaches once it's curved ----
# This caught me out. The flat disc is 300 mm but the material has to travel
# along the wave, not straight, so the finished thing comes out smaller.
# The ribs live in the finished shape, so they use this radius, not 150.
walk = 0.0
R_PROJ = R
for i in range(1, N + 1):
walk += math.hypot(STEP, HEIGHT[i] - HEIGHT[i - 1])
if walk >= R:
R_PROJ = STEP * i
break
RIB_R1 = R_PROJ - MARGIN
BASE = min(HEIGHT) - CLEAR
RIB_L = RIB_R1 - RIB_R0
# --- draw it: wavy top left to right, then straight back along the bottom ---
X0, Y0 = 5.0, 5.0 + (max(HEIGHT) - BASE)
pts = []
steps = 400
for i in range(steps + 1):
r = RIB_R0 + RIB_L * i / steps
pts.append((X0 + (r - RIB_R0), Y0 - (height(r) - BASE)))
d = "M " + " L ".join(f"{p[0]:.3f} {p[1]:.3f}" for p in pts)
d += f" L {X0 + RIB_L:.3f} {Y0:.3f} L {X0:.3f} {Y0:.3f} Z"
W, H = RIB_L + 10, Y0 + 5
svg = [f'<svg xmlns="http://www.w3.org/2000/svg" width="{W:.0f}mm" height="{H:.0f}mm" '
f'viewBox="0 0 {W:.0f} {H:.0f}">',
'<g fill="none" stroke="#000000" stroke-width="0.1">',
f'<path d="{d}"/>', '</g></svg>']
open(OUT, "w").write("\n".join(svg))
edge = 0.0
for i in range(1, len(pts)):
edge += math.hypot(pts[i][0] - pts[i - 1][0], pts[i][1] - pts[i - 1][1])
print(f"{OUT} written - cut 12 of these")
print(f"flat disc is {2 * R:.0f} mm, curved it ends up {2 * R_PROJ:.0f} mm")
print(f"rib runs r={RIB_R0} to r={RIB_R1:.1f}, so {RIB_L:.1f} mm long")
print(f"baseline sits {-BASE:.2f} mm below the disc rim")
print()
print(" r height above baseline")
for rr in (12, 25, 43, 60, 79, 100, 114, 130, RIB_R1):
print(f"{rr:6.1f} {height(rr) - BASE:9.2f} mm")
print()
print(f"tallest {max(height(RIB_R0 + RIB_L * i / 400) - BASE for i in range(401)):.1f} mm, "
f"shortest {min(height(RIB_R0 + RIB_L * i / 400) - BASE for i in range(401)):.1f} mm")
print(f"one rib is {(edge + RIB_L + 2 * (height(RIB_R0) - BASE)) / 1000:.2f} m of cutting, "
f"x12 = {12 * (edge + RIB_L + 2 * (height(RIB_R0) - BASE)) / 1000:.2f} m")
print(f"spacing at the rim {2 * math.pi * RIB_R1 / 12:.0f} mm")
"""
Ripple lamp - hub generator.
The hub is the little disc that holds the 12 ribs at 30 degrees apart.
Slots are open at the rim so a rib slides in sideways along the table.
They can't drop in from above, a rib is 131 mm long and the slot is 26.
I cut this twice and glue the two together, 7 mm of grip instead of 3.5
keeps the ribs from wobbling.
"""
import math
# ---- the important bit ----
# My sheet was sold as 4 mm and calipers say 3.5. It also isn't even across
# the sheet, I get about +/- 0.3 mm from corner to corner. First time round I
# used 3.65 mm slots and 4 of the 12 ribs wouldn't go in, so now I measure the
# THICKEST spot I can find and add clearance on top of that.
T_MEASURED = 3.5 # thickest reading I got with the calipers
CLEARANCE = 0.5 # covers sheet variation plus the charring from the laser
SLOT_W = T_MEASURED + CLEARANCE
HUB_R = 38.0 # hub radius
SLOT_R0 = 12.0 # slot bottom, this is what sets where the rib stops
N_RIB = 12
# Small comb I cut first to check the fit before committing to the hub.
# Each tooth gap is 0.1 mm wider than the last.
MAKE_TEST_COMB = True
COMB_START = 3.7
COMB_STEP = 0.1
COMB_N = 6
OUT = "hub.svg"
paths = []
HX = HY = HUB_R + 5.0
# ---- hub outline, one closed path ----
# Going round: arc along the rim, dive in to the slot bottom, round the end,
# back out, arc to the next slot.
hw = SLOT_W / 2
t_out = math.sqrt(HUB_R ** 2 - hw ** 2) # where the slot side meets the rim
d = []
first = None
for k in range(N_RIB):
a = k * 2 * math.pi / N_RIB
ca, sa = math.cos(a), math.sin(a)
def pt(t, off):
return (HX + t * ca - off * sa, HY + t * sa + off * ca)
out_m, in_m = pt(t_out, -hw), pt(SLOT_R0, -hw)
in_p, out_p = pt(SLOT_R0, hw), pt(t_out, hw)
if k == 0:
first = out_m
d.append(f"M {out_m[0]:.3f} {out_m[1]:.3f}")
else:
d.append(f"A {HUB_R:.3f} {HUB_R:.3f} 0 0 1 {out_m[0]:.3f} {out_m[1]:.3f}")
d.append(f"L {in_m[0]:.3f} {in_m[1]:.3f}")
d.append(f"A {hw:.3f} {hw:.3f} 0 0 1 {in_p[0]:.3f} {in_p[1]:.3f}")
d.append(f"L {out_p[0]:.3f} {out_p[1]:.3f}")
d.append(f"A {HUB_R:.3f} {HUB_R:.3f} 0 0 1 {first[0]:.3f} {first[1]:.3f} Z")
paths.append(" ".join(d))
cut = 2 * math.pi * HUB_R + N_RIB * (2 * (t_out - SLOT_R0) + math.pi * hw)
# ---- fit test comb ----
W = 2 * HUB_R + 10
H = 2 * HUB_R + 10
if MAKE_TEST_COMB:
cw, ch, tooth = 10.0, 22.0, 6.0
x0, y0 = 5.0, H + 8.0
total_w = COMB_N * (cw + COMB_START) + 10
paths.append(f"M {x0:.3f} {y0:.3f} L {x0 + total_w:.3f} {y0:.3f} "
f"L {x0 + total_w:.3f} {y0 + ch:.3f} L {x0:.3f} {y0 + ch:.3f} Z")
x = x0 + 5.0
for i in range(COMB_N):
w = COMB_START + i * COMB_STEP
paths.append(f"M {x:.3f} {y0:.3f} L {x + w:.3f} {y0:.3f} "
f"L {x + w:.3f} {y0 + tooth + 6:.3f} L {x:.3f} {y0 + tooth + 6:.3f} Z")
cut += 2 * (w + tooth + 6)
x += w + cw
H = y0 + ch + 5
W = max(W, x0 + total_w + 5)
svg = [f'<svg xmlns="http://www.w3.org/2000/svg" width="{W:.0f}mm" height="{H:.0f}mm" '
f'viewBox="0 0 {W:.0f} {H:.0f}">', '<g fill="none" stroke="#000000" stroke-width="0.1">']
svg += [f'<path d="{p}"/>' for p in paths]
svg.append('</g></svg>')
open(OUT, "w").write("\n".join(svg))
print(f"{OUT} written")
print(f"hub {2 * HUB_R:.0f} mm across, {N_RIB} open slots")
print(f"slot width {SLOT_W:.2f} mm for {T_MEASURED} mm plywood "
f"({CLEARANCE} mm clearance)")
print(f"slot depth {HUB_R - SLOT_R0:.0f} mm, tooth {2 * math.pi * HUB_R / N_RIB - SLOT_W:.1f} mm wide")
print(f"solid middle {2 * SLOT_R0:.0f} mm across")
if MAKE_TEST_COMB:
widths = ", ".join(f"{COMB_START + i * COMB_STEP:.1f}" for i in range(COMB_N))
print(f"test comb slots: {widths} mm")
print(f"cut length {cut / 1000:.2f} m - cut TWO of the hub")
print(f"sheet {W:.0f} x {H:.0f} mm")
"""
Ripple lamp - top disc generator.
Cuts a 300 mm disc with one long variable-pitch spiral slot in it.
Once it comes off the laser the whole thing is basically one 6.6 m
ribbon held at the hub and at the rim, so it can drape over the ribs.
"""
import math
# ---- disc ----
R = 150.0 # disc radius, so 300 mm across
R_START = 15.0 # spiral starts here, everything inside stays solid
R_END = 138.5 # spiral stops here, the rest is my glue land for the cylinder
P0 = 6.0 # radial pitch near the centre
P1 = 11.0 # radial pitch out at the rim
SLOT_W = 2.0 # slot width, this is also my closing budget when it bends
T = 3.5 # actual measured thickness of my plywood
# ---- wave shape ---
# I'm using the J0 Bessel function instead of a cosine because that's the real
# shape a drop makes on a round membrane, and the peak comes out gentler.
A0 = 9.0 # amplitude
KZ = 13.3237 # 3rd max of J0 -> dip in the middle, dip at the rim
K = KZ / R
# --- transport tabs ----
# Without these the spiral unwinds the second I lift it off the bed.
# They all sit on one radius so I can snap them with a single pass of a blade.
TAB_ANGLE = 0.0
TAB_W = 0.6
OUT = "disc.svg"
def bessel(n, x, steps=1500):
"""J_n via the integral form. No scipy needed."""
total = 0.0
for i in range(steps + 1):
t = math.pi * i / steps
w = 1.0 if 0 < i < steps else 0.5
total += w * math.cos(n * t - x * math.sin(t))
return total / steps
# Precompute the profile once, it's slow otherwise.
N = 20000
STEP = R / N
HEIGHT = [-A0 * bessel(0, K * STEP * i) for i in range(N + 1)]
def height(r):
return HEIGHT[max(0, min(N, int(r / STEP)))]
def curvature(r, h=1.0):
return (height(r + h) - 2 * height(r) + height(r - h)) / h ** 2
def pitch(r):
"""Tighter pitch in the middle where the wood has to work hardest."""
u = (r - R_START) / (R_END - R_START)
return P0 + (P1 - P0) * max(0.0, min(1.0, u))
# ---- walk the spiral ----
CX = CY = 155.0
polar = []
theta, r = 0.0, R_START
d_theta = 0.0008
while r < R_END:
polar.append((theta, r))
r += pitch(r) / (2 * math.pi) * d_theta
theta += d_theta
polar.append((theta, R_END))
TURNS = theta / (2 * math.pi)
points = [(CX + rr * math.cos(th), CY + rr * math.sin(th)) for th, rr in polar]
# running length along the centreline, I need it to place the tabs
length = [0.0]
for i in range(1, len(points)):
length.append(length[-1] + math.hypot(points[i][0] - points[i - 1][0],
points[i][1] - points[i - 1][1]))
RIBBON = length[-1]
# every time the spiral crosses the tab radius, leave a bridge
gaps = []
for i in range(1, len(polar)):
a_prev = (polar[i - 1][0] - TAB_ANGLE) % (2 * math.pi)
a_now = (polar[i][0] - TAB_ANGLE) % (2 * math.pi)
if a_now < a_prev and 15 < length[i] < RIBBON - 15:
gaps.append((length[i] - TAB_W / 2, length[i] + TAB_W / 2))
def slot_outline(seg):
"""Offset a run of centreline points both ways and cap the ends round."""
hw = SLOT_W / 2
left, right = [], []
n = len(seg)
for i in range(n):
a, b = max(0, i - 1), min(n - 1, i + 1)
dx, dy = seg[b][0] - seg[a][0], seg[b][1] - seg[a][1]
m = math.hypot(dx, dy)
if m == 0:
continue
nx, ny = -dy / m, dx / m
left.append((seg[i][0] + hw * nx, seg[i][1] + hw * ny))
right.append((seg[i][0] - hw * nx, seg[i][1] - hw * ny))
d = "M " + " L ".join(f"{p[0]:.3f} {p[1]:.3f}" for p in left)
d += f" A {hw:.3f} {hw:.3f} 0 0 1 {right[-1][0]:.3f} {right[-1][1]:.3f}"
d += " L " + " L ".join(f"{p[0]:.3f} {p[1]:.3f}" for p in reversed(right[:-1]))
d += f" A {hw:.3f} {hw:.3f} 0 0 1 {left[0][0]:.3f} {left[0][1]:.3f} Z"
return d
paths = [f"M {CX - R:.3f} {CY:.3f} A {R:.3f} {R:.3f} 0 1 1 {CX + R:.3f} {CY:.3f} "
f"A {R:.3f} {R:.3f} 0 1 1 {CX - R:.3f} {CY:.3f} Z"]
cut = 2 * math.pi * R
# break the spiral into runs, skipping the tab positions
run = []
for i, p in enumerate(points):
in_gap = any(g0 <= length[i] <= g1 for g0, g1 in gaps)
if in_gap:
if len(run) > 3:
paths.append(slot_outline(run))
cut += 2 * (length[i] - length[i - len(run)]) + math.pi * SLOT_W
run = []
else:
run.append(p)
if len(run) > 3:
paths.append(slot_outline(run))
cut += 2 * (length[-1] - length[-len(run)]) + math.pi * SLOT_W
SIZE = 310
svg = [f'<svg xmlns="http://www.w3.org/2000/svg" width="{SIZE}mm" height="{SIZE}mm" '
f'viewBox="0 0 {SIZE} {SIZE}">', '<g fill="none" stroke="#000000" stroke-width="0.1">']
svg += [f'<path d="{d}"/>' for d in paths]
svg.append('</g></svg>')
open(OUT, "w").write("\n".join(svg))
# ---- sanity check before I waste a sheet ----
print(f"{OUT} written")
print(f"single spiral, {TURNS:.1f} turns, pitch {P0} -> {P1} mm")
print(f"ribbon length {RIBBON / 1000:.2f} m")
print(f"{len(gaps)} transport tabs, {TAB_W} mm each")
print(f"solid centre {2 * (R_START - SLOT_W / 2):.0f} mm across, "
f"solid rim {R - R_END - SLOT_W / 2:.1f} mm wide")
print()
print(" r pitch height slot closes ribbon w/t")
for rr in (15, 25, 43, 60, 79, 100, 114, 130, 150):
c = abs(curvature(rr))
p = pitch(rr)
print(f"{rr:5.0f} {p:6.1f} {height(rr) - height(R):+8.2f} "
f"{T * c * p:9.2f} mm {(p - SLOT_W) / T:9.2f}")
print()
print(f"wave height {max(HEIGHT) - min(HEIGHT):.1f} mm")
print(f"total cut {cut / 1000:.1f} m")
"""
Ripple lamp - base ring generator.
A flat strip with a living hinge cut into it, so it rolls up into the ring the
disc sits on. The LED strip goes on the inside face near the top and fires
inward across the underside of the waves.
The strip is 875 mm around, which doesn't fit my sheet, so it comes out in
three pieces joined with splice plates on the inside.
"""
import math
# ---- geometry that has to agree with the disc ----
R = 150.0 # flat disc radius
A0 = 9.0
KZ = 13.3237
K = KZ / R
T = 3.5 # measured plywood thickness
RING_OUT_R = 141.0 # outside radius of the ring
HEIGHT_MM = 45.0 # how tall the ring is
N_SEG = 3 # pieces, each has to fit my sheet
# ---- living hinge ----
HINGE_PITCH = 8.0 # spacing within one row
MARGIN = 3.0 # keep slits off the top and bottom edges
OVERLAP = 3.0 # how much the two rows overlap in the middle
END_SOLID = 15.0 # uncut zone at each end, that's the glue land
SPLICE_W, SPLICE_H = 30.0, 39.0
OUT = "ring.svg"
def bessel(n, x, steps=1500):
total = 0.0
for i in range(steps + 1):
t = math.pi * i / steps
w = 1.0 if 0 < i < steps else 0.5
total += w * math.cos(n * t - x * math.sin(t))
return total / steps
# Work out how big the disc really ends up and where its solid rim lands,
# because the ring has to glue onto that band and nowhere else.
N = 30000
STEP = R / N
H = [-A0 * bessel(0, K * STEP * i) for i in range(N + 1)]
RIM = H[N]
H = [h - RIM for h in H]
walk = [0.0]
for i in range(1, N + 1):
walk.append(walk[-1] + math.hypot(STEP, H[i] - H[i - 1]))
def projected(flat_r):
"""Where a point that sits at flat_r on the flat disc ends up once curved."""
for i in range(N + 1):
if walk[i] >= flat_r:
return STEP * i
return R
R_PROJ = projected(R) # finished disc radius
RIM_INNER = projected(139.5) # inner edge of the solid rim, curved
RING_IN_R = RING_OUT_R - T
MID_R = RING_OUT_R - T / 2
LENGTH = 2 * math.pi * MID_R # measured on the middle of the wall
SEG_L = LENGTH / N_SEG
paths = []
cut = 0.0
def rect(x, y, w, h):
return (f"M {x:.3f} {y:.3f} L {x + w:.3f} {y:.3f} "
f"L {x + w:.3f} {y + h:.3f} L {x:.3f} {y + h:.3f} Z")
# ---- the three strips ----
# Two rows of slits, offset by half a pitch, overlapping in the middle. The
# overlap is what actually does the bending - the wood twists there.
row_a = (MARGIN, (HEIGHT_MM + OVERLAP) / 2)
row_b = ((HEIGHT_MM - OVERLAP) / 2, HEIGHT_MM - MARGIN)
for seg in range(N_SEG):
x0, y0 = 3.0, 3.0 + seg * (HEIGHT_MM + 8)
paths.append(rect(x0, y0, SEG_L, HEIGHT_MM))
cut += 2 * (SEG_L + HEIGHT_MM)
x = x0 + END_SOLID
row = 0
while x < x0 + SEG_L - END_SOLID:
a, b = row_a if row % 2 == 0 else row_b
paths.append(f"M {x:.3f} {y0 + a:.3f} L {x:.3f} {y0 + b:.3f}")
cut += b - a
x += HINGE_PITCH / 2
row += 1
# ---- splice plates, glued on the inside over each butt joint ----
sy = 3.0 + N_SEG * (HEIGHT_MM + 8) + 5
for k in range(N_SEG):
paths.append(rect(3.0 + k * (SPLICE_W + 6), sy, SPLICE_W, SPLICE_H))
cut += 2 * (SPLICE_W + SPLICE_H)
W = max(3.0 + SEG_L + 3, 3.0 + N_SEG * (SPLICE_W + 6) + 3)
HT = sy + SPLICE_H + 3
svg = [f'<svg xmlns="http://www.w3.org/2000/svg" width="{W:.0f}mm" height="{HT:.0f}mm" '
f'viewBox="0 0 {W:.0f} {HT:.0f}">',
'<g fill="none" stroke="#000000" stroke-width="0.1">']
svg += [f'<path d="{p}"/>' for p in paths]
svg.append('</g></svg>')
open(OUT, "w").write("\n".join(svg))
print(f"{OUT} written")
print(f"finished disc {2 * R_PROJ:.0f} mm across")
print(f"ring {2 * RING_OUT_R:.0f} mm outside, {2 * RING_IN_R:.0f} mm inside")
print(f"overhang {R_PROJ - RING_OUT_R:.1f} mm all round")
print(f"strip {LENGTH:.0f} mm long -> {N_SEG} pieces of {SEG_L:.1f} mm")
print()
print("checks:")
print(f" disc solid rim sits between r={RIM_INNER:.1f} and r={R_PROJ:.1f} once curved")
print(f" ring wall sits between r={RING_IN_R:.1f} and r={RING_OUT_R:.1f}")
ok = RIM_INNER <= RING_IN_R and RING_OUT_R <= R_PROJ
print(f" wall lands on the glue band: {'yes' if ok else 'NO'}")
print(f" ribs must stop before r={RING_IN_R - 1:.1f} - set MARGIN in rib.py to 10")
print(f" bend radius {MID_R:.1f} mm, strain without the hinge would be "
f"{T / 2 / MID_R * 100:.2f}%")
print(f" LED strip runs about {2 * math.pi * RING_IN_R:.0f} mm around the inside")
print()
print(f"cut length {cut / 1000:.2f} m")
print(f"sheet {W:.0f} x {HT:.0f} mm")
Комментарии
Ремиксы
Вам также может понравиться

GIF
Лампа для расслабления Zen LED
10
56

Спиральная настольная лампа Helix — настольный светильник середины века
2
6

Шестиугольная направленная башня с крышей
14
23

Деревянная настольная лампа в форме зонта с тёплым фональным освещением
0
2

Деревянная луна и горная лампа
8
32
Диммируемая лампа с взаимозаменяемыми абажурами и световыми проекциями
14
42

Лампа Хамса.
19
56

$1.99
$1.99
Кредиты199
Прямоугольная деревянная настольная лампа с синим светодиодным светом и органическим вырезом
0

Деревянный фонарь, лазерная резка. Декоративная лампа. Абстрактный волновой узор. Домашний декор. Современный интерьер
13
132

Персонализированная светодиодная лампа Heart Horse — лазерный ночной светильник
2
2
Настольная лампа в западной тематике с абажуром из ткани в стиле лошади и коренных американцев
4
2

Современная геометрическая спиральная настольная лампа с тёплым фональным освещением
36
168

Геометрическое украшение свечей / настольная лампа
1
2

Сочленённая деревянная настольная лампа с шторой
22
74

Светодиодная акриловая / стеклянная ночная лампа с деревянным основанием для дома.
0
2

Лазерно гравированный акриловый светодиод с абстрактным дизайном человеческой фигуры
6
41

Настольная лампа
295
1300

$2.20
$2.20
Кредиты220
Комплект 3D-светодиодных ламп. Акриловые фонарь, лазерные напильники. Деревянная лампа для ночника. Светодиодная табличка Boho с подсвечиванием. Cactus svg. Лебедь. Грибы. Вино
17


