Skip to menu

Happy Life Ecosystem

import bpy
import bmesh
import math

class BangkaArchitect:
    def __init__(self, length=8.0, beam=1.0, height=0.75): # 폭을 약간 넓혀 안정감 추가
        self.L = length      # 선체 길이
        self.W = beam        # 선체 최대 폭
        self.H = height      # 선체 높이
        self.materials = {}
        self.setup_materials()

    def setup_materials(self):
        """재료별 쉐이더 설정"""
        mats = {
            "FRP_Hull": (0.7, 0.7, 0.7, 0.9),   # FRP 외판 (반투명도 약간 줄임)
            "FRP_Bottom": (0.2, 0.4, 0.8, 1.0), # 선체 하단색 (깊은 바다색)
            "Bamboo_Core": (0.8, 0.7, 0.4, 1.0), # 대나무 합판
            "Hardwood": (0.4, 0.2, 0.1, 1.0),    # 강화 목재 (상단 트림, 엔진박스)
            "Bamboo_Outrigger": (0.7, 0.6, 0.4, 1.0), # 아웃리거 대나무
            "Metal_Fasteners": (0.6, 0.6, 0.6, 1.0) # 금속 부품 (연결대 등)
        }
        for name, color in mats.items():
            mat = bpy.data.materials.new(name=name)
            mat.use_nodes = True
            nodes = mat.node_tree.nodes
            bsdf = nodes.get("Principled BSDF")
            if bsdf:
                bsdf.inputs[0].default_value = color
                if color[3] < 1.0: # 투명도 설정
                    mat.blend_method = 'BLEND'
                    bsdf.inputs['Alpha'].default_value = color[3]
            self.materials[name] = mat

    def create_v_hull(self):
        """선체 외판 생성 (V-Hull) - 상단 곡률과 하단 색상 분할"""
        mesh = bpy.data.meshes.new("HullMesh")
        obj = bpy.data.objects.new("Main_Hull_FRP", mesh)
        bpy.context.collection.objects.link(obj)
        
        bm = bmesh.new()
        seg_y = 80 # 정밀도 더 향상
        
        # 선체 상단과 하단이 분리되도록 정점 생성
        verts = []
        for i in range(seg_y + 1):
            y = -self.L/2 + (self.L * i / seg_y)
            t = i / seg_y
            
            # 유선형 폭/높이 계산
            curve_factor = math.sin(math.pi * t)
            w = self.W * curve_factor * 0.9
            h = self.H * (0.2 + 0.8 * curve_factor)
            
            # 상단부 정점 (곡선)
            verts.append(bm.verts.new((w/2, y, h*1.05)))
            verts.append(bm.verts.new((-w/2, y, h*1.05)))
            
            # 바닥부 정점 (V-hull)
            verts.append(bm.verts.new((0, y, -0.05))) # 바닥 중앙선 (물에 잠기는 부분)
            verts.append(bm.verts.new((w/2 * 0.8, y, h*0.3))) # 중간지점
            verts.append(bm.verts.new((-w/2 * 0.8, y, h*0.3)))
            
        bm.verts.ensure_lookup_table()

        faces_top = []
        faces_bottom = []
        for i in range(seg_y):
            # 상단 곡면 (FRP_Hull 색상)
            idx_top_start = i * 5
            idx_top_next = (i + 1) * 5

            faces_top.append(bm.faces.new((verts[idx_top_start], verts[idx_top_next], verts[idx_top_next+1], verts[idx_top_start+1]))) # 우현 상단
            faces_top.append(bm.faces.new((verts[idx_top_start], verts[idx_top_start+2], verts[idx_top_next+2], verts[idx_top_next]))) # 좌현 상단
            
            # 하단 곡면 (FRP_Bottom 색상)
            faces_bottom.append(bm.faces.new((verts[idx_top_start+2], verts[idx_top_next+2], verts[idx_top_next+3], verts[idx_top_start+3]))) # 바닥 우측
            faces_bottom.append(bm.faces.new((verts[idx_top_start+2], verts[idx_top_start+4], verts[idx_top_next+4], verts[idx_top_next+2]))) # 바닥 좌측
            
        bm.to_mesh(mesh)
        
        # 재료 슬롯 할당
        obj.data.materials.append(self.materials["FRP_Hull"])
        obj.data.materials.append(self.materials["FRP_Bottom"])
        
        # 면에 재료 할당
        for face in faces_top:
            face.material_index = 0
        for face in faces_bottom:
            face.material_index = 1
            
        bm.free()

    def create_bamboo_grid_system(self):
        """내부 대나무 격자 보강재 (가로 늑골 및 세로 스트링거)"""
        grid_spacing = 0.35
        num_ribs = int(self.L / grid_spacing)

        for i in range(num_ribs):
            y_pos = -self.L/2.1 + (i * grid_spacing)
            t = (y_pos + self.L/2) / self.L
            curve = math.sin(math.pi * t)
            if curve < 0.1: continue
            
            w_at_pos = self.W * curve * 0.8 # 선체 폭에 맞춰 조정
            
            bpy.ops.mesh.primitive_cube_add(size=1, location=(0, y_pos, 0.15))
            rib = bpy.context.active_object
            rib.scale = (w_at_pos, 0.03, 0.08)
            rib.data.materials.append(self.materials["Bamboo_Core"])

        # 세로 스트링거 (3개)
        for side_offset in [-0.2, 0, 0.2]:
            bpy.ops.mesh.primitive_cylinder_add(radius=0.025, depth=self.L*0.95, location=(side_offset, 0, 0.05))
            st = bpy.context.active_object
            st.rotation_euler[0] = math.pi/2 # 90도 회전
            st.data.materials.append(self.materials["Bamboo_Core"])

    def create_deck_and_gunwale(self):
        """갑판 및 상단 트림 (강화 목재)"""
        # 상단 트림 (Gunwale) - 배의 끝선 강화
        for side in [-1, 1]:
            bpy.ops.mesh.primitive_cube_add(size=1, location=(self.W*0.48*side, 0, self.H*0.95))
            gunwale = bpy.context.active_object
            gunwale.scale = (0.05, self.L*0.98, 0.1)
            gunwale.data.materials.append(self.materials["Hardwood"])

        # 갑판 (단순화를 위해 중앙에 판 형태로)
        bpy.ops.mesh.primitive_cube_add(size=1, location=(0, self.L*0.1, self.H*0.85))
        deck = bpy.context.active_object
        deck.scale = (self.W*0.7, self.L*0.6, 0.03) # 배 길이의 60% 정도만 갑판으로
        deck.data.materials.append(self.materials["Hardwood"])

    def create_engine_and_seating(self):
        """엔진 마운트 및 좌석"""
        # 엔진 박스 (후미)
        bpy.ops.mesh.primitive_cube_add(size=1, location=(0, -self.L*0.4, 0.3))
        eng_box = bpy.context.active_object
        eng_box.scale = (self.W*0.4, 0.8, 0.4)
        eng_box.data.materials.append(self.materials["Hardwood"])
        
        # 좌석 (중앙 2개)
        for y_pos_offset in [0.1, 0.3]:
            bpy.ops.mesh.primitive_cube_add(size=1, location=(0, self.L * y_pos_offset, self.H*0.7))
            seat = bpy.context.active_object
            seat.scale = (self.W*0.6, 0.5, 0.05)
            seat.data.materials.append(self.materials["Hardwood"])

    def create_katig_system(self):
        """아웃리거 (카티그) 시스템"""
        aka_y_positions = [-1.5, 0, 1.5] # 3개의 연결대
        aka_width = 5.8 # 연결대 길이

        for y in aka_y_positions:
            # 아웃리거 연결대 (Akas)
            bpy.ops.mesh.primitive_cylinder_add(radius=0.04, depth=aka_width, location=(0, y, self.H*0.8))
            aka = bpy.context.active_object
            aka.rotation_euler[1] = math.pi/2 # 90도 회전
            aka.data.materials.append(self.materials["Bamboo_Outrigger"])

            # 연결대와 선체 사이 지지대 (선체에 붙는 부분)
            for side in [-1, 1]:
                bpy.ops.mesh.primitive_cube_add(size=1, location=(self.W*0.45*side, y, self.H*0.75))
                brace = bpy.context.active_object
                brace.scale = (0.05, 0.15, 0.1)
                brace.rotation_euler[2] = math.pi/4 if side == 1 else -math.pi/4 # 사선으로 연결
                brace.data.materials.append(self.materials["Hardwood"])

        # 대나무 부표 (Floaters)
        for x in [-aka_width/2, aka_width/2]:
            bpy.ops.mesh.primitive_cylinder_add(radius=0.12, depth=self.L*0.9, location=(x, 0, 0.1))
            floater = bpy.context.active_object
            floater.rotation_euler[0] = math.pi/2 # 90도 회전
            floater.data.materials.append(self.materials["Bamboo_Outrigger"])
            
            # 부표와 연결대 연결하는 끈 (단순화된 실린더)
            for y_con in [-self.L/3, self.L/3]:
                bpy.ops.mesh.primitive_cylinder_add(radius=0.01, depth=aka_width-self.W*1.5, location=(0, y_con, 0.5))
                rope = bpy.context.active_object
                rope.rotation_euler[1] = math.pi/2
                rope.data.materials.append(self.materials["Metal_Fasteners"]) # 끈은 금속 느낌으로

    def add_annotations(self):
        """화면에 설계 정보 텍스트 추가"""
        info = [
            ("PROJECT: Jaksim Hybrid 8M", (4, 0, 2.5)),
            ("TYPE: Bamboo Sandwich (One-off)", (4, 0, 2.1)),
            ("SPEC: No External Keel / V-Hull", (4, 0, 1.7)),
            ("DATA: PHP 80,000 Material Estimate", (4, 0, 1.3))
        ]
        for text, loc in info:
            bpy.ops.object.text_add(location=loc)
            t_obj = bpy.context.active_object
            t_obj.data.body = content
            t_obj.scale = (0.35, 0.35, 0.35)
            t_obj.rotation_euler[0], t_obj.rotation_euler[2] = math.pi/2, math.pi/2

def main():
    # 장면 초기화
    bpy.ops.object.select_all(action='SELECT')
    bpy.ops.object.delete()
    
    # 아키텍처 인스턴스 생성 및 실행
    builder = BangkaArchitect()
    builder.create_v_hull()
    builder.create_bamboo_grid_system()
    builder.create_deck_and_gunwale()
    builder.create_engine_and_seating()
    builder.create_katig_system()
    builder.add_annotations()

if __name__ == "__main__":
    main()

Up