
끌어서 회전, 휠·두 손가락으로 확대
이 모형에 대하여
숭례문은 조선의 수도 한양을 둘러싼 도성의 남쪽 정문으로, 흔히 남대문이라 부른다. 1398년(태조 7년)에 처음 세워졌고 이후 여러 차례 고쳐 지었다. 1962년 국보로 지정되었으며, 지정번호가 쓰이던 때에는 국보 제1호였다.
2008년 2월 방화로 문루 2층 대부분과 1층 일부가 불탔다. 이후 전통 기법과 재료를 써서 복구 공사를 진행해 2013년 5월 다시 공개되었다.
건물은 무지개 모양 홍예문을 낸 화강암 석축과 그 위의 목조 문루로 이루어진다. 문루는 정면 5칸·측면 2칸의 2층 건물로, 네 면 모두 경사진 우진각 지붕을 얹었고 기둥 위에는 공포를 촘촘히 짜 올린 다포 양식을 썼다. 이 모델은 석축의 쌓기 켜와 홍예석, 석축 위 여장, 단청을 칠한 공포와 서까래, 백회를 바른 지붕마루, 상층 중앙의 세로 현판을 표현했다.
만든 방법
석축은 켜마다 조금씩 들여쌓은 화강암 블록을 고정 시드 난수로 길이를 나눠 만들고, EXACT 불리언으로 홍예 통로를 뚫은 뒤 부채꼴 홍예석과 측벽석을 따로 쌓았다. 지붕은 정사각 둘레를 직사각 고리로 옮기는 매개화로 우진각 면을 만들고 귀 쪽으로 갈수록 처마가 들리고 바깥으로 빠지도록 곡선을 주었으며, 수키와 줄·추녀마루·서까래는 지붕면 위를 따라 단면을 쓸어(sweep) 만들었다. 다포 공포 한 세트를 메시 공유 복제로 둘레에 배열했고 기둥도 같은 방식으로 복제했다. 현판 글씨는 실제 글자 대신 금색 획 조각으로 암시했고, 내부 구조·계단·주변 성곽은 생략했다. 치수는 실측 도면이 아니라 사진 비례에 맞춘 근사값이다.
build.py
이 파일 하나를 Blender 에서 실행하면 위의 모형이 만들어집니다. 657줄.
# 숭례문(서울 남대문) — 석축 홍예문 위에 올린 정면 5칸·측면 2칸, 2층 우진각 지붕 문루.# 석축은 켜마다 조금씩 들여 쌓은 화강암 블록을 난수(고정 시드)로 나눠 줄눈을 만들고, 불리언으로 홍예 통로를 뚫은 뒤# 홍예석·측벽석을 따로 쌓았다. 지붕은 '정사각 둘레 → 직사각 고리' 매개화로 우진각 면을 만들고# 처마 곡선(앙곡·안허리)을 귀 쪽으로 갈수록 들리게 했으며, 그 위에 수키와 줄을 한 줄씩 쓸어(sweep) 얹었다.# 공포(다포)는 한 세트 메시를 연결 복제로 둘레에 배열했고, 기둥·창방·평방·단청 띠·난간·현판은 상자 조합으로 만들었다.# 실측 도면이 아니라 사진 비례에 맞춘 근사 모델이다(단위 m).from atelier import *import randomrandom.seed(1398)SHOT = {"azimuth": -56, "elevation": 8, "lens": 50, "light": "day", "background": "#4b5d72"}# ---------------------------------------------------------------- 재질GRANITE = mat('화강암', '#a19b8f', 0.85)GRANITE2 = mat('화강암_짙은돌', '#8b867c', 0.9)BRICK = mat('여장_회색전돌', '#7d7a74', 0.9)TILE = mat('기와_회흑색', '#3b3e44', 0.72)MORTAR = mat('양성바름_백회', '#e2ddd1', 0.85)COL = mat('석간주_기둥', '#7a2e24', 0.6)GREEN = mat('단청_뇌록', '#3f8a72', 0.6)BLUE = mat('단청_군청', '#2d5b8e', 0.55)RED = mat('단청_주홍', '#b5402d', 0.55)OCHRE = mat('단청_황토', '#d5aa52', 0.55)WOOD = mat('마루_목재', '#76583c', 0.7)PLAQUE = mat('현판_바탕', '#22252d', 0.5)GOLD = mat('현판_금박', '#c9a347', 0.35, 0.6)class MB: """여러 상자·스윕을 한 메시로 모으는 작은 빌더(재질 슬롯 자동).""" def __init__(self): self.v, self.f, self.m, self.slots = [], [], [], [] def _mi(self, m): if m not in self.slots: self.slots.append(m) return self.slots.index(m) def face(self, idx, m): self.f.append(list(idx)) self.m.append(self._mi(m)) def obox(self, c, ax, ay, az, m): b = len(self.v) for k in range(8): sx = 1 if k & 1 else -1나머지 609줄 펼치기접기
sy = 1 if k & 2 else -1 sz = 1 if k & 4 else -1 self.v.append(c + ax * sx + ay * sy + az * sz) for q in ((0, 2, 3, 1), (4, 5, 7, 6), (0, 1, 5, 4), (2, 6, 7, 3), (0, 4, 6, 2), (1, 3, 7, 5)): self.face([b + i for i in q], m) def box(self, mn, mx, m): c = Vector(((mn[0] + mx[0]) / 2, (mn[1] + mx[1]) / 2, (mn[2] + mx[2]) / 2)) self.obox(c, Vector(((mx[0] - mn[0]) / 2, 0, 0)), Vector((0, (mx[1] - mn[1]) / 2, 0)), Vector((0, 0, (mx[2] - mn[2]) / 2)), m) def prism_y(self, pts, y0, y1, m): """XZ 다각형을 y0~y1 로 돌출.""" b = len(self.v) n = len(pts) for x, z in pts: self.v.append(Vector((x, y0, z))) for x, z in pts: self.v.append(Vector((x, y1, z))) self.face([b + i for i in range(n)], m) self.face([b + n + i for i in reversed(range(n))], m) for i in range(n): j = (i + 1) % n self.face([b + i, b + j, b + n + j, b + n + i], m) def sweep(self, pts, prof, m, mcap0=None, mcap1=None): """점 목록을 따라 닫힌 단면 prof=[(가로, 법선)] 을 쓸어 만든다.""" rings = [] n = len(pts) for i, p in enumerate(pts): t = (pts[min(i + 1, n - 1)] - pts[max(i - 1, 0)]).normalized() lat = t.cross(Vector((0, 0, 1))) if lat.length < 1e-6: lat = Vector((1, 0, 0)) lat.normalize() nor = lat.cross(t).normalized() if nor.z < 0: nor = -nor rings.append(len(self.v)) for a, b in prof: self.v.append(p + lat * a + nor * b) k = len(prof) for i in range(n - 1): for j in range(k): j2 = (j + 1) % k self.face([rings[i] + j, rings[i] + j2, rings[i + 1] + j2, rings[i + 1] + j], m) self.face([rings[0] + j for j in reversed(range(k))], mcap0 or m) self.face([rings[-1] + j for j in range(k)], mcap1 or m) def build(self, name, recalc=True): me = bpy.data.meshes.new(name) me.from_pydata([tuple(v) for v in self.v], [], self.f) for s in self.slots: me.materials.append(s) me.polygons.foreach_set('material_index', self.m) me.update() if recalc: bm = bmesh.new() bm.from_mesh(me) bmesh.ops.recalc_face_normals(bm, faces=bm.faces) bm.to_mesh(me) bm.free() ob = bpy.data.objects.new(name, me) link(ob) return obdef ring_beams(mb, Wx, Wy, off, hw, z0, z1, m): """기둥선(Wx,Wy)에서 off 만큼 바깥에 도는 사각 띠(앞뒤 통짜, 옆면은 맞닿게).""" ox, oy = Wx + off, Wy + off for s in (-1, 1): mb.box((-(ox + hw), s * oy - hw, z0), (ox + hw, s * oy + hw, z1), m) mb.box((s * ox - hw, -(oy - hw), z0), (s * ox + hw, oy - hw, z1), m)# ---------------------------------------------------------------- 석축(石築)과 홍예문W, D, H = 30.0, 15.0, 8.0 # 석축 밑변 너비·깊이·높이NC, BATTER = 11, 0.04 # 켜 수, 켜마다 들여쌓기Z_PL, Z_MI = 0.35, H - 0.32 # 지대석 윗면, 미석 아랫면CH = (Z_MI - Z_PL) / NCR_IN, R_OUT, ZS = 2.75, 3.45, 3.95 # 홍예 안 반지름, 홍예석 바깥 반지름, 홍예 기점 높이G = 0.022 # 줄눈def half_at(k): return W / 2 - k * BATTER, D / 2 - k * BATTERdef build_platform(): blocks = MB() blocks.box((-W / 2 - 0.15, -D / 2 - 0.15, 0), (W / 2 + 0.15, D / 2 + 0.15, Z_PL), GRANITE2) for k in range(NC): hx, hy = half_at(k) z0, z1 = Z_PL + k * CH, Z_PL + (k + 1) * CH for s in (-1, 1): # 앞·뒷면 블록 x = -hx first = True while x < hx - 0.05: ln = random.uniform(0.9, 2.1) * (0.6 if first and k % 2 else 1.0) first = False x1 = min(hx, x + ln) if hx - x1 < 0.6: x1 = hx pr = random.uniform(0, 0.025) m = GRANITE if random.random() < 0.7 else GRANITE2 if s < 0: blocks.box((x + G, -hy - pr, z0 + G), (x1 - G, -hy + 1.0, z1 - G), m) else: blocks.box((x + G, hy - 1.0, z0 + G), (x1 - G, hy + pr, z1 - G), m) x = x1 # 옆면 블록 y = -hy + 1.0 while y < hy - 1.05: y1 = min(hy - 1.0, y + random.uniform(0.9, 2.0)) if hy - 1.0 - y1 < 0.5: y1 = hy - 1.0 pr = random.uniform(0, 0.025) m = GRANITE if random.random() < 0.7 else GRANITE2 if s < 0: blocks.box((-hx - pr, y + G, z0 + G), (-hx + 1.0, y1 - G, z1 - G), m) else: blocks.box((hx - 1.0, y + G, z0 + G), (hx + pr, y1 - G, z1 - G), m) y = y1 ob_blocks = blocks.build('Seokchuk_Masonry') core = MB() core.box((-(W / 2 - 0.7), -(D / 2 - 0.7), 0.3), (W / 2 - 0.7, D / 2 - 0.7, H - 0.3), GRANITE2) ob_core = core.build('Seokchuk_Core') cut = MB() prof = [(-R_OUT, -1.0), (R_OUT, -1.0)] for i in range(25): a = math.pi * i / 24 prof.append((R_OUT * math.cos(a), ZS + R_OUT * math.sin(a))) cut.prism_y(prof, -D, D, GRANITE) ob_cut = cut.build('Hongye_Cutter') for o in (ob_blocks, ob_core): boolean(o, ob_cut) apply_all(o) bpy.data.objects.remove(ob_cut, do_unlink=True) # 미석(眉石)과 윗면 박석 hx, hy = half_at(NC) top = MB() top.box((-hx - 0.14, -hy - 0.14, Z_MI), (hx + 0.14, hy + 0.14, H), GRANITE) # 통로 바닥 박석 top.box((-R_OUT - 0.05, -D / 2 - 0.15, 0), (R_OUT + 0.05, D / 2 + 0.15, 0.06), GRANITE2) # 홍예 측벽석(기점 아래) nj = 5 for side in (-1, 1): for k in range(nj): z0, z1 = k * ZS / nj, (k + 1) * ZS / nj kk = (z0 + z1) / 2 / CH hyk = D / 2 - kk * BATTER y = -hyk - 0.02 while y < hyk: y1 = min(hyk + 0.02, y + random.uniform(1.4, 2.2)) if hyk - y1 < 0.6: y1 = hyk + 0.02 x_in, x_out = R_IN, R_OUT + 0.05 xa, xb = (x_in, x_out) if side > 0 else (-x_out, -x_in) top.box((xa, y + G, max(z0, 0.06) + G), (xb, y1 - G, z1 - G), GRANITE if random.random() < 0.6 else GRANITE2) y = y1 # 홍예석(무지개돌) — 부채꼴 돌을 통로 깊이로 나눠 쌓음 NV = 13 for i in range(NV): a0 = math.pi * i / NV + 0.006 a1 = math.pi * (i + 1) / NV - 0.006 pts = [] for j in range(3): a = a0 + (a1 - a0) * j / 2 pts.append((R_IN * math.cos(a), ZS + R_IN * math.sin(a))) for j in range(3): a = a1 - (a1 - a0) * j / 2 pts.append(((R_OUT + 0.05) * math.cos(a), ZS + (R_OUT + 0.05) * math.sin(a))) zm = ZS + (R_IN + R_OUT) / 2 * math.sin((a0 + a1) / 2) hyk = D / 2 - (zm - Z_PL) / CH * BATTER + 0.03 segs = 7 for s in range(segs): y0 = -hyk + 2 * hyk * s / segs + (0 if s == 0 else G) y1 = -hyk + 2 * hyk * (s + 1) / segs - (0 if s == segs - 1 else G) top.prism_y(pts, y0, y1, GRANITE if (i + s) % 3 else GRANITE2) ob_top = top.build('Seokchuk_Mison_Hongyeseok') # 성문 문짝(열린 상태로 측벽에 붙임) door = MB() for side in (-1, 1): xa = side * (R_IN - 0.02) xb = side * (R_IN - 0.16) door.box((min(xa, xb), 1.2, 0.08), (max(xa, xb), 3.9, ZS + 0.55), COL) for zz in (0.6, 1.6, 2.6, 3.4): # 띠쇠 door.box((min(xa, xb) - 0.015, 1.2 + 0.05, zz), (max(xa, xb) + 0.015, 3.9 - 0.05, zz + 0.12), GRANITE2) door.build('Gate_Doors') # 여장(女墻) — 석축 위 낮은 담과 기와 지붕돌 par = MB() t, ph = 0.55, 1.05 ex, ey = hx - 0.05, hy - 0.05 segs = [] n = 9 for i in range(n): x0 = -ex + 2 * ex * i / n x1 = -ex + 2 * ex * (i + 1) / n for s in (-1, 1): segs.append(((x0 + (0.14 if i else 0), s * ey - t / 2), (x1 - (0.14 if i < n - 1 else 0), s * ey + t / 2))) n2 = 4 for i in range(n2): y0 = -(ey - t / 2) + 2 * (ey - t / 2) * i / n2 y1 = -(ey - t / 2) + 2 * (ey - t / 2) * (i + 1) / n2 for s in (-1, 1): segs.append(((s * ex - t / 2, y0 + (0.14 if i else 0)), (s * ex + t / 2, y1 - (0.14 if i < n2 - 1 else 0)))) for (xa, ya), (xb, yb) in segs: if xb - xa > yb - ya: xa, xb = xa - (t / 2 if xa < -ex + 0.01 else 0), xb + (t / 2 if xb > ex - 0.01 else 0) par.box((xa, ya, H), (xb, yb, H + ph), BRICK) par.box((xa - 0.08, ya - 0.08, H + ph), (xb + 0.08, yb + 0.08, H + ph + 0.12), TILE) # 총안(銃眼) 자리 — 작은 어두운 구멍판 if xb - xa > yb - ya: for k in range(2): cx = xa + (xb - xa) * (k + 1) / 3 par.box((cx - 0.12, ya - 0.02, H + 0.45), (cx + 0.12, yb + 0.02, H + 0.68), TILE) par.build('Yeojang_Parapet') return H# ---------------------------------------------------------------- 지붕def f_prof(t): return 0.5 * t + 0.5 * t * tclass Roof: def __init__(self, ax, ay, r, h, ze, lift, bulge, th=0.36): self.ax, self.ay, self.r, self.h, self.ze = ax, ay, r, h, ze self.lift, self.bulge, self.th = lift, bulge, th def X(self, t): return self.ax - (self.ax - self.r) * t def Y(self, t): return self.ay * (1 - t) def pt(self, u, v, t, dz=0.0): c = min(abs(u), abs(v)) ** 3 w = max(0.0, 1 - t / 0.55) ** 2 b = self.bulge * c * w return Vector((u * (self.X(t) + b), v * (self.Y(t) + b), self.ze + self.h * f_prof(t) + self.lift * c * w + dz))def build_roof(name, R, tmax, t_purlin, full, n_figs): Nf, Ne, K = 56, 28, 24 per = [] for i in range(Nf): per.append((-1 + 2 * i / Nf, -1)) for i in range(Ne): per.append((1, -1 + 2 * i / Ne)) for i in range(Nf): per.append((1 - 2 * i / Nf, 1)) for i in range(Ne): per.append((-1, 1 - 2 * i / Ne)) P = len(per) ts = [tmax * (k / K) ** 1.2 for k in range(K + 1)] sk = MB() bases = [] for dz in (0.0, -R.th): bases.append(len(sk.v)) for t in ts: for u, v in per: sk.v.append(R.pt(u, v, t, dz)) tb, bb = bases for k in range(K): for i in range(P): a, b = k * P + i, k * P + (i + 1) % P sk.face([tb + a, tb + b, tb + b + P, tb + a + P], TILE) sk.face([bb + a + P, bb + b + P, bb + b, bb + a], COL) for i in range(P): j = (i + 1) % P sk.face([bb + i, bb + j, tb + j, tb + i], TILE) if not full: o = K * P sk.face([tb + o + i, tb + o + j, bb + o + j, bb + o + i], TILE) sk.build(name + '_Surface', recalc=False) # 수키와 줄 tl = MB() pitch = 0.34 hp = [(0.13 * math.cos(math.pi * i / 6), 0.11 * math.sin(math.pi * i / 6) - 0.02) for i in range(7)] span = R.ax - R.r for v in (-1, 1): x = -R.ax + pitch / 2 while x < R.ax: lim = abs(x) / 0.975 if lim < R.ax: te = min(tmax, (R.ax - lim) / span) if te > 0.015: pts = [] for j in range(19): t = te * j / 18 u = max(-0.975, min(0.975, x / R.X(t))) pts.append(R.pt(u, v, t, 0.0)) tl.sweep(pts, hp, TILE) x += pitch for u in (-1, 1): y = -R.ay + pitch / 2 while y < R.ay: te = min(tmax, 1 - abs(y) / (0.975 * R.ay)) if te > 0.015: pts = [] for j in range(13): t = te * j / 12 v = max(-0.975, min(0.975, y / R.Y(t))) pts.append(R.pt(u, v, t, 0.0)) tl.sweep(pts, hp, TILE) y += pitch # 마루: 추녀마루(백회 양성 + 기와 덮개) hip_body = [(-0.27, -0.2), (0.27, -0.2), (0.27, 0.42), (-0.27, 0.42)] hip_cap = [(-0.32, 0.4), (0.32, 0.4), (0.32, 0.58), (-0.32, 0.58)] tend = 0.985 if full else tmax for su in (-1, 1): for sv in (-1, 1): pts = [R.pt(su, sv, -0.03 + (tend + 0.03) * j / 30) for j in range(31)] tl.sweep(pts, hip_body, MORTAR) tl.sweep(pts, hip_cap, TILE) # 잡상 for q in range(n_figs): t = 0.1 + q * 0.03 p = R.pt(su, sv, t) + Vector((0, 0, 0.62)) s = 0.1 if q else 0.13 tl.box((p.x - s, p.y - s, p.z - 0.05), (p.x + s, p.y + s, p.z + 0.22), TILE) tl.box((p.x - s * 0.7, p.y - s * 0.7, p.z + 0.22), (p.x + s * 0.7, p.y + s * 0.7, p.z + 0.36), TILE) # 추녀·사래(귀 서까래) pts = [R.pt(su, sv, -0.07 + (t_purlin + 0.07) * j / 14, -R.th - 0.2) for j in range(15)] tl.sweep(pts, [(-0.2, -0.22), (0.2, -0.22), (0.2, 0.2), (-0.2, 0.2)], GREEN, mcap0=OCHRE) if full: zt = R.ze + R.h * f_prof(0.985) rr = R.r + 0.25 tl.box((-rr, -0.36, zt - 0.5), (rr, 0.36, zt + 0.55), MORTAR) tl.box((-rr - 0.05, -0.43, zt + 0.55), (rr + 0.05, 0.43, zt + 0.78), TILE) for s in (-1, 1): # 취두 x0 = s * (rr - 0.1) prof = [(0, -0.2), (0.85, -0.2), (0.95, 0.5), (1.15, 0.95), (1.0, 1.35), (0.55, 1.55), (0.15, 1.35), (0, 1.0)] pts = [(x0 + s * a, zt + b) for a, b in prof] if s < 0: pts.reverse() tl.prism_y(pts, -0.42, 0.42, TILE) tl.box((min(x0 + s * 0.2, x0 + s * 0.75), -0.47, zt + 0.25), (max(x0 + s * 0.2, x0 + s * 0.75), 0.47, zt + 0.4), MORTAR) tl.build(name + '_Tiles_Ridges') # 서까래(부연) — 처마 밑 rf = MB() sq = [(-0.09, -0.09), (0.09, -0.09), (0.09, 0.09), (-0.09, 0.09)] rp = 0.42 dz = -R.th - 0.085 tr = t_purlin + 0.05 for v in (-1, 1): x = -R.ax + rp / 2 while x < R.ax: lim = abs(x) / 0.95 if lim < R.ax: te = min(tr, (R.ax - lim) / span) if te > 0.01: pts = [] for j in range(7): t = -0.025 + (te + 0.025) * j / 6 u = max(-0.95, min(0.95, x / R.X(max(t, 0)))) pts.append(R.pt(u, v, t, dz)) rf.sweep(pts, sq, GREEN, mcap0=BLUE) x += rp for u in (-1, 1): y = -R.ay + rp / 2 while y < R.ay: te = min(tr, 1 - abs(y) / (0.95 * R.ay)) if te > 0.01: pts = [] for j in range(7): t = -0.025 + (te + 0.025) * j / 6 v = max(-0.95, min(0.95, y / R.Y(max(t, 0)))) pts.append(R.pt(u, v, t, dz)) rf.sweep(pts, sq, GREEN, mcap0=BLUE) y += rp rf.build(name + '_Rafters')# ---------------------------------------------------------------- 목조부def column_mesh(): return cylinder('Column_proto', r=0.32, h=1.0, seg=16, material=COL)def make_bracket(): """다포 공포 한 세트. 원점=벽선·공포대 바닥, 바깥 = -Y.""" b = MB() b.box((-0.27, -0.27, 0.0), (0.27, 0.27, 0.27), GREEN) # 주두 b.box((-0.45, -0.13, 0.27), (0.45, 0.13, 0.45), BLUE) # 첨차 1 b.box((-0.13, -0.8, 0.27), (0.13, 0.35, 0.45), GREEN) # 살미 1 b.box((-0.13, -0.8, 0.27), (0.13, -0.62, 0.45), RED) for x, y in ((-0.38, 0), (0.38, 0), (0, -0.62), (0, 0)): b.box((x - 0.1, y - 0.1, 0.45), (x + 0.1, y + 0.1, 0.52), OCHRE) # 소로 b.box((-0.58, -0.13, 0.52), (0.58, 0.13, 0.7), BLUE) # 첨차 2 b.box((-0.58, -0.75, 0.52), (0.58, -0.49, 0.7), BLUE) b.box((-0.13, -1.15, 0.52), (0.13, 0.35, 0.7), GREEN) # 살미 2 b.box((-0.135, -1.15, 0.52), (0.135, -0.97, 0.7), RED) for x, y in ((-0.5, 0), (0.5, 0), (-0.5, -0.62), (0.5, -0.62), (0, -1.0)): b.box((x - 0.1, y - 0.1, 0.7), (x + 0.1, y + 0.1, 0.77), OCHRE) b.box((-0.13, -1.5, 0.77), (0.13, 0.35, 0.92), GREEN) # 살미 3(쇠서) b.box((-0.135, -1.5, 0.77), (0.135, -1.32, 0.92), RED) return b.build('Gongpo_Dapo_Set')BRACKET = Nonedef inst(proto, loc, rot=(0, 0, 0), scale=(1, 1, 1), name=None): """숨긴 원형 메시를 공유하는 보이는 복제본.""" ob = duplicate(proto, loc=loc, rot=rot, scale=scale, name=name) ob.hide_render = False ob.hide_viewport = False if 'atelier_cutter' in ob: del ob['atelier_cutter'] return obdef floor_frame(name, xs, ys, z_col_top, gongpo_skip=()): """창방·평방·포벽·장혀·외목도리 띠와 단청 머리초, 공포 배열. 공포대 윗면 z 반환.""" Wx, Wy = xs[-1], ys[-1] fr = MB() zc = z_col_top zb = zc + 0.22 ring_beams(fr, Wx, Wy, 0, 0.19, zc - 0.5, zc, GREEN) # 창방 ring_beams(fr, Wx, Wy, 0, 0.29, zc, zb, GREEN) # 평방 ring_beams(fr, Wx, Wy, 0, 0.06, zb, zb + 0.74, RED) # 포벽 ring_beams(fr, Wx, Wy, 0, 0.2, zb + 0.74, zb + 1.06, GREEN) # 장혀 ring_beams(fr, Wx, Wy, 1.25, 0.14, zb + 0.9, zb + 1.13, GREEN) # 외목도리 # 머리초(창방·평방 기둥머리 단청) for (cx, cy) in [(x, s * Wy) for x in xs for s in (-1, 1)]: fr.box((cx - 0.6, cy - 0.205, zc - 0.48), (cx + 0.6, cy + 0.205, zc - 0.02), BLUE) fr.box((cx - 0.22, cy - 0.215, zc - 0.47), (cx + 0.22, cy + 0.215, zc - 0.03), RED) fr.box((cx - 0.5, cy - 0.3, zc + 0.02), (cx + 0.5, cy + 0.3, zb - 0.02), OCHRE) for (cx, cy) in [(s * Wx, y) for y in ys[1:-1] for s in (-1, 1)]: fr.box((cx - 0.205, cy - 0.6, zc - 0.48), (cx + 0.205, cy + 0.6, zc - 0.02), BLUE) fr.box((cx - 0.215, cy - 0.22, zc - 0.47), (cx + 0.215, cy + 0.22, zc - 0.03), RED) fr.box((cx - 0.3, cy - 0.5, zc + 0.02), (cx + 0.3, cy + 0.5, zb - 0.02), OCHRE) fr.build(name + '_Beams_Dancheong') # 공포 배열 n = max(2, round(2 * Wx / 1.3)) for i in range(n + 1): x = -Wx + 2 * Wx * i / n for s in (-1, 1): if s < 0 and abs(x) < 0.9 and 'front_center' in gongpo_skip: continue inst(BRACKET, loc=(x, s * Wy, zb), rot=(0, 0, 0 if s < 0 else math.pi), name=name + '_Gongpo') m = max(2, round(2 * Wy / 1.3)) for i in range(1, m): y = -Wy + 2 * Wy * i / m for s in (-1, 1): inst(BRACKET, loc=(s * Wx, y, zb), rot=(0, 0, s * math.pi / 2), name=name + '_Gongpo') return zb + 1.13def main(): global BRACKET PT = build_platform() # 하층·상층 기둥 격자 XL = [-11.0, -7.0, -2.6, 2.6, 7.0, 11.0] YL = [-5.0, 0.0, 5.0] XU = [-9.4, -6.0, -2.2, 2.2, 6.0, 9.4] YU = [-3.6, 0.0, 3.6] ZB = PT + 0.6 # 기단 윗면 ZC1 = ZB + 3.9 # 하층 기둥머리 # 기단 gd = MB() gd.box((-12.3, -6.3, PT), (12.3, 6.3, ZB), GRANITE) gd.box((-12.15, -6.15, ZB), (12.15, 6.15, ZB + 0.02), GRANITE2) for x in XL: for y in YL: if abs(y) > 0.1 or abs(x) > 10: gd.box((x - 0.48, y - 0.48, ZB + 0.02), (x + 0.48, y + 0.48, ZB + 0.3), GRANITE) gd.build('Gidan_Base_Jucho') BRACKET = make_bracket() BRACKET.location = (0, 0, -100) BRACKET.hide_render = True BRACKET.hide_viewport = True BRACKET['atelier_cutter'] = True colp = column_mesh() colp.hide_render = True colp.hide_viewport = True colp['atelier_cutter'] = True # 하층 평주 for x in XL: for y in YL: if abs(y) > 0.1 or abs(x) > 10: inst(colp, loc=(x, y, ZB + 0.3), scale=(1, 1, ZC1 - ZB - 0.3), name='Column_Lower') zbt1 = floor_frame('Lower', XL, YL, ZC1) # 하층 지붕: 상층 마루 둘레(9.9 x 4.1)에서 잘린 우진각 처마 ax1, ay1 = 11.0 + 3.3, 5.0 + 3.2 tc = 1 - (YU[-1] + 0.5) / ay1 r1 = ax1 - (ax1 - (XU[-1] + 0.5)) / tc h1 = 6.0 tp1 = 1 - (YL[-1] + 1.25) / ay1 ze1 = zbt1 + 0.36 + 0.17 - h1 * f_prof(tp1) R1 = Roof(ax1, ay1, r1, h1, ze1, lift=0.75, bulge=0.55) build_roof('Roof_Lower', R1, tc, tp1, full=False, n_figs=5) zdeck = ze1 + h1 * f_prof(tc) + 0.03 ZU = zdeck + 0.3 ZC2 = ZU + 2.9 # 상층 마루·고주(하층 바닥부터 올라가는 내진 기둥) dk = MB() DX, DY = XU[-1] + 0.95, YU[-1] + 0.95 dk.box((-DX, -DY, zdeck), (DX, DY, ZU), WOOD) dk.box((-DX - 0.03, -DY - 0.03, zdeck + 0.02), (DX + 0.03, DY + 0.03, zdeck + 0.2), GREEN) # 계자난간 hr = 0.95 rail_pts = [] nx, ny = 16, 6 for i in range(nx + 1): for s in (-1, 1): rail_pts.append((-DX + 0.1 + 2 * (DX - 0.1) * i / nx, s * (DY - 0.1))) for i in range(1, ny): for s in (-1, 1): rail_pts.append((s * (DX - 0.1), -(DY - 0.1) + 2 * (DY - 0.1) * i / ny)) for x, y in rail_pts: dk.box((x - 0.07, y - 0.07, ZU), (x + 0.07, y + 0.07, ZU + hr + 0.08), COL) ox, oy = DX - 0.1, DY - 0.1 for z0, z1, hw, m in ((ZU + 0.05, ZU + 0.14, 0.06, COL), (ZU + 0.5, ZU + 0.58, 0.06, COL), (ZU + hr - 0.06, ZU + hr + 0.04, 0.075, COL), (ZU + 0.14, ZU + 0.5, 0.025, GREEN)): for s in (-1, 1): dk.box((-ox - hw, s * oy - hw, z0), (ox + hw, s * oy + hw, z1), m) dk.box((s * ox - hw, -oy + hw, z0), (s * ox + hw, oy - hw, z1), m) dk.build('Upper_Floor_Deck_Railing') for x in XU: for y in YU: if abs(y) > 0.1 or abs(x) > 9: inst(colp, loc=(x, y, ZB + 0.3), scale=(0.95, 0.95, ZC2 - ZB - 0.3), name='Column_Upper_Gozu') # 상층 판벽 wl = MB() Wx2, Wy2 = XU[-1], YU[-1] for s in (-1, 1): for i in range(len(XU) - 1): x0, x1 = XU[i] + 0.2, XU[i + 1] - 0.2 wl.box((x0, s * Wy2 - 0.08, ZU), (x1, s * Wy2 + 0.08, ZC2 - 0.5), COL) wl.box((x0, s * Wy2 - 0.12, ZU), (x1, s * Wy2 + 0.12, ZU + 0.35), GREEN) # 하인방 wl.box((x0, s * Wy2 - 0.12, ZU + 1.55), (x1, s * Wy2 + 0.12, ZU + 1.75), GREEN) # 중인방 # 판문 띠장 nb = 3 for k in range(1, nb + 1): xx = x0 + (x1 - x0) * k / (nb + 1) wl.box((xx - 0.05, s * Wy2 - 0.1, ZU + 0.35), (xx + 0.05, s * Wy2 + 0.1, ZU + 1.55), GREEN) # 상부 창살(교창) xx = x0 + 0.15 while xx < x1 - 0.15: wl.box((xx - 0.025, s * Wy2 - 0.11, ZU + 1.75), (xx + 0.025, s * Wy2 + 0.11, ZC2 - 0.5), WOOD) xx += 0.22 for i in range(len(YU) - 1): y0, y1 = YU[i] + 0.2, YU[i + 1] - 0.2 wl.box((s * Wx2 - 0.08, y0, ZU), (s * Wx2 + 0.08, y1, ZC2 - 0.5), COL) wl.box((s * Wx2 - 0.12, y0, ZU), (s * Wx2 + 0.12, y1, ZU + 0.35), GREEN) wl.box((s * Wx2 - 0.12, y0, ZU + 1.55), (s * Wx2 + 0.12, y1, ZU + 1.75), GREEN) wl.build('Upper_Panel_Walls') zbt2 = floor_frame('Upper', XU, YU, ZC2, gongpo_skip=('front_center',)) # 상층 우진각 지붕 ax2, ay2 = Wx2 + 3.2, Wy2 + 3.2 h2 = 5.6 tp2 = 1 - (Wy2 + 1.25) / ay2 ze2 = zbt2 + 0.36 + 0.17 - h2 * f_prof(tp2) R2 = Roof(ax2, ay2, ax2 - ay2 * 1.02, h2, ze2, lift=0.95, bulge=0.65) build_roof('Roof_Upper', R2, 0.985, tp2, full=True, n_figs=7) # 현판(세로로 건 '崇禮門' 현판 — 글자는 획 덩어리로 암시) pq = MB() pw, ph_ = 0.72, 1.5 pq.box((-pw, -0.07, -ph_), (pw, 0.07, ph_), GREEN) pq.box((-pw + 0.1, -0.09, -ph_ + 0.1), (pw - 0.1, 0.0, ph_ - 0.1), GOLD) pq.box((-pw + 0.15, -0.1, -ph_ + 0.15), (pw - 0.15, 0.0, ph_ - 0.15), PLAQUE) rnd = random.Random(7) for c in range(3): cz = ph_ - 0.14 - (2 * ph_ - 0.28) * (c + 0.5) / 3 for k in range(7): if rnd.random() < 0.5: w, hh = rnd.uniform(0.25, 0.5), 0.06 else: w, hh = 0.06, rnd.uniform(0.2, 0.32) px = rnd.uniform(-0.38 + w, 0.38 - w) pz = cz + rnd.uniform(-0.28 + hh, 0.28 - hh) pq.box((px - w, -0.13, pz - hh), (px + w, -0.08, pz + hh), GOLD) plq = pq.build('Hyeonpan_Sungnyemun') plq.location = (0, -(Wy2 + 0.95), ZC2 - 0.45) plq.rotation_euler = (math.radians(-6), 0, 0) # 현판 걸이 hk = MB() hk.box((-0.06, -(Wy2 + 1.0), ZC2 + 0.9), (0.06, -(Wy2 + 0.2), ZC2 + 1.05), COL) hk.build('Hyeonpan_Hanger') for proto in (BRACKET, colp): bpy.data.objects.remove(proto, do_unlink=True)main()






