
끌어서 회전, 휠·두 손가락으로 확대
이 모형에 대하여
자유의 여신상은 미국 독립 100주년을 기념해 프랑스가 미국에 보낸 선물로, 1886년 10월 뉴욕항 리버티섬에서 제막되었다. 조각가 프레데리크 오귀스트 바르톨디가 형태를 만들었고, 얇은 구리판을 지탱하는 내부 철골은 귀스타브 에펠이 설계했다. 화강암 받침대는 미국 건축가 리처드 모리스 헌트가 맡았으며, 1984년 유네스코 세계유산에 등재되었다.
조각상은 로마식 옷을 입은 여성이 오른손에 횃불을 높이 들고, 왼손에는 독립선언일 'JULY IV MDCCLXXVI'가 새겨진 서판을 안고 앞으로 걸어 나오는 모습이다. 머리에는 일곱 갈래 빛살이 뻗은 왕관을 썼고, 발치에는 끊어진 쇠사슬이 놓여 있다. 본래 붉은 구리색이었으나 수십 년에 걸쳐 산화하면서 지금의 청록색 녹청으로 덮였다.
받침대는 1812년 전쟁 무렵 지어진 별 모양 요새 포트 우드의 성벽 안에 세워졌다. 이 모델은 열한 꼭짓점의 별 성벽, 계단식 콘크리트 기초, 열주와 원반 장식 띠를 두른 화강암 받침대, 그리고 그 위의 조각상까지 지면에서 횃불 끝까지 약 93m를 실제 크기로 재현했다.
만든 방법
조각상의 드레스는 높이마다 다른 타원 단면을 쌓은 bmesh 격자이며, 각도와 높이에 따른 사인파를 반지름에 더해 세로 주름, 앞으로 나온 무릎, 뒤로 끌리는 옷자락을 표현했다. 왼쪽 어깨에서 오른쪽 겨드랑이로 걸친 망토는 몸통을 따라가는 띠 모양 격자에 두께를 주어 덧입혔다. 팔과 소매는 캣멀롬 곡선을 따라가는 관으로, 머리·머리카락·코는 타원체로, 왕관 빛살은 사각뿔로, 횃불은 회전체와 비틀린 물결 격자의 불꽃으로 만들었다. 받침대와 기초는 정사각 원뿔대를 층층이 쌓고 기둥·난간·원반 장식은 메시를 공유하는 복제로 배치했다. 얼굴의 이목구비, 손가락, 옷 주름의 세부는 크게 단순화했으며 받침대 내부와 섬의 지형은 생략했다.
build.py
이 파일 하나를 Blender 에서 실행하면 위의 모형이 만들어집니다. 413줄.
# 자유의 여신상 (Statue of Liberty, 뉴욕 리버티섬) — 양식화한 실물 크기 모델 (지면~횃불 끝 약 93m)# - 기단: 11각 별 모양 포트 우드 성벽(별 다각형 돌출) → 계단식 콘크리트 기초(사각 원뿔대 3단)# → 화강암 받침대(러스티케이션 띠, 모서리 기둥, 로지아 열주, 원반 장식 띠, 코니스, 난간).# - 조각상: 높이별 타원 단면을 쌓은 bmesh 격자에 사인파 주름을 더해 드레스(스톨라)를 만들고,# 왼쪽 어깨에서 오른쪽 겨드랑이로 비스듬히 걸친 망토(팔라)는 띠 모양 격자 + 두께(solidify)로 덧입혔다.# - 들어 올린 오른팔·소매·왼팔은 캣멀롬 곡선을 따라가는 관(tube), 머리·머리카락은 타원체,# 일곱 갈래 왕관 빛살은 사각뿔, 횃불은 회전체(lathe)와 물결 친 불꽃 격자로 만들었다.# - 왼손의 서판(키스톤 모양)에는 "JULY IV MDCCLXXVI" 를 양각했고, 발치에는 끊어진 쇠사슬을 두었다.import randomfrom atelier import *random.seed(1886)SHOT = {"azimuth": -102, "elevation": 8, "lens": 60, "light": "day", "background": "#27303c"}# ---------------------------------------------------------------- 재질M_PATINA = mat('Copper Patina', '#74a08f', rough=0.55, metal=0.15)M_PATINA_D = mat('Copper Patina Shadow', '#5f8a7b', rough=0.6, metal=0.12)M_GOLD = mat('Gold Leaf Flame', '#e2ab3c', rough=0.28, metal=1.0)M_GRANITE = mat('Stony Creek Granite', '#ab9d88', rough=0.75)M_GRANITE_D = mat('Granite Trim', '#968872', rough=0.7)M_CONCRETE = mat('Foundation Concrete', '#9c9686', rough=0.85)M_FORT = mat('Fort Wood Stone', '#837a6b', rough=0.85)M_PAVING = mat('Terrace Paving', '#76736b', rough=0.9)M_WINDOW = mat('Crown Window Glass', '#24343a', rough=0.15, metal=0.3)Z0 = 46.6 # 조각상 발밑(받침대 위) 높이TAU = 2 * math.pidef lerp(a, b, s): return a + (b - a) * sdef interp_table(table, t): if t <= table[0][0]: return table[0][1:] for (t0, *v0), (t1, *v1) in zip(table, table[1:]): if t <= t1: s = (t - t0) / (t1 - t0) s = s * s * (3 - 2 * s) return [lerp(a, b, s) for a, b in zip(v0, v1)] return table[-1][1:]def grid_mesh(name, rows, material, closed=True, cap_bottom=False, cap_top=False, smooth_it=True): """rows: 같은 길이의 3D 점 목록들. 행끼리 사각면으로 잇는다(closed 면 둘레도 닫음).""" bm = bmesh.new()나머지 365줄 펼치기접기
vs = [[bm.verts.new(p) for p in row] for row in rows] n = len(rows[0]) m = n if closed else n - 1 for i in range(len(rows) - 1): for j in range(m): k = (j + 1) % n bm.faces.new((vs[i][j], vs[i][k], vs[i + 1][k], vs[i + 1][j])) if cap_bottom: bm.faces.new(list(reversed(vs[0]))) if cap_top: bm.faces.new(vs[-1]) bmesh.ops.recalc_face_normals(bm, faces=bm.faces) me = bpy.data.meshes.new(name) bm.to_mesh(me) bm.free() ob = bpy.data.objects.new(name, me) link(ob) assign(ob, material) if smooth_it: smooth(ob) return obdef catmull(pts, n): """제어점을 지나는 캣멀롬 곡선을 n 점으로 샘플.""" P = [Vector(pts[0]) * 2 - Vector(pts[1])] + [Vector(p) for p in pts] + [Vector(pts[-1]) * 2 - Vector(pts[-2])] out = [] segs = len(pts) - 1 for i in range(n): u = i / (n - 1) * segs k = min(int(u), segs - 1) s = u - k p0, p1, p2, p3 = P[k], P[k + 1], P[k + 2], P[k + 3] out.append(0.5 * ((2 * p1) + (-p0 + p2) * s + (2 * p0 - 5 * p1 + 4 * p2 - p3) * s * s + (-p0 + 3 * p1 - 3 * p2 + p3) * s ** 3)) return outdef tube(name, ctrl, radius_fn, material, n=40, seg=24, cap=True, fold_fn=None): """곡선 관. radius_fn(s)→반지름, fold_fn(s, ang)→반지름 가감.""" path = catmull(ctrl, n) rows = [] ref = Vector((0, 1, 0)) prev_nrm = None for i, p in enumerate(path): tng = (path[min(i + 1, n - 1)] - path[max(i - 1, 0)]).normalized() if prev_nrm is None: nrm = tng.cross(ref).normalized() else: nrm = (prev_nrm - tng * prev_nrm.dot(tng)).normalized() prev_nrm = nrm bin_ = tng.cross(nrm) s = i / (n - 1) row = [] for j in range(seg): a = TAU * j / seg r = radius_fn(s) + (fold_fn(s, a) if fold_fn else 0.0) row.append(p + (nrm * math.cos(a) + bin_ * math.sin(a)) * r) rows.append(row) return grid_mesh(name, rows, material, cap_bottom=cap, cap_top=cap)def sq_frustum(name, side0, side1, z0, z1, material): """정사각 원뿔대(아래 한 변 side0, 위 side1).""" ob = cylinder(name, r=side0 / math.sqrt(2), r2=side1 / math.sqrt(2), h=z1 - z0, loc=(0, 0, z0), seg=4, material=material) ob.rotation_euler.z = math.pi / 4 return obdef aim(ob, direction): ob.rotation_mode = 'QUATERNION' ob.rotation_quaternion = Vector((0, 0, 1)).rotation_difference(Vector(direction).normalized()) return ob# ================================================================ 1. 포트 우드 별 성벽N_STAR = 11R_OUT, R_IN = 37.0, 27.5def star_pts(ro, ri, n=N_STAR, rot=math.pi / 2): pts = [] for i in range(2 * n): r = ro if i % 2 == 0 else ri a = rot + math.pi * i / n pts.append((r * math.cos(a), r * math.sin(a))) return ptsfort = prism('FortWood_StarWall', star_pts(R_OUT, R_IN), 6.6, material=M_FORT)fort_base = prism('FortWood_Footing', star_pts(R_OUT + 0.6, R_IN + 0.45), 0.9, material=M_GRANITE_D)coping = prism('FortWood_Coping', star_pts(R_OUT + 0.35, R_IN + 0.25), 0.55, loc=(0, 0, 6.35), material=M_GRANITE_D)terrace = prism('FortWood_Terrace', star_pts(R_OUT - 0.4, R_IN - 0.3), 0.05, loc=(0, 0, 6.88), material=M_PAVING)# ================================================================ 2. 계단식 콘크리트 기초sq_frustum('Foundation_Tier1', 28.0, 27.0, 6.8, 11.2, M_CONCRETE)sq_frustum('Foundation_Tier2', 25.0, 24.0, 11.2, 15.6, M_CONCRETE)sq_frustum('Foundation_Tier3', 22.0, 21.0, 15.6, 20.0, M_CONCRETE)for i, (s, z) in enumerate(((27.4, 11.2), (24.4, 15.6))): sq_frustum(f'Foundation_StepLedge_{i}', s + 0.4, s + 0.4, z - 0.35, z, M_GRANITE_D)# ================================================================ 3. 화강암 받침대sq_frustum('Pedestal_BaseCourse', 20.4, 20.0, 20.0, 21.2, M_GRANITE_D)sq_frustum('Pedestal_Plinth', 19.4, 18.4, 21.2, 23.2, M_GRANITE)sq_frustum('Pedestal_LowerShaft', 17.4, 17.0, 23.2, 30.2, M_GRANITE)# 러스티케이션 띠(돌 쌓기 줄눈 느낌)for i in range(5): z = 24.2 + i * 1.3 s = lerp(17.4, 17.0, (z - 23.2) / 7.0) + 0.24 sq_frustum(f'Pedestal_Rustication_{i}', s, s, z, z + 0.7, M_GRANITE)sq_frustum('Pedestal_MidCornice_Lower', 17.8, 18.6, 30.2, 30.8, M_GRANITE_D)sq_frustum('Pedestal_MidCornice', 18.6, 18.6, 30.8, 31.3, M_GRANITE_D)# 로지아 층: 움푹 들어간 코어 + 모서리 기둥 + 면마다 4개의 기둥sq_frustum('Pedestal_LoggiaCore', 14.6, 14.6, 31.3, 40.6, M_GRANITE)PIER = 4.2HALF = 8.2for cx in (-1, 1): for cy in (-1, 1): box(f'Pedestal_CornerPier_{cx}{cy}', (PIER, PIER, 9.3), loc=(cx * (HALF - PIER / 2), cy * (HALF - PIER / 2), 31.3), base=True, material=M_GRANITE) for k in range(4): box(f'Pedestal_PierRustic_{cx}{cy}_{k}', (PIER + 0.2, PIER + 0.2, 0.55), loc=(cx * (HALF - PIER / 2), cy * (HALF - PIER / 2), 32.2 + k * 2.0), base=True, material=M_GRANITE)col_proto = lathe('Pedestal_LoggiaColumn', [(0, 31.3), (0.62, 31.3), (0.62, 31.65), (0.5, 31.8), (0.46, 38.9), (0.6, 39.2), (0.6, 39.5), (0, 39.5)], seg=20, material=M_GRANITE_D)cap_proto = box('Pedestal_ColumnAbacus', (1.4, 1.4, 0.35), loc=(0, 0, 39.5), base=True, material=M_GRANITE_D)first = Truefor face in range(4): a = face * math.pi / 2 ca, sa = math.cos(a), math.sin(a) for x in (-2.9, -0.97, 0.97, 2.9): px, py = x * ca - (-7.75) * sa, x * sa + (-7.75) * ca if first: col_proto.location = (px, py, 0) cap_proto.location = (px, py, 39.5) first = False else: duplicate(col_proto, loc=(px, py, 0)) duplicate(cap_proto, loc=(px, py, 39.5)) # 로지아 발코니 바닥과 난간 bx, by = -(-8.1) * sa, (-8.1) * ca bal = box(f'Pedestal_Balcony_{face}', (7.8, 1.6, 0.35), loc=(bx, by, 31.3), rot=(0, 0, a), base=True, material=M_GRANITE_D) for k in range(9): x = -3.6 + k * 0.9 px, py = x * ca - (-8.6) * sa, x * sa + (-8.6) * ca box(f'Pedestal_Baluster_{face}_{k}', (0.28, 0.28, 1.0), loc=(px, py, 31.65), rot=(0, 0, a), base=True, material=M_GRANITE_D) rx, ry = -(-8.6) * sa, (-8.6) * ca box(f'Pedestal_BalconyRail_{face}', (7.8, 0.5, 0.25), loc=(rx, ry, 32.65), rot=(0, 0, a), base=True, material=M_GRANITE_D)sq_frustum('Pedestal_Architrave', 16.6, 16.6, 39.85, 40.9, M_GRANITE_D)sq_frustum('Pedestal_Frieze', 16.0, 16.0, 40.9, 43.2, M_GRANITE)# 원반(방패) 장식 띠: 면마다 10개, 모두 40개disc = cylinder('Pedestal_FriezeDisc', r=0.5, h=0.18, seg=24, base=False, material=M_GRANITE_D)first = Truefor face in range(4): a = face * math.pi / 2 for k in range(10): x = -6.3 + k * 1.4 px, py = x * math.cos(a) + 8.05 * math.sin(a), x * math.sin(a) - 8.05 * math.cos(a) ob = disc if first else duplicate(disc) first = False ob.location = (px, py, 42.05) ob.rotation_euler = (math.pi / 2, 0, a)sq_frustum('Pedestal_CorniceBed', 16.2, 17.2, 43.2, 43.7, M_GRANITE_D)sq_frustum('Pedestal_Cornice', 17.6, 17.6, 43.7, 44.4, M_GRANITE_D)sq_frustum('Pedestal_Attic', 16.2, 16.2, 44.4, 45.95, M_GRANITE)# 꼭대기 전망대 난간for k, (sx, sy, x, y) in enumerate(((16.2, 0.6, 0, -7.8), (16.2, 0.6, 0, 7.8), (0.6, 15.0, -7.8, 0), (0.6, 15.0, 7.8, 0))): box(f'Pedestal_Parapet_{k}', (sx, sy, 1.15), loc=(x, y, 45.95), base=True, material=M_GRANITE_D)# ================================================================ 4. 조각상 — 드레스 몸통plinth = cylinder('Statue_Plinth', r=6.9, r2=6.6, h=0.75, seg=8, loc=(0, 0, Z0 - 0.75 + 0.05), material=M_PATINA_D)plinth.rotation_euler.z = math.pi / 8# (t, 좌우 반폭 a, 앞뒤 반폭 b)BODY = [(-0.1, 5.75, 5.0), (0.8, 5.5, 4.9), (3, 5.15, 4.6), (7, 4.9, 4.35), (12, 4.75, 4.2), (16, 4.8, 4.3), (19, 4.7, 4.15), (22, 4.5, 3.9), (24, 4.4, 3.75), (25.4, 4.2, 3.45), (26.4, 3.7, 3.05), (27.3, 2.7, 2.4), (28.0, 1.7, 1.7), (28.8, 1.32, 1.42), (30.2, 1.28, 1.38)]PH = [random.uniform(0, TAU) for _ in range(6)]def gauss(x, s): return math.exp(-(x / s) ** 2)def ang_d(a, b): return (a - b + math.pi) % TAU - math.pidef body_r(th, t): a, b = interp_table(BODY, t) c, s = math.cos(th), math.sin(th) r = a * b / math.sqrt((b * c) ** 2 + (a * s) ** 2) low = max(0.0, 1.0 - t / 19.0) # 늘어진 세로 주름(아래로 갈수록 깊다) r += (0.4 * math.sin(9 * th + PH[0] + 0.015 * t) + 0.18 * math.sin(15 * th + PH[1]) + 0.07 * math.sin(26 * th + PH[2])) * (0.25 + 0.75 * low) * (1 if t < 26 else max(0, (28 - t) / 2)) # 가슴의 비스듬한 주름 if 17 < t < 26.5: r += 0.09 * math.sin(11 * th + 0.55 * t + PH[3]) * gauss(t - 22, 3) # 앞으로 내민 오른쪽 무릎, 가슴, 뒤로 끌리는 옷자락 r += 0.55 * gauss(ang_d(th, math.radians(238)), 0.42) * gauss(t - 10, 3.5) r += 0.35 * gauss(ang_d(th, math.radians(-90)), 0.7) * gauss(t - 23, 1.6) if s > 0: r += 0.7 * s * max(0.0, 1 - t / 7) ** 1.5 return rTH_N = 120ts = [-0.1 + i * 0.25 for i in range(int(30.3 / 0.25) + 1)]rows = []for t in ts: rows.append([(body_r(TAU * j / TH_N, t) * math.cos(TAU * j / TH_N), body_r(TAU * j / TH_N, t) * math.sin(TAU * j / TH_N), Z0 + t) for j in range(TH_N)])grid_mesh('Statue_Robe_Stola', rows, M_PATINA, cap_bottom=True, cap_top=True)# ---------------------------------------------------------------- 망토(팔라): 왼쪽 어깨→오른쪽 겨드랑이로 걸친 띠def mantle_hem(th): return 8.5 + 5.0 * math.cos(th - math.radians(235)) + 0.9 * math.sin(3 * th) + 0.6 * math.sin(7 * th + 1.0)def mantle_top(th): # 왼쪽 어깨(+X) 쪽에서 높고, 오른쪽 겨드랑이(-X) 쪽에서 낮다 return 21.8 + 4.6 * math.cos(th) + 2.6 * math.sin(th) + 0.6 * max(0.0, math.cos(th)) ** 0.5def mantle_off(th, t, s): return 0.5 + 0.32 * math.sin(7 * th + 0.35 * t + PH[4]) + 0.1 * math.sin(13 * th - 0.2 * t + PH[5]) + 0.15 * (1 - s)M_ROWS = 34mrows = []top_edge, hem_edge = [], []for i in range(M_ROWS + 1): s = i / M_ROWS row = [] for j in range(TH_N): th = TAU * j / TH_N t = lerp(mantle_hem(th), mantle_top(th), s) r = body_r(th, t) + mantle_off(th, t, s) row.append((r * math.cos(th), r * math.sin(th), Z0 + t)) mrows.append(row)mantle = grid_mesh('Statue_Mantle_Palla', mrows, M_PATINA, cap_bottom=False, cap_top=False)solidify(mantle, 0.3)mantle.modifiers[-1].offset = 0.0curve_tube('Statue_Mantle_RolledEdge', [mrows[-1][j] for j in range(TH_N)], radius=0.3, material=M_PATINA, closed=True, resolution=2)curve_tube('Statue_Mantle_Hem', [mrows[0][j] for j in range(TH_N)], radius=0.17, material=M_PATINA, closed=True, resolution=2)# 왼팔 아래로 늘어진 망토 자락(왼쪽 옆 실루엣)tube('Statue_Mantle_Cascade', [(5.2, 0.2, Z0 + 20.5), (5.9, 0.0, Z0 + 15.5), (6.0, -0.2, Z0 + 10.5), (5.7, -0.3, Z0 + 5.0)], lambda s: lerp(1.25, 0.75, s), M_PATINA, n=30, seg=20, fold_fn=lambda s, a: 0.18 * math.sin(5 * a + 3 * s))# ---------------------------------------------------------------- 발과 끊어진 쇠사슬sphere('Statue_LeftFoot', r=1.0, loc=(1.6, -5.1, Z0 + 0.25), scale=(0.75, 1.35, 0.5), material=M_PATINA)sphere('Statue_RightToes', r=1.0, loc=(-1.9, -5.0, Z0 + 0.15), scale=(0.6, 0.9, 0.35), material=M_PATINA)link_proto = torus('Statue_BrokenChain_Link', R=0.42, r=0.12, seg=20, rseg=8, material=M_PATINA_D)for k in range(5): x = -0.6 - k * 0.72 ob = link_proto if k == 0 else duplicate(link_proto) if k % 2 == 0: ob.location = (x, -5.95, Z0 + 0.12) ob.rotation_euler = (0, 0, 0) else: ob.location = (x, -5.95, Z0 + 0.4) ob.rotation_euler = (math.pi / 2, 0, 0)shackle = torus('Statue_Shackle', R=0.55, r=0.16, seg=24, rseg=8, loc=(-3.3, -5.25, Z0 + 0.5), rot=(math.pi / 2, 0, 0.5), material=M_PATINA_D)# ================================================================ 5. 머리·왕관HC = Vector((0, -0.2, Z0 + 31.35))sphere('Statue_Face', r=1.0, loc=HC, scale=(1.45, 1.75, 2.45), seg=40, rings=24, material=M_PATINA)sphere('Statue_Hair', r=1.0, loc=HC + Vector((0, 0.75, 0.45)), scale=(1.75, 1.7, 2.2), seg=40, rings=24, material=M_PATINA)sphere('Statue_HairBun', r=0.95, loc=HC + Vector((0, 1.85, -1.1)), scale=(1.1, 0.9, 0.9), material=M_PATINA)for side in (-1, 1): sphere(f'Statue_HairCurl_{side}', r=0.7, loc=HC + Vector((side * 1.45, 0.35, -0.9)), scale=(0.7, 1.0, 1.3), material=M_PATINA)sphere('Statue_Nose', r=1.0, loc=HC + Vector((0, -1.72, -0.15)), scale=(0.22, 0.32, 0.55), material=M_PATINA)sphere('Statue_Chin', r=1.0, loc=HC + Vector((0, -1.25, -1.75)), scale=(0.55, 0.45, 0.45), material=M_PATINA)for side in (-1, 1): sphere(f'Statue_Brow_{side}', r=1.0, loc=HC + Vector((side * 0.6, -1.62, 0.42)), scale=(0.5, 0.18, 0.13), material=M_PATINA) sphere(f'Statue_Cheek_{side}', r=1.0, loc=HC + Vector((side * 0.75, -1.38, -0.5)), scale=(0.45, 0.35, 0.5), material=M_PATINA)# 왕관 띠(타원 회전체) + 25개의 창 + 7개의 빛살CR_Z = HC.z + 1.05band = lathe('Statue_Crown_Diadem', [(1.62, -0.45), (1.92, -0.45), (2.0, -0.25), (2.0, 0.42), (1.92, 0.6), (1.62, 0.6), (1.62, -0.45)], seg=64, loc=(HC.x, HC.y + 0.2, CR_Z), material=M_PATINA)band.scale = (1.0, 1.08, 1.0)win_proto = box('Statue_Crown_Window', (0.2, 0.12, 0.42), material=M_WINDOW)for k in range(25): a = math.radians(-90 + (k - 12) * 5.6) ob = win_proto if k == 0 else duplicate(win_proto) ob.location = (HC.x + 2.0 * math.cos(a), HC.y + 0.2 + 2.0 * 1.08 * math.sin(a), CR_Z + 0.08) ob.rotation_euler = (0, 0, a + math.pi / 2)ray_proto = cone('Statue_Crown_Ray', r=0.5, h=3.5, seg=4, material=M_PATINA)for k in range(7): a = math.radians(-90 + (k - 3) * 31) el = math.radians(24 + 4 * abs(k - 3)) d = Vector((math.cos(a) * math.cos(el), math.sin(a) * math.cos(el), math.sin(el))) ob = ray_proto if k == 0 else duplicate(ray_proto) ob.location = Vector((HC.x + 1.55 * math.cos(a), HC.y + 0.2 + 1.65 * math.sin(a), CR_Z + 0.35)) aim(ob, d)# ================================================================ 6. 들어 올린 오른팔과 횃불R_SH = (-3.0, 0.3, Z0 + 25.0)R_ARM = [R_SH, (-4.3, 0.45, Z0 + 28.6), (-5.3, 0.6, Z0 + 32.6), (-6.05, 0.7, Z0 + 36.0), (-6.45, 0.7, Z0 + 37.7)]tube('Statue_RightArm', R_ARM, lambda s: lerp(1.15, 0.72, s), M_PATINA, n=40, seg=24)# 소매: 어깨 쪽으로 흘러내려 모인 넓은 천sleeve = tube('Statue_RightSleeve', R_ARM[:3] + [(-5.6, 0.62, Z0 + 33.9)], lambda s: lerp(2.0, 1.45, s) + 0.2 * (1 - s), M_PATINA, n=30, seg=28, cap=False, fold_fn=lambda s, a: 0.17 * math.sin(7 * a + 2.0 * s) * (1 - 0.4 * s))solidify(sleeve, 0.22)HAND = Vector((-6.55, 0.7, Z0 + 38.5))sphere('Statue_RightHand', r=1.0, loc=HAND, scale=(0.95, 0.9, 1.15), material=M_PATINA)for k in range(4): torus(f'Statue_RightFinger_{k}', R=0.62, r=0.2, seg=20, rseg=8, loc=HAND + Vector((0.0, -0.05, -0.55 + k * 0.36)), material=M_PATINA)TX, TY = HAND.x, HAND.ylathe('Statue_Torch_Handle', [(0, Z0 + 36.6), (0.42, Z0 + 36.6), (0.5, Z0 + 37.0), (0.5, Z0 + 39.8), (0.62, Z0 + 40.1), (0.78, Z0 + 40.3), (0.62, Z0 + 40.6), (0.62, Z0 + 40.9), (1.15, Z0 + 41.35), (1.75, Z0 + 41.95), (2.05, Z0 + 42.3), (2.05, Z0 + 42.5), (2.62, Z0 + 42.5), (2.62, Z0 + 42.72), (0, Z0 + 42.72)], loc=(TX, TY, 0), seg=40, material=M_PATINA)post = cylinder('Statue_Torch_RailPost', r=0.06, h=0.95, seg=6, loc=(TX + 2.52, TY, Z0 + 42.72), material=M_PATINA_D)for k in range(1, 20): a = TAU * k / 20 duplicate(post, loc=(TX + 2.52 * math.cos(a), TY + 2.52 * math.sin(a), Z0 + 42.72))torus('Statue_Torch_Rail', R=2.52, r=0.08, seg=48, rseg=6, loc=(TX, TY, Z0 + 43.67), material=M_PATINA_D)# 불꽃: 금박을 입힌 물결 친 혀 모양frows = []FH = 4.4for i in range(29): h = i / 28 row = [] for j in range(48): th = TAU * j / 48 r = 1.75 * (1 - h) ** 0.72 * (1 + 0.42 * math.sin(math.pi * min(h * 1.7, 1.0))) r *= 1 + 0.2 * h * math.sin(5 * th + 5.5 * h) + 0.06 * math.sin(9 * th - 3 * h) row.append((TX + r * math.cos(th + 0.9 * h), TY + r * math.sin(th + 0.9 * h), Z0 + 42.55 + h * FH)) frows.append(row)grid_mesh('Statue_Torch_Flame', frows, M_GOLD, cap_bottom=True)# ================================================================ 7. 왼팔과 서판L_ARM = [(3.2, 0.3, Z0 + 25.0), (4.75, 0.7, Z0 + 22.2), (5.0, 0.4, Z0 + 19.4), (4.7, -1.6, Z0 + 18.6), (4.3, -3.1, Z0 + 19.1)]tube('Statue_LeftArm', L_ARM, lambda s: lerp(1.25, 0.75, s), M_PATINA, n=40, seg=24, fold_fn=lambda s, a: 0.12 * math.sin(6 * a + 4 * s) * (1 - s))sphere('Statue_LeftHand', r=1.0, loc=(4.15, -3.35, Z0 + 19.45), scale=(0.8, 0.95, 0.7), material=M_PATINA)TAB_LOC = Vector((5.0, -2.75, Z0 + 22.4))TAB_ROT = Euler((-0.24, 0.12, math.radians(28)))tablet = extrude_profile('Statue_Tablet', [(-1.75, -3.6), (1.75, -3.6), (2.07, 3.6), (-2.07, 3.6)], 0.61, material=M_PATINA)tablet.location = TAB_LOCtablet.rotation_euler = TAB_ROTTM = Matrix.Translation(TAB_LOC) @ TAB_ROT.to_matrix().to_4x4()# 서판 테두리(살짝 도드라진 액자)frame_pts = [(-1.6, -3.42), (1.6, -3.42), (1.88, 3.42), (-1.88, 3.42), (-1.6, -3.42)]curve_tube('Statue_Tablet_Border', [tuple(TM @ Vector((x, -0.31, z))) for x, z in frame_pts], radius=0.07, material=M_PATINA_D)txt = text3d('Statue_Tablet_Inscription', 'JULY IV MDCCLXXVI', size=0.62, depth=0.03, material=M_PATINA_D)txt.data.align_y = 'CENTER'dg = bpy.context.evaluated_depsgraph_get()tme = bpy.data.meshes.new_from_object(txt.evaluated_get(dg))bpy.data.objects.remove(txt, do_unlink=True)inscr = bpy.data.objects.new('Statue_Tablet_Inscription', tme)link(inscr)assign(inscr, M_PATINA_D)# 글자: 서판 긴 변을 따라 아래에서 위로, 앞면(-Y) 위에 양각inscr.matrix_world = TM @ Matrix.Translation((0, -0.31, 0)) @ Euler((math.pi / 2, 0, 0)).to_matrix().to_4x4() @ Euler((0, 0, math.pi / 2)).to_matrix().to_4x4()






