신고

리플 램프 - 레이저 컷 나무 물방울

설명서
disc.xs
disc
.xs
응용 시나리오
레이저 가공
기계 및 모듈
xTool M2
10W
사용된 재료
합판
LED 스트립
gift
6
530

정보

      300mm 합판 디스크를 하나의 연속된 나선형 슬롯으로 절단하여 드레이핑할 수 있습니다
열두 개의 모양 갈비뼈 위에 있으며, 물결이 퍼져 나가는 모양을 띱니다.
떨어지는 떨어지는 물결. 베이스 링 안에 LED 스트립이 가장자리에서 조명을 제공합니다.

옆모습은 손으로 그려지지 않습니다. 이것은 베셀 함수이며,
원형 막에 떨어지는 낙하의 실제 해, 즉 간격은
고리와 그 고리가 바깥쪽으로 사라지는 방식은 물리 법칙에서 비롯된 것입니다.

여기서 흥미로운 점은 평평한 원반이 수학적으로 다음과 같은 것이 될 수 없다는 것입니다
자신의 둘레를 바꾸지 않은 물결 모양의 표면. 
동심원 슬롯은 그럴 수 없는데, 닫힌 링은 할 수 없기 때문입니다
둘레를 바꾸는 것. 나선형은 스스로에게 말이 부딪히는 것처럼 말이죠
시계 스프링. 그래서 패턴이 이렇게 보이는 거예요.

3.5mm 포플러 합판에 10W 다이오드 레이저로 절단했습니다. 모든 기하학은
4개의 짧은 Python 스크립트로 생성되어 직경을 변경할 수 있습니다,
진폭 또는 슬롯 피치를 조정하고 파일을 재생성합니다.

페이스북에 올렸어요: 

https://www.facebook.com/groups/lasermaterials/permalink/1338253281623420/

레딧에 올렸습니다: 

https://www.reddit.com/r/xToolOfficial/s/fd0xtk4Syw

____
이 프로젝트는 여러 차례 테스트와 조립을 거쳐 완성된 제 설계입니다. 다른 램프나 프로젝트와 닮은 점은 우연일 뿐입니다. 시작하기 전에 검색해봤지만 비슷한 것은 찾지 못했지만, 이와 유사한 이전 작업을 아신다면 진심으로 보고 싶습니다.
    

설명서

1

단계1: 자료 + 소셜 미디어 링크 받아보세요

- 3.5mm 합판, 대략 300 x 900mm 
- 24 V COB LED STRIP, 웜 화이트, 약 900mm
- 목공 접착제
- 고운 사포, 240방

 페이스북에 올렸습니다: 
 
2

단계2: 합판 크기를 측정하세요

이걸 건너뛰지 마세요. 제 시트는 4mm로 판매되었고, 캘리퍼스에는 3.5mm라고 적혀 있었습니다. 더 나쁜 건
, 일관성도 없었다는 거예
요: 같은 시트의 모서리 사이에 약 0.3mm 정도 차이
가 있었
어요.
 
제가 처음 자른 허브는 0.15mm 정도의 여유 공간을 사용했습니다. 갈비뼈 8개는 잘 들어갔고,
마지막 4개는 억지로 넣으
려다 으깨졌다. 파일 슬롯이
이제 0.5mm 더 커져서 좀 엉성하게 들릴 수 있지만,
레이저 차에 시트 변형
을 더하면 실제로 잘 작동합니다.
3

단계3: 테스트 빗을 먼저 자르세요 - 선택 사항입니다

SVG를 매우 정밀하게 재생성하고 싶다면 이 단계를 진행하세요:
 
hub.py 6개의 슬롯이 있는 작은 빗도 출력하며, 3.7mm에서 4.2mm
까지
0.1mm 간격으로 출력합니다. 그냥 잘라내고, 각 슬롯에 합판 조각을 슬쩍 넣어보세요
.
엄지손가락을
살짝 눌러도 스크랩이 스스로 빠지지 않는 걸 원하세요.
그 번호는 허브에 들어가요. 여기서 2분
이면 망가진 부분을 구할 수 있어.
4

단계4: 스크립트 실행 - 선택 사항입니다

SVG를 매우 정밀하게 다시 생성하고 싶다면 이 단계를 하세요:

python3 disc.py

python3 rib.py

python3 hub.py 

python3 ring.py
 
각 사이트가 SVG를 작성하기 전에 요약본을 출력하므로,
무것도 열지 않고도 숫자를 제대로 확인할 수 있습니다. 원반은 약 15회 회전과
6.6m 리본
을 기록할 것입니다.
5

단계5: 다이얼 인 더 컷

결정하기 전에 자르기 위에 나선형 패턴의 작은 정사각형을 잘
라주세요.
10W 다이오드를 3.5mm 포플러 소재로 설정했는데, 100% 출력, 4 mm/s,
1 패스, 에어 어시스트 켜기였습니다.
6

단계6: 다 잘라.

리플 램프 - 레이저 컷 나무 물방울 instruction: step 6 - 다 잘라.
미리보기
리플 램프 - 레이저 컷 나무 물방울 instruction: step 6 - 다 잘라.
미리보기
리플 램프 - 레이저 컷 나무 물방울 instruction: step 6 - 다 잘라.
미리보기
리플 램프 - 레이저 컷 나무 물방울 instruction: step 6 - 다 잘라.
미리보기
- 디스크: 1
- 리브: 12
- 허브: 2 (접착 처리)
- 링: 3개의 스트립과 3개의 접합판
7

단계7: 접착하기 전에 갈비뼈가 무엇인지 이해하세요

리플 램프 - 레이저 컷 나무 물방울 instruction: step 7 - 접착하기 전에 갈비뼈가 무엇인지 이해하세요
미리보기
리플 램프 - 레이저 컷 나무 물방울 instruction: step 7 - 접착하기 전에 갈비뼈가 무엇인지 이해하세요
미리보기
갈비뼈는 평평하게 쌓아서 쌓는 것이 아닙니다. 그들은 배 선체 안의 프레임처럼 가장자리에 서 있으며
, 파도를 고정하는 것은 상단 가장자리의 형태
입니다
. 원반은 12개 모두에 한꺼번에 걸쳐 있습니다.

 

높이는 안쪽 끝에서 12.4 mm, 첫 번째 능선에서 22.6 mm,
골짜기에서 16.3 mm, 두 번째 능선에서 다시 21.2 mm까지 올라갑니다. 저
곡선은 나무에 새겨진 베셀 프로파일입니다.
8

단계8: 허브를 건설하세요

리플 램프 - 레이저 컷 나무 물방울 instruction: step 8 - 허브를 건설하세요
미리보기
리플 램프 - 레이저 컷 나무 물방울 instruction: step 8 - 허브를 건설하세요
미리보기
두 허브 디스크를 맞대고 슬롯을 맞춰 붙이세요. 신뢰할 수 있는 방법은 쌓
서 두 개의 자른 갈비뼈를 반대편 슬롯에 밀어 넣어 정렬을 강제로 맞추고,
접착제를 붙
여 그렇게 말리세요.
두 겹으로 늘리면 각 갈비뼈에 3.5mm 대신 7mm 정도 그립이 생깁니다.
그 다음 갈비뼈를 밀어 넣으세요.
9

단계9: 디스크를 풀어줘

리플 램프 - 레이저 컷 나무 물방울 instruction: step 9 - 디스크를 풀어줘
미리보기
한 반경을 따라 나선을 레지스터에 고정하는 0.6mm 작은 탭이 14개 있습니다
. 그것들이 없으면 침대에서 들어 올리는 순간 전체가 풀려버
립니다.
 
원반을 평평하게 눕히고 날카로운 칼로 하나씩 자르세요. 모두요.

 

지금은 6.6m 길이의 리본이 중앙과 테두리에만 고정되어 있습니다. 카드를 평평하게 올려서 들고 다니세요
. 가장자리를 잡지 마세요.
10

단계10: 형태와 접착

리플 램프 - 레이저 컷 나무 물방울 instruction: step 10 - 형태와 접착
미리보기
원판을 거미 위에 올려놓고 중앙에 맞춘 다음, 접착하면서 바깥쪽으로 붙이세요.

 

무게를 씌우세요, 중력만으로는 리본이 갈비뼈에 꼬이기엔 부족합니다.
11

단계11: 베이스 링과 조명

디스크가 곡선되면 300mm가 아니라 300mm가 아니라 293mm 너비가 됩니다. 이는 물질이 파동을 따라 직선으로 이동하지 않기 때문입니다. 반지는 그걸 기준으로 사이즈가 정해졌어요.

링을 디스크 가장자리 밑면에 붙이고 무게를 싣습니다.

그 다음 COB 스트립을 안쪽 면, 상단 가장자리에서 8에서 10mm 아래, 안쪽으로 향하게 붙입니다.

12

단계12: 파이썬 코드

"""
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")


 


 

댓글

리믹스

이것도 좋아하실 수 있습니다