{"allowContribute":false,"item":{"content":"## 用途\n\n將 `.stp` 3D 圖檔（通訊天線組件）自動渲染成產品級 Studio 質感圖。\n\n## 自動完成的工作\n\n\u003ctable style=\"min-width: 50px;\"\u003e\u003ccolgroup\u003e\u003ccol style=\"min-width: 25px;\"\u003e\u003ccol style=\"min-width: 25px;\"\u003e\u003c/colgroup\u003e\u003ctbody\u003e\u003ctr\u003e\u003cth colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e步驟\u003c/p\u003e\u003c/th\u003e\u003cth colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e內容\u003c/p\u003e\u003c/th\u003e\u003c/tr\u003e\u003ctr\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e1\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e清除 Blender 預設場景\u003c/p\u003e\u003c/td\u003e\u003c/tr\u003e\u003ctr\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e2\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e匯入 \u003ccode\u003e.stp\u003c/code\u003e 檔案\u003c/p\u003e\u003c/td\u003e\u003c/tr\u003e\u003ctr\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e3\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e依零件名稱自動分類並指派材質\u003c/p\u003e\u003c/td\u003e\u003c/tr\u003e\u003ctr\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e4\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e設定 Studio 三燈佈光（主光 + 補光 + 頂光）\u003c/p\u003e\u003c/td\u003e\u003c/tr\u003e\u003ctr\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e5\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e白色世界背景\u003c/p\u003e\u003c/td\u003e\u003c/tr\u003e\u003ctr\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e6\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e等軸測正交相機（自動計算場景中心與包圍盒）\u003c/p\u003e\u003c/td\u003e\u003c/tr\u003e\u003ctr\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e7\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003eCycles 渲染輸出 4K PNG\u003c/p\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/tbody\u003e\u003c/table\u003e\n\n## 材質對應\n\n\u003ctable style=\"min-width: 75px;\"\u003e\u003ccolgroup\u003e\u003ccol style=\"min-width: 25px;\"\u003e\u003ccol style=\"min-width: 25px;\"\u003e\u003ccol style=\"min-width: 25px;\"\u003e\u003c/colgroup\u003e\u003ctbody\u003e\u003ctr\u003e\u003cth colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e零件類型\u003c/p\u003e\u003c/th\u003e\u003cth colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e材質\u003c/p\u003e\u003c/th\u003e\u003cth colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e外觀\u003c/p\u003e\u003c/th\u003e\u003c/tr\u003e\u003ctr\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e灰色底板\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e\u003cstrong\u003eSatin Aluminum\u003c/strong\u003e（霧面噴砂鋁）\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e灰色金屬、粗糙度 0.40、金屬度 0.70\u003c/p\u003e\u003c/td\u003e\u003c/tr\u003e\u003ctr\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e白色支架\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e\u003cstrong\u003eMatte Aluminum\u003c/strong\u003e（霧面鋁合金）\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e白色金屬、粗糙度 0.50、金屬度 0.60\u003c/p\u003e\u003c/td\u003e\u003c/tr\u003e\u003ctr\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e綠色 PCB\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e\u003cstrong\u003ePCB Green FR4\u003c/strong\u003e\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e深綠色、粗糙度 0.35、微弱次表面散射\u003c/p\u003e\u003c/td\u003e\u003c/tr\u003e\u003ctr\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e未識別\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e\u003cstrong\u003eNeutral Gray\u003c/strong\u003e\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e中性灰、粗糙度 0.40\u003c/p\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/tbody\u003e\u003c/table\u003e\n\n## 使用步驟\n\n### 1\\. 安裝 Blender\n\n官網下載：https://www.blender.org/download/\n\n### 2\\. 建立工作資料夾\n\n```\n天線渲染/\n├── antenna.stp    ← 你的 3D 檔案（檔名可自訂）\n├── render.py      ← 本腳本\n└── (跑完後自動產出) antenna_render.png\n```\n\n### 3\\. 修改腳本設定\n\n打開 `render.py`，修改最上方的設定：\n\n```python\nSTP_FILENAME = \"antenna.stp\"    # ← 改成你的 .stp 檔名\nSAMPLES = 256                    # 128=快速 / 256=標準 / 512=高品質\n```\n\n### 4\\. 執行渲染\n\n打開終端機，`cd` 到工作資料夾，執行：\n\n```bash\nblender --background --python render.py\n```\n\n若 Blender 不在 PATH 裡，改用完整路徑：\n\n*   Windows：`\"C:\\Program Files\\Blender Foundation\\Blender 4.2\\blender.exe\" --background --python render.py`\n    \n*   Mac：`/Applications/Blender.app/Contents/MacOS/Blender --background --python render.py`\n    \n\n### 5\\. 取得結果\n\n跑完後同一資料夾裡會出現 `antenna_render.png`（4K PNG）。\n\n## 自訂選項\n\n### 命令列指定 STP 檔名（不改腳本）\n\n```bash\nblender --background --python render.py -- 我的檔案.stp\n```\n\n### 手動指定零件材質\n\n如果自動偵測不準，在腳本 `MANUAL_MAP` 中手動配對：\n\n```python\nMANUAL_MAP = {\n    \"PART_001_BASE\": 1,     # 1 = 底板（Satin Aluminum）\n    \"PART_002_HOLDER\": 2,   # 2 = 支架（Matte Aluminum）\n    \"PART_003_BOARD\": 3,    # 3 = PCB（PCB Green FR4）\n}\n```\n\n關鍵字不需要完整名稱，部分匹配即可。\n\n## 自動偵測關鍵字\n\n腳本會依以下關鍵字自動判別零件類型（不區分大小寫）：\n\n*   **PCB**：`pcb`, `board`, `circuit`, `fr4`, `pcba`\n    \n*   **底板**：`base`, `bottom`, `plate`, `lower`, `ground`, `chassis`\n    \n*   **支架**：`bracket`, `support`, `mount`, `stand`, `holder`, `frame`, `housing`, `white`, `top`, `cover`, `shield`, `radome`\n    \n\n若你的零件名稱用中文（如「底板」「支架」「電路板」），請自行修改 `RULES` 區塊加入中文關鍵字。\n\n## 渲染參數建議\n\n\u003ctable style=\"min-width: 75px;\"\u003e\u003ccolgroup\u003e\u003ccol style=\"min-width: 25px;\"\u003e\u003ccol style=\"min-width: 25px;\"\u003e\u003ccol style=\"min-width: 25px;\"\u003e\u003c/colgroup\u003e\u003ctbody\u003e\u003ctr\u003e\u003cth colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e用途\u003c/p\u003e\u003c/th\u003e\u003cth colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003eSAMPLES\u003c/p\u003e\u003c/th\u003e\u003cth colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e預估時間 (RTX 3060)\u003c/p\u003e\u003c/th\u003e\u003c/tr\u003e\u003ctr\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e快速預覽\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e64\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e~30 秒\u003c/p\u003e\u003c/td\u003e\u003c/tr\u003e\u003ctr\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e一般輸出\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e256\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e~2 分鐘\u003c/p\u003e\u003c/td\u003e\u003c/tr\u003e\u003ctr\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e高品質\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e512\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e~4 分鐘\u003c/p\u003e\u003c/td\u003e\u003c/tr\u003e\u003ctr\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e最終交付\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e1024\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e~8 分鐘\u003c/p\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/tbody\u003e\u003c/table\u003e\n\n## 系統需求\n\n*   **Blender 3.0 以上**（推薦 4.x）\n    \n*   支援 GPU 渲染可大幅加速（Blender 偏好設定中啟用 CUDA/OptiX/Metal）\n    \n\n## 腳本全文\n\n將以下內容儲存為 `render.py`：\n\n```python\n#!/usr/bin/env python3\n\"\"\"\n天線 3D 渲染腳本 - 產品級 Studio 渲染\n========================================\n用法：\n  blender --background --python render.py\n\n材質：\n  灰色底板  → 霧面噴砂鋁 (Satin Aluminum)\n  白色支架  → 霧面鋁合金 (Matte Aluminum)\n  綠色PCB   → PCB Green FR4\n  背景      → 白色 Studio\n  燈光      → Area Light × 3（主光 + 補光 + 頂光）\n\n輸出：\n  4K PNG（與腳本同目錄）\n\"\"\"\n\nimport bpy\nimport os\nimport sys\nimport math\nfrom mathutils import Vector\n\n# ╔═══════════════════════════════════════════════════════════╗\n# ║              使用者設定區（可自行修改）                    ║\n# ╚═══════════════════════════════════════════════════════════╝\n\n# 放在同一個資料夾的 .stp 檔名（改成你的檔名）\nSTP_FILENAME = \"antenna.stp\"\n\n# 輸出解析度\nRENDER_WIDTH  = 3840   # 4K 寬\nRENDER_HEIGHT = 2160   # 4K 高\n\n# 取樣數：128=快速預覽 / 256=標準 / 512=高品質 / 1024=最終輸出\nSAMPLES = 256\n\n# 輸出檔名\nOUTPUT_FILENAME = \"antenna_render.png\"\n\n# ── 手動指定零件材質（選用） ──────────────────────────────\n# 如果你的 .stp 零件名稱與自動偵測不符，\n# 可以在這裡手動指定：零件名稱關鍵字 → 材質代號\n# 材質代號：底板=1, 支架=2, PCB=3\n# 範例：\n# MANUAL_MAP = {\n#     \"PART_001_BASE\": 1,\n#     \"PART_002_HOLDER\": 2,\n# }\nMANUAL_MAP = {}  # 留空 = 全自動偵測\n\n# ╔═══════════════════════════════════════════════════════════╗\n# ║              下方為腳本邏輯，一般不需修改                  ║\n# ╚═══════════════════════════════════════════════════════════╝\n\n# ── 路徑處理 ──────────────────────────────────────────────\nscript_dir = os.path.dirname(os.path.abspath(__file__))\nstp_path   = os.path.join(script_dir, STP_FILENAME)\noutput_path = os.path.join(script_dir, OUTPUT_FILENAME)\n\n# 支援命令列傳入 STP 檔名（優先於上方設定）\n# 用法：blender --background --python render.py -- your_file.stp\nif \"--\" in sys.argv:\n    idx = sys.argv.index(\"--\")\n    if idx + 1 \u003c len(sys.argv):\n        stp_path = os.path.join(script_dir, sys.argv[idx + 1])\n        STP_FILENAME = sys.argv[idx + 1]\n\nif not os.path.exists(stp_path):\n    print(f\"\\n{'='*60}\")\n    print(f\"  ERROR: STP file not found: {stp_path}\")\n    print(f\"{'='*60}\")\n    print(f\"\\nPlease check:\")\n    print(f\"  1. .stp file is in the same folder as render.py\")\n    print(f\"  2. STP_FILENAME matches your file name\")\n    print(f\"\\nFiles in current folder:\")\n    for f in sorted(os.listdir(script_dir)):\n        print(f\"    {f}\")\n    print(f\"\\nYou can also specify filename via command line:\")\n    print(f\"  blender --background --python render.py -- your_file.stp\")\n    print(f\"{'='*60}\\n\")\n    sys.exit(1)\n\n# ── 步驟 1：清除場景 ──────────────────────────────────────\nprint(f\"\\n{'='*60}\")\nprint(f\"  Antenna 3D Studio Render Script\")\nprint(f\"{'='*60}\")\nprint(f\"  STP file : {STP_FILENAME}\")\nprint(f\"  Output   : {OUTPUT_FILENAME}\")\nprint(f\"  Size     : {RENDER_WIDTH}x{RENDER_HEIGHT}\")\nprint(f\"  Samples  : {SAMPLES}\")\nprint(f\"{'='*60}\\n\")\n\nprint(\"[1/7] Clearing default scene...\")\nbpy.ops.object.select_all(action='SELECT')\nbpy.ops.object.delete(use_global=False)\n\nfor block in bpy.data.materials:\n    bpy.data.materials.remove(block)\n\n# ── 步驟 2：匯入 STP ──────────────────────────────────────\nprint(f\"[2/7] Importing STP file...\")\n\ntry:\n    bpy.ops.import_scene.step(filepath=stp_path)\nexcept AttributeError:\n    try:\n        bpy.ops.wm.step_import(filepath=stp_path)\n    except AttributeError:\n        print(\"\\nERROR: Cannot import STP. Please use Blender 3.0+\")\n        sys.exit(1)\nexcept Exception as e:\n    print(f\"\\nERROR importing STP: {e}\")\n    sys.exit(1)\n\nmesh_objects = [obj for obj in bpy.context.scene.objects if obj.type == 'MESH']\nprint(f\"  Imported {len(mesh_objects)} parts:\")\nfor obj in mesh_objects:\n    print(f\"    {obj.name}\")\n\n# ── 步驟 3：建立材質 ──────────────────────────────────────\nprint(f\"\\n[3/7] Creating materials...\")\n\ndef make_material(name, r, g, b, roughness, metallic, subsurface=0.0):\n    mat = bpy.data.materials.new(name=name)\n    mat.use_nodes = True\n    bsdf = mat.node_tree.nodes.get(\"Principled BSDF\")\n    if bsdf is None:\n        bsdf = mat.node_tree.nodes.new(\"ShaderNodeBsdfPrincipled\")\n        output = mat.node_tree.nodes.get(\"Material Output\")\n        if output:\n            mat.node_tree.links.new(bsdf.outputs[0], output.inputs[0])\n    bsdf.inputs['Base Color'].default_value = (r, g, b, 1.0)\n    bsdf.inputs['Roughness'].default_value = roughness\n    bsdf.inputs['Metallic'].default_value = metallic\n    if subsurface \u003e 0:\n        bsdf.inputs['Subsurface'].default_value = subsurface\n    return mat\n\nMAT_SATIN   = make_material(\"Satin_Aluminum\",  0.62, 0.62, 0.64, 0.40, 0.70)\nMAT_MATTE   = make_material(\"Matte_Aluminum\",  0.80, 0.80, 0.82, 0.50, 0.60)\nMAT_PCB     = make_material(\"PCB_Green_FR4\",   0.06, 0.33, 0.10, 0.35, 0.00, 0.05)\nMAT_DEFAULT = make_material(\"Neutral_Gray\",    0.55, 0.55, 0.56, 0.40, 0.30)\n\nprint(\"  Satin Aluminum  - gray base plate\")\nprint(\"  Matte Aluminum  - white brackets\")\nprint(\"  PCB Green FR4   - green circuit board\")\nprint(\"  Neutral Gray    - fallback\")\n\n# ── 步驟 4：自動分類零件 ──────────────────────────────────\nprint(f\"\\n[4/7] Classifying parts...\")\n\nRULES = [\n    (MAT_PCB,    [\"pcb\", \"board\", \"circuit\", \"fr4\", \"pcba\"]),\n    (MAT_SATIN,  [\"base\", \"bottom\", \"plate\", \"lower\", \"ground\", \"chassis\"]),\n    (MAT_MATTE,  [\"bracket\", \"support\", \"mount\", \"stand\", \"holder\", \"frame\",\n                   \"housing\", \"white\", \"top\", \"cover\", \"shield\", \"radome\"]),\n]\n\nstats = {}\nfor mat in [MAT_PCB, MAT_SATIN, MAT_MATTE, MAT_DEFAULT]:\n    stats[mat.name] = 0\n\nfor obj in mesh_objects:\n    name = obj.name.lower()\n\n    assigned = False\n    for key, mat_code in MANUAL_MAP.items():\n        if key.lower() in name:\n            mat_map = {1: MAT_SATIN, 2: MAT_MATTE, 3: MAT_PCB}\n            target = mat_map.get(mat_code, MAT_DEFAULT)\n            obj.data.materials.clear()\n            obj.data.materials.append(target)\n            stats[target.name] += 1\n            print(f\"  [MANUAL] {obj.name} -\u003e {target.name}\")\n            assigned = True\n            break\n\n    if assigned:\n        continue\n\n    best_mat = MAT_DEFAULT\n    best_len = 0\n    for mat, keywords in RULES:\n        for kw in keywords:\n            if kw in name and len(kw) \u003e best_len:\n                best_mat = mat\n                best_len = len(kw)\n\n    obj.data.materials.clear()\n    obj.data.materials.append(best_mat)\n    stats[best_mat.name] += 1\n\n    labels = {MAT_PCB: \"PCB\", MAT_SATIN: \"BASE\", MAT_MATTE: \"BRACKET\", MAT_DEFAULT: \"OTHER\"}\n    print(f\"  [{labels.get(best_mat, '?')}] {obj.name}\")\n\nprint(f\"\\n  Material assignment summary:\")\nfor mat_name, count in stats.items():\n    if count \u003e 0:\n        print(f\"    {mat_name}: {count} parts\")\n\n# ── 步驟 5：燈光與環境 ─────────────────────────────────────\nprint(f\"\\n[5/7] Setting up studio lighting...\")\n\nworld = bpy.context.scene.world\nworld.use_nodes = True\nworld_nodes = world.node_tree.nodes\nworld_bg = world_nodes.get(\"Background\")\nif world_bg is None:\n    world_bg = world_nodes.new(\"ShaderNodeBackground\")\n    world_output = world_nodes.get(\"World Output\")\n    if world_output:\n        world.node_tree.links.new(world_bg.outputs[0], world_output.inputs[0])\nworld_bg.inputs['Color'].default_value = (1.0, 1.0, 1.0, 1.0)\nworld_bg.inputs['Strength'].default_value = 0.15\n\n# Key light - large softbox from upper-left\nbpy.ops.object.light_add(type='AREA', location=(10, -8, 12))\nkey = bpy.context.active_object\nkey.name = \"Key_Light\"\nkey.data.energy = 600\nkey.data.size = 4.0\nkey.data.color = (1.0, 0.97, 0.93)\n\n# Fill light - right side, cooler tone\nbpy.ops.object.light_add(type='AREA', location=(-5, 8, 5))\nfill = bpy.context.active_object\nfill.name = \"Fill_Light\"\nfill.data.energy = 250\nfill.data.size = 2.5\nfill.data.color = (0.95, 0.95, 1.0)\n\n# Top light\nbpy.ops.object.light_add(type='AREA', location=(0, 0, 14))\ntop = bpy.context.active_object\ntop.name = \"Top_Light\"\ntop.data.energy = 150\ntop.data.size = 2.0\ntop.data.color = (1.0, 1.0, 1.0)\n\nprint(\"  Key light  : upper-left warm softbox\")\nprint(\"  Fill light : right side cool softbox\")\nprint(\"  Top light  : overhead ambient\")\n\n# ── 步驟 6：相機 ───────────────────────────────────────────\nprint(f\"\\n[6/7] Setting up isometric camera...\")\n\nif mesh_objects:\n    bbox_center = Vector((0.0, 0.0, 0.0))\n    for obj in mesh_objects:\n        bbox_center += obj.location\n    bbox_center /= len(mesh_objects)\n\n    all_verts = []\n    for obj in mesh_objects:\n        for vert in obj.data.vertices:\n            all_verts.append(obj.matrix_world @ vert.co)\n    if all_verts:\n        min_c = Vector(all_verts[0])\n        max_c = Vector(all_verts[0])\n        for v in all_verts:\n            min_c.x = min(min_c.x, v.x)\n            min_c.y = min(min_c.y, v.y)\n            min_c.z = min(min_c.z, v.z)\n            max_c.x = max(max_c.x, v.x)\n            max_c.y = max(max_c.y, v.y)\n            max_c.z = max(max_c.z, v.z)\n        bbox_size = (max_c - min_c).length\n    else:\n        bbox_size = 10.0\nelse:\n    bbox_center = Vector((0, 0, 0))\n    bbox_size = 10.0\n\nprint(f\"  Scene center : ({bbox_center.x:.2f}, {bbox_center.y:.2f}, {bbox_center.z:.2f})\")\nprint(f\"  Scene extent : {bbox_size:.2f}\")\n\ncam_dist = bbox_size * 1.8\ncam_loc = bbox_center + Vector((cam_dist, -cam_dist, cam_dist * 0.7))\n\nbpy.ops.object.camera_add(location=cam_loc)\ncamera = bpy.context.active_object\ncamera.name = \"Render_Camera\"\ncamera.data.type = 'ORTHO'\ncamera.data.ortho_scale = bbox_size * 1.5\n\nlook_dir = bbox_center - camera.location\ncamera.rotation_euler = look_dir.to_track_quat('-Z', 'Y').to_euler()\nbpy.context.scene.camera = camera\n\nprint(f\"  Camera pos  : ({cam_loc.x:.2f}, {cam_loc.y:.2f}, {cam_loc.z:.2f})\")\nprint(f\"  Ortho scale : {camera.data.ortho_scale:.2f}\")\n\n# ── 步驟 7：渲染 ───────────────────────────────────────────\nprint(f\"\\n[7/7] Rendering...\")\n\nscene = bpy.context.scene\nscene.render.engine = 'CYCLES'\nscene.render.resolution_x = RENDER_WIDTH\nscene.render.resolution_y = RENDER_HEIGHT\nscene.render.resolution_percentage = 100\nscene.render.image_settings.file_format = 'PNG'\nscene.render.image_settings.color_mode = 'RGBA'\nscene.render.image_settings.color_depth = '16'\nscene.render.filepath = output_path\n\ncycles = scene.cycles\ncycles.samples = SAMPLES\ncycles.use_denoising = True\ncycles.use_adaptive_sampling = True\ncycles.adaptive_threshold = 0.01\n\nscene.view_settings.view_transform = 'Standard'\nscene.view_settings.look = 'Medium High Contrast'\n\nprint(f\"  Rendering {RENDER_WIDTH}x{RENDER_HEIGHT} at {SAMPLES} samples...\")\nprint(f\"  This may take a few minutes. Please wait...\\n\")\n\nbpy.ops.render.render(write_still=True)\n\nprint(f\"\\n{'='*60}\")\nprint(f\"  RENDER COMPLETE!\")\nprint(f\"  Output : {output_path}\")\nprint(f\"  Size   : {RENDER_WIDTH} x {RENDER_HEIGHT} px\")\nprint(f\"  Samples: {SAMPLES}\")\nprint(f\"{'='*60}\\n\")\n```\n\n​","createdAt":1784776211103,"deletedAt":null,"id":"4c6cb76033dee29ddc619739","isNew":false,"isPublic":true,"itemType":"NOTE","name":"Blender 3D 天線渲染腳本","parents":{"8a5f4288be52364b48c19bc1":0},"preParentID":null,"updatedAt":1787592729356,"updatedBy":{"agentId":"ceo","agentName":"CEO","userId":"6a37ba07022c4e99579334","userName":"W Love"},"version":10,"websiteContent":"[{\"originUrl\":\"https://www.blender.org/download/\",\"title\":null,\"content\":null,\"imageUrl\":null}]"},"ownerName":"W Love","subtree":[{"content":"## 用途\n\n將 `.stp` 3D 圖檔（通訊天線組件）自動渲染成產品級 Studio 質感圖。\n\n## 自動完成的工作\n\n\u003ctable style=\"min-width: 50px;\"\u003e\u003ccolgroup\u003e\u003ccol style=\"min-width: 25px;\"\u003e\u003ccol style=\"min-width: 25px;\"\u003e\u003c/colgroup\u003e\u003ctbody\u003e\u003ctr\u003e\u003cth colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e步驟\u003c/p\u003e\u003c/th\u003e\u003cth colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e內容\u003c/p\u003e\u003c/th\u003e\u003c/tr\u003e\u003ctr\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e1\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e清除 Blender 預設場景\u003c/p\u003e\u003c/td\u003e\u003c/tr\u003e\u003ctr\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e2\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e匯入 \u003ccode\u003e.stp\u003c/code\u003e 檔案\u003c/p\u003e\u003c/td\u003e\u003c/tr\u003e\u003ctr\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e3\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e依零件名稱自動分類並指派材質\u003c/p\u003e\u003c/td\u003e\u003c/tr\u003e\u003ctr\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e4\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e設定 Studio 三燈佈光（主光 + 補光 + 頂光）\u003c/p\u003e\u003c/td\u003e\u003c/tr\u003e\u003ctr\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e5\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e白色世界背景\u003c/p\u003e\u003c/td\u003e\u003c/tr\u003e\u003ctr\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e6\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e等軸測正交相機（自動計算場景中心與包圍盒）\u003c/p\u003e\u003c/td\u003e\u003c/tr\u003e\u003ctr\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e7\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003eCycles 渲染輸出 4K PNG\u003c/p\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/tbody\u003e\u003c/table\u003e\n\n## 材質對應\n\n\u003ctable style=\"min-width: 75px;\"\u003e\u003ccolgroup\u003e\u003ccol style=\"min-width: 25px;\"\u003e\u003ccol style=\"min-width: 25px;\"\u003e\u003ccol style=\"min-width: 25px;\"\u003e\u003c/colgroup\u003e\u003ctbody\u003e\u003ctr\u003e\u003cth colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e零件類型\u003c/p\u003e\u003c/th\u003e\u003cth colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e材質\u003c/p\u003e\u003c/th\u003e\u003cth colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e外觀\u003c/p\u003e\u003c/th\u003e\u003c/tr\u003e\u003ctr\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e灰色底板\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e\u003cstrong\u003eSatin Aluminum\u003c/strong\u003e（霧面噴砂鋁）\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e灰色金屬、粗糙度 0.40、金屬度 0.70\u003c/p\u003e\u003c/td\u003e\u003c/tr\u003e\u003ctr\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e白色支架\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e\u003cstrong\u003eMatte Aluminum\u003c/strong\u003e（霧面鋁合金）\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e白色金屬、粗糙度 0.50、金屬度 0.60\u003c/p\u003e\u003c/td\u003e\u003c/tr\u003e\u003ctr\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e綠色 PCB\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e\u003cstrong\u003ePCB Green FR4\u003c/strong\u003e\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e深綠色、粗糙度 0.35、微弱次表面散射\u003c/p\u003e\u003c/td\u003e\u003c/tr\u003e\u003ctr\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e未識別\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e\u003cstrong\u003eNeutral Gray\u003c/strong\u003e\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e中性灰、粗糙度 0.40\u003c/p\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/tbody\u003e\u003c/table\u003e\n\n## 使用步驟\n\n### 1\\. 安裝 Blender\n\n官網下載：https://www.blender.org/download/\n\n### 2\\. 建立工作資料夾\n\n```\n天線渲染/\n├── antenna.stp    ← 你的 3D 檔案（檔名可自訂）\n├── render.py      ← 本腳本\n└── (跑完後自動產出) antenna_render.png\n```\n\n### 3\\. 修改腳本設定\n\n打開 `render.py`，修改最上方的設定：\n\n```python\nSTP_FILENAME = \"antenna.stp\"    # ← 改成你的 .stp 檔名\nSAMPLES = 256                    # 128=快速 / 256=標準 / 512=高品質\n```\n\n### 4\\. 執行渲染\n\n打開終端機，`cd` 到工作資料夾，執行：\n\n```bash\nblender --background --python render.py\n```\n\n若 Blender 不在 PATH 裡，改用完整路徑：\n\n*   Windows：`\"C:\\Program Files\\Blender Foundation\\Blender 4.2\\blender.exe\" --background --python render.py`\n    \n*   Mac：`/Applications/Blender.app/Contents/MacOS/Blender --background --python render.py`\n    \n\n### 5\\. 取得結果\n\n跑完後同一資料夾裡會出現 `antenna_render.png`（4K PNG）。\n\n## 自訂選項\n\n### 命令列指定 STP 檔名（不改腳本）\n\n```bash\nblender --background --python render.py -- 我的檔案.stp\n```\n\n### 手動指定零件材質\n\n如果自動偵測不準，在腳本 `MANUAL_MAP` 中手動配對：\n\n```python\nMANUAL_MAP = {\n    \"PART_001_BASE\": 1,     # 1 = 底板（Satin Aluminum）\n    \"PART_002_HOLDER\": 2,   # 2 = 支架（Matte Aluminum）\n    \"PART_003_BOARD\": 3,    # 3 = PCB（PCB Green FR4）\n}\n```\n\n關鍵字不需要完整名稱，部分匹配即可。\n\n## 自動偵測關鍵字\n\n腳本會依以下關鍵字自動判別零件類型（不區分大小寫）：\n\n*   **PCB**：`pcb`, `board`, `circuit`, `fr4`, `pcba`\n    \n*   **底板**：`base`, `bottom`, `plate`, `lower`, `ground`, `chassis`\n    \n*   **支架**：`bracket`, `support`, `mount`, `stand`, `holder`, `frame`, `housing`, `white`, `top`, `cover`, `shield`, `radome`\n    \n\n若你的零件名稱用中文（如「底板」「支架」「電路板」），請自行修改 `RULES` 區塊加入中文關鍵字。\n\n## 渲染參數建議\n\n\u003ctable style=\"min-width: 75px;\"\u003e\u003ccolgroup\u003e\u003ccol style=\"min-width: 25px;\"\u003e\u003ccol style=\"min-width: 25px;\"\u003e\u003ccol style=\"min-width: 25px;\"\u003e\u003c/colgroup\u003e\u003ctbody\u003e\u003ctr\u003e\u003cth colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e用途\u003c/p\u003e\u003c/th\u003e\u003cth colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003eSAMPLES\u003c/p\u003e\u003c/th\u003e\u003cth colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e預估時間 (RTX 3060)\u003c/p\u003e\u003c/th\u003e\u003c/tr\u003e\u003ctr\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e快速預覽\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e64\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e~30 秒\u003c/p\u003e\u003c/td\u003e\u003c/tr\u003e\u003ctr\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e一般輸出\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e256\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e~2 分鐘\u003c/p\u003e\u003c/td\u003e\u003c/tr\u003e\u003ctr\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e高品質\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e512\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e~4 分鐘\u003c/p\u003e\u003c/td\u003e\u003c/tr\u003e\u003ctr\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e最終交付\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e1024\u003c/p\u003e\u003c/td\u003e\u003ctd colspan=\"1\" rowspan=\"1\"\u003e\u003cp\u003e~8 分鐘\u003c/p\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/tbody\u003e\u003c/table\u003e\n\n## 系統需求\n\n*   **Blender 3.0 以上**（推薦 4.x）\n    \n*   支援 GPU 渲染可大幅加速（Blender 偏好設定中啟用 CUDA/OptiX/Metal）\n    \n\n## 腳本全文\n\n將以下內容儲存為 `render.py`：\n\n```python\n#!/usr/bin/env python3\n\"\"\"\n天線 3D 渲染腳本 - 產品級 Studio 渲染\n========================================\n用法：\n  blender --background --python render.py\n\n材質：\n  灰色底板  → 霧面噴砂鋁 (Satin Aluminum)\n  白色支架  → 霧面鋁合金 (Matte Aluminum)\n  綠色PCB   → PCB Green FR4\n  背景      → 白色 Studio\n  燈光      → Area Light × 3（主光 + 補光 + 頂光）\n\n輸出：\n  4K PNG（與腳本同目錄）\n\"\"\"\n\nimport bpy\nimport os\nimport sys\nimport math\nfrom mathutils import Vector\n\n# ╔═══════════════════════════════════════════════════════════╗\n# ║              使用者設定區（可自行修改）                    ║\n# ╚═══════════════════════════════════════════════════════════╝\n\n# 放在同一個資料夾的 .stp 檔名（改成你的檔名）\nSTP_FILENAME = \"antenna.stp\"\n\n# 輸出解析度\nRENDER_WIDTH  = 3840   # 4K 寬\nRENDER_HEIGHT = 2160   # 4K 高\n\n# 取樣數：128=快速預覽 / 256=標準 / 512=高品質 / 1024=最終輸出\nSAMPLES = 256\n\n# 輸出檔名\nOUTPUT_FILENAME = \"antenna_render.png\"\n\n# ── 手動指定零件材質（選用） ──────────────────────────────\n# 如果你的 .stp 零件名稱與自動偵測不符，\n# 可以在這裡手動指定：零件名稱關鍵字 → 材質代號\n# 材質代號：底板=1, 支架=2, PCB=3\n# 範例：\n# MANUAL_MAP = {\n#     \"PART_001_BASE\": 1,\n#     \"PART_002_HOLDER\": 2,\n# }\nMANUAL_MAP = {}  # 留空 = 全自動偵測\n\n# ╔═══════════════════════════════════════════════════════════╗\n# ║              下方為腳本邏輯，一般不需修改                  ║\n# ╚═══════════════════════════════════════════════════════════╝\n\n# ── 路徑處理 ──────────────────────────────────────────────\nscript_dir = os.path.dirname(os.path.abspath(__file__))\nstp_path   = os.path.join(script_dir, STP_FILENAME)\noutput_path = os.path.join(script_dir, OUTPUT_FILENAME)\n\n# 支援命令列傳入 STP 檔名（優先於上方設定）\n# 用法：blender --background --python render.py -- your_file.stp\nif \"--\" in sys.argv:\n    idx = sys.argv.index(\"--\")\n    if idx + 1 \u003c len(sys.argv):\n        stp_path = os.path.join(script_dir, sys.argv[idx + 1])\n        STP_FILENAME = sys.argv[idx + 1]\n\nif not os.path.exists(stp_path):\n    print(f\"\\n{'='*60}\")\n    print(f\"  ERROR: STP file not found: {stp_path}\")\n    print(f\"{'='*60}\")\n    print(f\"\\nPlease check:\")\n    print(f\"  1. .stp file is in the same folder as render.py\")\n    print(f\"  2. STP_FILENAME matches your file name\")\n    print(f\"\\nFiles in current folder:\")\n    for f in sorted(os.listdir(script_dir)):\n        print(f\"    {f}\")\n    print(f\"\\nYou can also specify filename via command line:\")\n    print(f\"  blender --background --python render.py -- your_file.stp\")\n    print(f\"{'='*60}\\n\")\n    sys.exit(1)\n\n# ── 步驟 1：清除場景 ──────────────────────────────────────\nprint(f\"\\n{'='*60}\")\nprint(f\"  Antenna 3D Studio Render Script\")\nprint(f\"{'='*60}\")\nprint(f\"  STP file : {STP_FILENAME}\")\nprint(f\"  Output   : {OUTPUT_FILENAME}\")\nprint(f\"  Size     : {RENDER_WIDTH}x{RENDER_HEIGHT}\")\nprint(f\"  Samples  : {SAMPLES}\")\nprint(f\"{'='*60}\\n\")\n\nprint(\"[1/7] Clearing default scene...\")\nbpy.ops.object.select_all(action='SELECT')\nbpy.ops.object.delete(use_global=False)\n\nfor block in bpy.data.materials:\n    bpy.data.materials.remove(block)\n\n# ── 步驟 2：匯入 STP ──────────────────────────────────────\nprint(f\"[2/7] Importing STP file...\")\n\ntry:\n    bpy.ops.import_scene.step(filepath=stp_path)\nexcept AttributeError:\n    try:\n        bpy.ops.wm.step_import(filepath=stp_path)\n    except AttributeError:\n        print(\"\\nERROR: Cannot import STP. Please use Blender 3.0+\")\n        sys.exit(1)\nexcept Exception as e:\n    print(f\"\\nERROR importing STP: {e}\")\n    sys.exit(1)\n\nmesh_objects = [obj for obj in bpy.context.scene.objects if obj.type == 'MESH']\nprint(f\"  Imported {len(mesh_objects)} parts:\")\nfor obj in mesh_objects:\n    print(f\"    {obj.name}\")\n\n# ── 步驟 3：建立材質 ──────────────────────────────────────\nprint(f\"\\n[3/7] Creating materials...\")\n\ndef make_material(name, r, g, b, roughness, metallic, subsurface=0.0):\n    mat = bpy.data.materials.new(name=name)\n    mat.use_nodes = True\n    bsdf = mat.node_tree.nodes.get(\"Principled BSDF\")\n    if bsdf is None:\n        bsdf = mat.node_tree.nodes.new(\"ShaderNodeBsdfPrincipled\")\n        output = mat.node_tree.nodes.get(\"Material Output\")\n        if output:\n            mat.node_tree.links.new(bsdf.outputs[0], output.inputs[0])\n    bsdf.inputs['Base Color'].default_value = (r, g, b, 1.0)\n    bsdf.inputs['Roughness'].default_value = roughness\n    bsdf.inputs['Metallic'].default_value = metallic\n    if subsurface \u003e 0:\n        bsdf.inputs['Subsurface'].default_value = subsurface\n    return mat\n\nMAT_SATIN   = make_material(\"Satin_Aluminum\",  0.62, 0.62, 0.64, 0.40, 0.70)\nMAT_MATTE   = make_material(\"Matte_Aluminum\",  0.80, 0.80, 0.82, 0.50, 0.60)\nMAT_PCB     = make_material(\"PCB_Green_FR4\",   0.06, 0.33, 0.10, 0.35, 0.00, 0.05)\nMAT_DEFAULT = make_material(\"Neutral_Gray\",    0.55, 0.55, 0.56, 0.40, 0.30)\n\nprint(\"  Satin Aluminum  - gray base plate\")\nprint(\"  Matte Aluminum  - white brackets\")\nprint(\"  PCB Green FR4   - green circuit board\")\nprint(\"  Neutral Gray    - fallback\")\n\n# ── 步驟 4：自動分類零件 ──────────────────────────────────\nprint(f\"\\n[4/7] Classifying parts...\")\n\nRULES = [\n    (MAT_PCB,    [\"pcb\", \"board\", \"circuit\", \"fr4\", \"pcba\"]),\n    (MAT_SATIN,  [\"base\", \"bottom\", \"plate\", \"lower\", \"ground\", \"chassis\"]),\n    (MAT_MATTE,  [\"bracket\", \"support\", \"mount\", \"stand\", \"holder\", \"frame\",\n                   \"housing\", \"white\", \"top\", \"cover\", \"shield\", \"radome\"]),\n]\n\nstats = {}\nfor mat in [MAT_PCB, MAT_SATIN, MAT_MATTE, MAT_DEFAULT]:\n    stats[mat.name] = 0\n\nfor obj in mesh_objects:\n    name = obj.name.lower()\n\n    assigned = False\n    for key, mat_code in MANUAL_MAP.items():\n        if key.lower() in name:\n            mat_map = {1: MAT_SATIN, 2: MAT_MATTE, 3: MAT_PCB}\n            target = mat_map.get(mat_code, MAT_DEFAULT)\n            obj.data.materials.clear()\n            obj.data.materials.append(target)\n            stats[target.name] += 1\n            print(f\"  [MANUAL] {obj.name} -\u003e {target.name}\")\n            assigned = True\n            break\n\n    if assigned:\n        continue\n\n    best_mat = MAT_DEFAULT\n    best_len = 0\n    for mat, keywords in RULES:\n        for kw in keywords:\n            if kw in name and len(kw) \u003e best_len:\n                best_mat = mat\n                best_len = len(kw)\n\n    obj.data.materials.clear()\n    obj.data.materials.append(best_mat)\n    stats[best_mat.name] += 1\n\n    labels = {MAT_PCB: \"PCB\", MAT_SATIN: \"BASE\", MAT_MATTE: \"BRACKET\", MAT_DEFAULT: \"OTHER\"}\n    print(f\"  [{labels.get(best_mat, '?')}] {obj.name}\")\n\nprint(f\"\\n  Material assignment summary:\")\nfor mat_name, count in stats.items():\n    if count \u003e 0:\n        print(f\"    {mat_name}: {count} parts\")\n\n# ── 步驟 5：燈光與環境 ─────────────────────────────────────\nprint(f\"\\n[5/7] Setting up studio lighting...\")\n\nworld = bpy.context.scene.world\nworld.use_nodes = True\nworld_nodes = world.node_tree.nodes\nworld_bg = world_nodes.get(\"Background\")\nif world_bg is None:\n    world_bg = world_nodes.new(\"ShaderNodeBackground\")\n    world_output = world_nodes.get(\"World Output\")\n    if world_output:\n        world.node_tree.links.new(world_bg.outputs[0], world_output.inputs[0])\nworld_bg.inputs['Color'].default_value = (1.0, 1.0, 1.0, 1.0)\nworld_bg.inputs['Strength'].default_value = 0.15\n\n# Key light - large softbox from upper-left\nbpy.ops.object.light_add(type='AREA', location=(10, -8, 12))\nkey = bpy.context.active_object\nkey.name = \"Key_Light\"\nkey.data.energy = 600\nkey.data.size = 4.0\nkey.data.color = (1.0, 0.97, 0.93)\n\n# Fill light - right side, cooler tone\nbpy.ops.object.light_add(type='AREA', location=(-5, 8, 5))\nfill = bpy.context.active_object\nfill.name = \"Fill_Light\"\nfill.data.energy = 250\nfill.data.size = 2.5\nfill.data.color = (0.95, 0.95, 1.0)\n\n# Top light\nbpy.ops.object.light_add(type='AREA', location=(0, 0, 14))\ntop = bpy.context.active_object\ntop.name = \"Top_Light\"\ntop.data.energy = 150\ntop.data.size = 2.0\ntop.data.color = (1.0, 1.0, 1.0)\n\nprint(\"  Key light  : upper-left warm softbox\")\nprint(\"  Fill light : right side cool softbox\")\nprint(\"  Top light  : overhead ambient\")\n\n# ── 步驟 6：相機 ───────────────────────────────────────────\nprint(f\"\\n[6/7] Setting up isometric camera...\")\n\nif mesh_objects:\n    bbox_center = Vector((0.0, 0.0, 0.0))\n    for obj in mesh_objects:\n        bbox_center += obj.location\n    bbox_center /= len(mesh_objects)\n\n    all_verts = []\n    for obj in mesh_objects:\n        for vert in obj.data.vertices:\n            all_verts.append(obj.matrix_world @ vert.co)\n    if all_verts:\n        min_c = Vector(all_verts[0])\n        max_c = Vector(all_verts[0])\n        for v in all_verts:\n            min_c.x = min(min_c.x, v.x)\n            min_c.y = min(min_c.y, v.y)\n            min_c.z = min(min_c.z, v.z)\n            max_c.x = max(max_c.x, v.x)\n            max_c.y = max(max_c.y, v.y)\n            max_c.z = max(max_c.z, v.z)\n        bbox_size = (max_c - min_c).length\n    else:\n        bbox_size = 10.0\nelse:\n    bbox_center = Vector((0, 0, 0))\n    bbox_size = 10.0\n\nprint(f\"  Scene center : ({bbox_center.x:.2f}, {bbox_center.y:.2f}, {bbox_center.z:.2f})\")\nprint(f\"  Scene extent : {bbox_size:.2f}\")\n\ncam_dist = bbox_size * 1.8\ncam_loc = bbox_center + Vector((cam_dist, -cam_dist, cam_dist * 0.7))\n\nbpy.ops.object.camera_add(location=cam_loc)\ncamera = bpy.context.active_object\ncamera.name = \"Render_Camera\"\ncamera.data.type = 'ORTHO'\ncamera.data.ortho_scale = bbox_size * 1.5\n\nlook_dir = bbox_center - camera.location\ncamera.rotation_euler = look_dir.to_track_quat('-Z', 'Y').to_euler()\nbpy.context.scene.camera = camera\n\nprint(f\"  Camera pos  : ({cam_loc.x:.2f}, {cam_loc.y:.2f}, {cam_loc.z:.2f})\")\nprint(f\"  Ortho scale : {camera.data.ortho_scale:.2f}\")\n\n# ── 步驟 7：渲染 ───────────────────────────────────────────\nprint(f\"\\n[7/7] Rendering...\")\n\nscene = bpy.context.scene\nscene.render.engine = 'CYCLES'\nscene.render.resolution_x = RENDER_WIDTH\nscene.render.resolution_y = RENDER_HEIGHT\nscene.render.resolution_percentage = 100\nscene.render.image_settings.file_format = 'PNG'\nscene.render.image_settings.color_mode = 'RGBA'\nscene.render.image_settings.color_depth = '16'\nscene.render.filepath = output_path\n\ncycles = scene.cycles\ncycles.samples = SAMPLES\ncycles.use_denoising = True\ncycles.use_adaptive_sampling = True\ncycles.adaptive_threshold = 0.01\n\nscene.view_settings.view_transform = 'Standard'\nscene.view_settings.look = 'Medium High Contrast'\n\nprint(f\"  Rendering {RENDER_WIDTH}x{RENDER_HEIGHT} at {SAMPLES} samples...\")\nprint(f\"  This may take a few minutes. Please wait...\\n\")\n\nbpy.ops.render.render(write_still=True)\n\nprint(f\"\\n{'='*60}\")\nprint(f\"  RENDER COMPLETE!\")\nprint(f\"  Output : {output_path}\")\nprint(f\"  Size   : {RENDER_WIDTH} x {RENDER_HEIGHT} px\")\nprint(f\"  Samples: {SAMPLES}\")\nprint(f\"{'='*60}\\n\")\n```\n\n​","createdAt":1784776211103,"deletedAt":null,"id":"4c6cb76033dee29ddc619739","isNew":false,"isPublic":true,"itemType":"NOTE","name":"Blender 3D 天線渲染腳本","parents":{"8a5f4288be52364b48c19bc1":0},"preParentID":null,"updatedAt":1787592729356,"updatedBy":{"agentId":"ceo","agentName":"CEO","userId":"6a37ba07022c4e99579334","userName":"W Love"},"version":10,"websiteContent":"[{\"originUrl\":\"https://www.blender.org/download/\",\"title\":null,\"content\":null,\"imageUrl\":null}]"}]}