init commot
This commit is contained in:
parent
1f4552aad1
commit
ef5bc0760d
|
@ -0,0 +1,295 @@
|
|||
# Isometquick Blender渲染微服务
|
||||
|
||||
基于FastAPI的Blender等轴测房间渲染API服务,专为Linux服务器环境设计。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 🏠 **等轴测房间渲染**: 支持自定义房间尺寸
|
||||
- 📷 **多视角渲染**: 正视图和侧视图
|
||||
- 🎨 **多渲染引擎**: 工作台、EEVEE Next、Cycles
|
||||
- 🚀 **异步处理**: 后台渲染任务
|
||||
- 📊 **任务管理**: 任务状态查询和文件下载
|
||||
- 🔍 **API文档**: 自动生成的Swagger文档
|
||||
|
||||
## 系统要求
|
||||
|
||||
- Python 3.8+
|
||||
- Blender 4.3+
|
||||
- Linux服务器环境
|
||||
- Isometquick插件已安装
|
||||
- 确保 `/data/Isometquick/` 目录存在且有写入权限
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 创建输出目录
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /data/Isometquick/
|
||||
sudo chown $USER:$USER /data/Isometquick/
|
||||
sudo chmod 755 /data/Isometquick/
|
||||
```
|
||||
|
||||
### 2. 安装依赖
|
||||
|
||||
```bash
|
||||
cd Isometquick-server
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### 3. 配置Blender路径(可选)
|
||||
|
||||
```bash
|
||||
export BLENDER_PATH="/usr/local/bin/blender"
|
||||
```
|
||||
|
||||
### 4. 启动服务
|
||||
|
||||
```bash
|
||||
python start.py
|
||||
```
|
||||
|
||||
或使用uvicorn直接启动:
|
||||
|
||||
```bash
|
||||
uvicorn main:app --host 0.0.0.0 --port 8003
|
||||
```
|
||||
|
||||
### 5. 访问API文档
|
||||
|
||||
打开浏览器访问:`http://localhost:8003/docs`
|
||||
|
||||
## API接口
|
||||
|
||||
### 创建渲染任务
|
||||
|
||||
**POST** `/render`
|
||||
|
||||
```json #prop_type:description="道具类型: 0=无, 1=窗, 2=拱门, 3=门"
|
||||
{
|
||||
"room": {
|
||||
"length": 4.0,
|
||||
"width": 4.0,
|
||||
"height": 3.0,
|
||||
"prop_type":0,
|
||||
},
|
||||
"camera": {
|
||||
"height": 1.3,
|
||||
"view_type": 2,
|
||||
"rotation_angle": 45.0
|
||||
},
|
||||
"render": {
|
||||
"resolution_x": 1080,
|
||||
"resolution_y": 2400,
|
||||
"engine": "workbench"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 查询任务状态
|
||||
|
||||
**GET** `/render/{task_id}`
|
||||
|
||||
### 下载渲染结果
|
||||
|
||||
**GET** `/render/{task_id}/download`
|
||||
|
||||
**注意**: 渲染图片保存在 `/data/Isometquick/` 目录下,文件名格式为 `render_{task_id}.png`
|
||||
|
||||
### 删除任务
|
||||
|
||||
**DELETE** `/render/{task_id}`
|
||||
|
||||
## 参数说明
|
||||
|
||||
### 房间尺寸 (room)
|
||||
|
||||
- `length`: 房间长度(X轴),默认4.0米
|
||||
- `width`: 房间宽度(Y轴),默认4.0米
|
||||
- `height`: 房间高度(Z轴),默认3.0米
|
||||
|
||||
### 摄像机设置 (camera)
|
||||
|
||||
- `height`: 摄像机垂直高度,默认1.3米
|
||||
- `view_type`: 视图类型,1=正视图,2=侧视图(**默认侧视图**)
|
||||
- `rotation_angle`: 摄像机旋转角度(仅侧视图),默认45度
|
||||
|
||||
#### 摄像机位置计算
|
||||
|
||||
**正视图 (view_type=1)**:
|
||||
- 位置: `(0, -(width/2-1), height)`
|
||||
- 旋转: `(90°, 0°, 0°)`
|
||||
|
||||
**侧视图 (view_type=2) - 默认**:
|
||||
- 位置: `((length/2-1), -(width/2-1), height)`
|
||||
- 旋转: `(90°, 0°, rotation_angle)`
|
||||
|
||||
### 渲染设置 (render)
|
||||
|
||||
- `resolution_x`: 渲染宽度,默认1080px
|
||||
- `resolution_y`: 渲染高度,默认2400px
|
||||
- `engine`: 渲染引擎,支持workbench/eevee/cycles
|
||||
|
||||
## 文件存储
|
||||
|
||||
- **输出目录**: `/data/Isometquick/`
|
||||
- **文件命名**: `render_{task_id}.png`
|
||||
- **文件权限**: 确保服务进程对该目录有读写权限
|
||||
- **清理策略**: 24小时后自动清理旧文件
|
||||
|
||||
## 使用示例
|
||||
|
||||
### Python客户端示例
|
||||
|
||||
```python
|
||||
import requests
|
||||
import time
|
||||
|
||||
# 创建渲染任务(使用默认侧视图)
|
||||
response = requests.post("http://localhost:8003/render", json={
|
||||
"room": {
|
||||
"length": 5.0,
|
||||
"width": 4.0,
|
||||
"height": 3.5
|
||||
},
|
||||
"camera": {
|
||||
"height": 1.5,
|
||||
"view_type": 2,
|
||||
"rotation_angle": 30.0
|
||||
},
|
||||
"render": {
|
||||
"resolution_x": 1920,
|
||||
"resolution_y": 1080,
|
||||
"engine": "workbench"
|
||||
}
|
||||
})
|
||||
|
||||
task_id = response.json()["task_id"]
|
||||
|
||||
# 轮询任务状态
|
||||
while True:
|
||||
status_response = requests.get(f"http://localhost:8003/render/{task_id}")
|
||||
status = status_response.json()["status"]
|
||||
|
||||
if status == "completed":
|
||||
# 下载结果
|
||||
download_response = requests.get(f"http://localhost:8003/render/{task_id}/download")
|
||||
with open(f"render_{task_id}.png", "wb") as f:
|
||||
f.write(download_response.content)
|
||||
break
|
||||
elif status == "failed":
|
||||
print("渲染失败")
|
||||
break
|
||||
|
||||
time.sleep(2)
|
||||
```
|
||||
|
||||
### curl示例
|
||||
|
||||
```bash
|
||||
# 创建任务(使用默认侧视图)
|
||||
curl -X POST "http://localhost:8003/render" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"room": {"length": 4.0, "width": 4.0, "height": 3.0},
|
||||
"camera": {"height": 1.3, "view_type": 2, "rotation_angle": 45.0},
|
||||
"render": {"resolution_x": 1080, "resolution_y": 2400, "engine": "workbench"}
|
||||
}'
|
||||
|
||||
# 查询状态
|
||||
curl "http://localhost:8003/render/{task_id}"
|
||||
|
||||
# 下载结果
|
||||
curl "http://localhost:8003/render/{task_id}/download" -o render.png
|
||||
```
|
||||
|
||||
## 部署
|
||||
|
||||
### 目录准备
|
||||
|
||||
```bash
|
||||
# 创建输出目录
|
||||
sudo mkdir -p /data/Isometquick/
|
||||
sudo chown www-data:www-data /data/Isometquick/ # 或适当的用户
|
||||
sudo chmod 755 /data/Isometquick/
|
||||
```
|
||||
|
||||
### Docker部署
|
||||
|
||||
```dockerfile
|
||||
FROM python:3.9-slim
|
||||
|
||||
# 安装Blender
|
||||
RUN apt-get update && apt-get install -y blender
|
||||
|
||||
# 创建输出目录
|
||||
RUN mkdir -p /data/Isometquick/
|
||||
|
||||
WORKDIR /app
|
||||
COPY requirements.txt .
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
COPY . .
|
||||
|
||||
# 确保输出目录权限
|
||||
RUN chmod 755 /data/Isometquick/
|
||||
|
||||
EXPOSE 8003
|
||||
CMD ["python", "start.py"]
|
||||
```
|
||||
|
||||
### systemd服务
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=Isometquick Blender Render Service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=blender
|
||||
WorkingDirectory=/opt/isometquick-server
|
||||
ExecStart=/usr/bin/python3 start.py
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
|
||||
# 确保有访问输出目录的权限
|
||||
ReadWritePaths=/data/Isometquick/
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
## 性能优化
|
||||
|
||||
- 调整 `MAX_CONCURRENT_RENDERS` 控制并发渲染数量
|
||||
- 使用工作台引擎获得最快渲染速度
|
||||
- 定期清理旧的渲染文件
|
||||
- 考虑使用Redis存储任务状态(生产环境)
|
||||
- 监控 `/data/Isometquick/` 目录磁盘使用情况
|
||||
|
||||
## 故障排除
|
||||
|
||||
### Blender未找到
|
||||
|
||||
确保Blender已安装并在系统PATH中,或设置`BLENDER_PATH`环境变量。
|
||||
|
||||
### 插件未加载
|
||||
|
||||
确保Isometquick插件已正确安装在Blender中。
|
||||
|
||||
### 渲染超时
|
||||
|
||||
调整`BLENDER_TIMEOUT`配置或优化渲染参数。
|
||||
|
||||
### 文件权限错误
|
||||
|
||||
确保服务进程对 `/data/Isometquick/` 目录有读写权限:
|
||||
|
||||
```bash
|
||||
sudo chown -R $SERVICE_USER:$SERVICE_GROUP /data/Isometquick/
|
||||
sudo chmod -R 755 /data/Isometquick/
|
||||
```
|
||||
|
||||
## 许可证
|
||||
|
||||
与原Isometquick插件相同的许可证。
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,592 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Blender渲染服务
|
||||
处理Blender渲染逻辑,适配Linux服务器环境
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import asyncio
|
||||
import tempfile
|
||||
import shutil
|
||||
import math
|
||||
import time
|
||||
from typing import Optional, Tuple
|
||||
import logging
|
||||
|
||||
from models import RenderRequest
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BlenderRenderService:
|
||||
"""Blender渲染服务类"""
|
||||
|
||||
def __init__(self, blender_path: str = None):
|
||||
"""
|
||||
初始化Blender服务
|
||||
|
||||
Args:
|
||||
blender_path: Blender可执行文件路径,如果为None则自动检测
|
||||
"""
|
||||
self.blender_path = blender_path or self._find_blender_path()
|
||||
self.output_dir = "/data/Isometquick/"
|
||||
self._ensure_output_dir()
|
||||
|
||||
def _find_blender_path(self) -> str:
|
||||
"""自动查找Blender路径"""
|
||||
# Linux常见路径
|
||||
possible_paths = [
|
||||
"/data/blender/blender-4.2.11-linux-x64/blender",
|
||||
"blender" # 系统PATH中
|
||||
]
|
||||
|
||||
for path in possible_paths:
|
||||
if shutil.which(path):
|
||||
logger.info(f"找到Blender: {path}")
|
||||
return path
|
||||
|
||||
# 如果都找不到,使用默认值
|
||||
logger.warning("未找到Blender,使用默认路径")
|
||||
return "blender"
|
||||
|
||||
def _ensure_output_dir(self):
|
||||
"""确保输出目录存在"""
|
||||
try:
|
||||
os.makedirs(self.output_dir, exist_ok=True)
|
||||
logger.info(f"输出目录设置为: {self.output_dir}")
|
||||
except PermissionError as e:
|
||||
logger.error(f"无法创建输出目录 {self.output_dir}: 权限不足")
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"创建输出目录失败 {self.output_dir}: {e}")
|
||||
raise
|
||||
|
||||
def check_blender_available(self) -> bool:
|
||||
"""检查Blender是否可用"""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[self.blender_path, "--version"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10
|
||||
)
|
||||
return result.returncode == 0
|
||||
except Exception as e:
|
||||
logger.error(f"Blender检查失败: {e}")
|
||||
return False
|
||||
|
||||
def _calculate_room_parameters(self, request: RenderRequest) -> Tuple[float, float, float]:
|
||||
"""
|
||||
计算房间参数:floor_scale_value, x_extrude_value, y_extrude_value
|
||||
|
||||
Returns:
|
||||
(floor_scale_value, x_extrude_value, y_extrude_value)
|
||||
"""
|
||||
room_length = request.room.length # Y轴(长度)
|
||||
room_width = request.room.width # X轴(宽度)
|
||||
|
||||
# 判断长宽是否相等
|
||||
if room_length == room_width:
|
||||
# 长宽相等,按原逻辑
|
||||
floor_scale_value = room_width # 使用宽度作为基准
|
||||
x_extrude_value = 0.0
|
||||
y_extrude_value = 0.0
|
||||
else:
|
||||
# 长宽不等,需要调整
|
||||
if room_length > room_width:
|
||||
# 长比宽大(Y轴比X轴大)
|
||||
floor_scale_value = room_width # 使用较小的值(宽度)
|
||||
x_extrude_value = 0.0
|
||||
y_extrude_value = room_length - room_width # Y轴延伸
|
||||
else:
|
||||
# 宽比长大(X轴比Y轴大)
|
||||
floor_scale_value = room_length # 使用较小的值(长度)
|
||||
x_extrude_value = room_width - room_length # X轴延伸
|
||||
y_extrude_value = 0.0
|
||||
|
||||
logger.info(
|
||||
f"房间参数计算: floor_scale={floor_scale_value}, x_extrude={x_extrude_value}, y_extrude={y_extrude_value}")
|
||||
return floor_scale_value, x_extrude_value, y_extrude_value
|
||||
|
||||
def _calculate_camera_position(self, request: RenderRequest) -> tuple:
|
||||
"""
|
||||
根据房间尺寸和视图类型计算摄像机位置
|
||||
|
||||
Returns:
|
||||
(x, y, z) 摄像机位置坐标
|
||||
"""
|
||||
room_length = request.room.length # Y轴(长度)
|
||||
room_width = request.room.width # X轴(宽度)
|
||||
camera_height = request.camera.height # Z轴
|
||||
|
||||
# 判断长宽是否相等
|
||||
if room_length == room_width:
|
||||
# 长宽相等,使用原来的逻辑
|
||||
if request.camera.view_type == 1:
|
||||
# 正视图: x=0, y=-(房间的长度/2-1), z=摄像机垂直高度
|
||||
x = 0
|
||||
y = -(room_length / 2 - 1)
|
||||
z = camera_height
|
||||
else:
|
||||
# 侧视图: x=(房间的宽度/2-1), y=-(房间的长度/2-1), z=摄像机垂直高度
|
||||
x = room_width / 2 - 1
|
||||
y = -(room_length / 2 - 1)
|
||||
z = camera_height
|
||||
else:
|
||||
# 长宽不等,使用新的逻辑
|
||||
if room_length > room_width:
|
||||
# 长比宽大(Y轴比X轴大)
|
||||
if request.camera.view_type == 1:
|
||||
# 正视图: x=0, y=-(房间的长度-(房间的宽度/2)-1), z=摄像机垂直高度
|
||||
x = 0
|
||||
y = -(room_length - (room_width / 2) - 1)
|
||||
z = camera_height
|
||||
else:
|
||||
# 侧视图: x=(房间的宽度/2-1), y=-(房间的长度-(房间的宽度/2)-1), z=摄像机垂直高度
|
||||
x = room_width / 2 - 1
|
||||
y = -(room_length - (room_width / 2) - 1)
|
||||
z = camera_height
|
||||
else:
|
||||
# 宽比长大(X轴比Y轴大)
|
||||
if request.camera.view_type == 1:
|
||||
# 正视图: x=(房间的宽度-房间的长度)/2, y=-(房间的长度/2-1), z=摄像机垂直高度
|
||||
x = (room_width - room_length) / 2
|
||||
y = -(room_length / 2 - 1)
|
||||
z = camera_height
|
||||
else:
|
||||
# 侧视图: x=(房间的宽度-(房间的长度/2)-1), y=-(房间的长度/2-1), z=摄像机垂直高度
|
||||
x = room_width - (room_length / 2) - 1
|
||||
y = -(room_length / 2 - 1)
|
||||
z = camera_height
|
||||
|
||||
logger.info(f"相机位置计算: x={x}, y={y}, z={z}")
|
||||
return (x, y, z)
|
||||
|
||||
def _calculate_camera_rotation(self, request: RenderRequest) -> tuple:
|
||||
"""
|
||||
根据视图类型计算摄像机旋转角度
|
||||
|
||||
Returns:
|
||||
(rx, ry, rz) 摄像机旋转角度(弧度)
|
||||
"""
|
||||
if request.camera.view_type == 1:
|
||||
# 正视图: 90, 0, 0
|
||||
rx = math.radians(90)
|
||||
ry = math.radians(0)
|
||||
rz = math.radians(0)
|
||||
else:
|
||||
# 侧视图: 90, 0, 相机旋转角度
|
||||
rx = math.radians(90)
|
||||
ry = math.radians(0)
|
||||
rz = math.radians(request.camera.rotation_angle)
|
||||
|
||||
return (rx, ry, rz)
|
||||
|
||||
def _generate_blender_script(self, task_id: str, request: RenderRequest) -> str:
|
||||
"""生成Blender Python脚本"""
|
||||
|
||||
# 计算房间参数
|
||||
floor_scale_value, x_extrude_value, y_extrude_value = self._calculate_room_parameters(
|
||||
request)
|
||||
|
||||
# 计算摄像机位置和旋转
|
||||
camera_pos = self._calculate_camera_position(request)
|
||||
camera_rot = self._calculate_camera_rotation(request)
|
||||
|
||||
# 设置输出文件路径
|
||||
output_file = os.path.join(self.output_dir, f"render_{task_id}.png")
|
||||
|
||||
# 渲染引擎映射
|
||||
engine_map = {
|
||||
"workbench": "BLENDER_WORKBENCH",
|
||||
"eevee": "BLENDER_EEVEE_NEXT",
|
||||
"cycles": "CYCLES"
|
||||
}
|
||||
render_engine = engine_map.get(
|
||||
request.render.engine, "BLENDER_WORKBENCH")
|
||||
|
||||
# 计算灯光高度(房间高度减一米)
|
||||
light_height = request.room.height - 1.0
|
||||
|
||||
script = f'''
|
||||
import bpy
|
||||
import bmesh
|
||||
import os
|
||||
import math
|
||||
import time
|
||||
|
||||
def disable_problematic_addons():
|
||||
"""禁用可能导致问题的插件"""
|
||||
try:
|
||||
problematic_addons = ['bl_tool']
|
||||
for addon in problematic_addons:
|
||||
if addon in bpy.context.preferences.addons:
|
||||
bpy.ops.preferences.addon_disable(module=addon)
|
||||
print(f"已禁用插件: {{addon}}")
|
||||
except:
|
||||
pass
|
||||
|
||||
def create_isometric_room():
|
||||
"""创建等轴测房间"""
|
||||
try:
|
||||
disable_problematic_addons()
|
||||
|
||||
# 清除默认立方体
|
||||
if "Cube" in bpy.data.objects:
|
||||
bpy.data.objects.remove(bpy.data.objects["Cube"], do_unlink=True)
|
||||
|
||||
# 设置3D游标位置到原点
|
||||
bpy.context.scene.cursor.location = (0.0, 0.0, 0.0)
|
||||
|
||||
# 检查Isometquick插件
|
||||
if not hasattr(bpy.context.scene, 'iso_tool'):
|
||||
print("错误: Isometquick插件未正确加载")
|
||||
return False
|
||||
|
||||
# 获取Isometquick设置
|
||||
iso_tool = bpy.context.scene.iso_tool
|
||||
|
||||
# 启用需要的组件
|
||||
iso_tool.create_floor = True
|
||||
iso_tool.create_left_wall = True
|
||||
iso_tool.create_right_wall = True
|
||||
iso_tool.create_left_light = True
|
||||
iso_tool.create_right_light = True
|
||||
iso_tool.create_hidden_ceiling = True
|
||||
iso_tool.create_hidden_leftwall = False
|
||||
iso_tool.create_hidden_rightwall = False
|
||||
|
||||
# 找到3D视图区域
|
||||
view3d_area = None
|
||||
for area in bpy.context.screen.areas:
|
||||
if area.type == 'VIEW_3D':
|
||||
view3d_area = area
|
||||
break
|
||||
|
||||
if view3d_area is None:
|
||||
print("错误: 找不到3D视图区域")
|
||||
return False
|
||||
|
||||
# 临时切换到3D视图上下文
|
||||
with bpy.context.temp_override(area=view3d_area):
|
||||
# 使用计算出的房间参数创建房间
|
||||
print(f"房间参数: floor_scale={floor_scale_value}, x_extrude={x_extrude_value}, y_extrude={y_extrude_value}")
|
||||
result = bpy.ops.object.isometric_operator(
|
||||
floor_scale_value={floor_scale_value},
|
||||
wall_height_value={request.room.height},
|
||||
equal_thickness_value=0.15,
|
||||
wall_thickness_value=0.0,
|
||||
floor_thickness_value=0.0,
|
||||
x_extrude_value={x_extrude_value},
|
||||
y_extrude_value={y_extrude_value}
|
||||
)
|
||||
|
||||
if result == {{'FINISHED'}}:
|
||||
print("等轴测房间创建完成!")
|
||||
fix_visibility()
|
||||
return True
|
||||
else:
|
||||
print("房间创建失败")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"创建房间时出现错误: {{str(e)}}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
def fix_visibility():
|
||||
"""修复对象可见性设置"""
|
||||
try:
|
||||
# 设置光源不可见
|
||||
light_objects = ["ISO Emission Left", "ISO Emission Right"]
|
||||
for obj_name in light_objects:
|
||||
if obj_name in bpy.data.objects:
|
||||
obj = bpy.data.objects[obj_name]
|
||||
if hasattr(obj, 'visibility_camera'):
|
||||
obj.visibility_camera = False
|
||||
elif hasattr(obj, 'cycles_visibility'):
|
||||
obj.cycles_visibility.camera = False
|
||||
print(f"已设置 {{obj_name}} 的可见性")
|
||||
|
||||
# 设置天花板可见
|
||||
if "Hidden Ceiling" in bpy.data.objects:
|
||||
ceiling_obj = bpy.data.objects["Hidden Ceiling"]
|
||||
if hasattr(ceiling_obj, 'visibility_camera'):
|
||||
ceiling_obj.visibility_camera = True
|
||||
elif hasattr(ceiling_obj, 'cycles_visibility'):
|
||||
ceiling_obj.cycles_visibility.camera = True
|
||||
ceiling_obj.hide_viewport = False
|
||||
ceiling_obj.hide_render = False
|
||||
print("已设置 Hidden Ceiling 为可见")
|
||||
|
||||
except Exception as e:
|
||||
print(f"设置可见性时出现错误: {{str(e)}}")
|
||||
|
||||
def setup_render_settings():
|
||||
"""设置渲染参数"""
|
||||
scene = bpy.context.scene
|
||||
|
||||
# 设置渲染引擎为EEVEE_NEXT
|
||||
scene.render.engine = 'BLENDER_EEVEE_NEXT'
|
||||
print(f"已设置渲染引擎为: BLENDER_EEVEE_NEXT")
|
||||
|
||||
# 设置EEVEE参数
|
||||
try:
|
||||
if hasattr(scene.eevee, 'taa_render_samples'):
|
||||
scene.eevee.taa_render_samples = 64
|
||||
print(f"已设置EEVEE渲染采样: {{scene.eevee.taa_render_samples}}")
|
||||
elif hasattr(scene.eevee, 'taa_samples'):
|
||||
scene.eevee.taa_samples = 64
|
||||
print(f"已设置EEVEE采样: {{scene.eevee.taa_samples}}")
|
||||
else:
|
||||
print("警告: 无法找到EEVEE采样设置,使用默认值")
|
||||
|
||||
# 启用屏幕空间反射
|
||||
if hasattr(scene.eevee, 'use_ssr'):
|
||||
scene.eevee.use_ssr = True
|
||||
scene.eevee.use_ssr_refraction = True
|
||||
print("已启用屏幕空间反射")
|
||||
|
||||
# 启用环境光遮蔽
|
||||
if hasattr(scene.eevee, 'use_gtao'):
|
||||
scene.eevee.use_gtao = True
|
||||
print("已启用环境光遮蔽")
|
||||
|
||||
# 启用体积雾
|
||||
if hasattr(scene.eevee, 'use_volumetric_lights'):
|
||||
scene.eevee.use_volumetric_lights = True
|
||||
print("已启用体积光照")
|
||||
|
||||
except AttributeError as e:
|
||||
print(f"EEVEE设置警告: {{e}}")
|
||||
print("使用默认EEVEE渲染设置")
|
||||
|
||||
# 设置分辨率
|
||||
scene.render.resolution_x = {request.render.resolution_x}
|
||||
scene.render.resolution_y = {request.render.resolution_y}
|
||||
scene.render.resolution_percentage = 100
|
||||
|
||||
# 设置输出格式
|
||||
scene.render.image_settings.file_format = 'PNG'
|
||||
scene.render.image_settings.color_mode = 'RGBA'
|
||||
|
||||
# 设置输出路径
|
||||
scene.render.filepath = "{output_file}"
|
||||
|
||||
print(f"渲染设置完成,输出路径: {{scene.render.filepath}}")
|
||||
print(f"分辨率: {{scene.render.resolution_x}}x{{scene.render.resolution_y}}")
|
||||
return scene.render.filepath
|
||||
|
||||
def setup_camera_and_lighting():
|
||||
"""设置摄像机和照明(180W点光源,高度为房间高度减一米)"""
|
||||
# 计算灯光高度(房间高度减一米)
|
||||
room_height = {request.room.height}
|
||||
light_height = room_height - 1.0
|
||||
|
||||
# 设置摄像机
|
||||
camera = None
|
||||
if "Camera" in bpy.data.objects:
|
||||
camera = bpy.data.objects["Camera"]
|
||||
else:
|
||||
bpy.ops.object.camera_add(location={camera_pos})
|
||||
camera = bpy.context.active_object
|
||||
camera.name = "API Camera"
|
||||
|
||||
# 设置摄像机位置和旋转
|
||||
camera.location = {camera_pos}
|
||||
camera.rotation_euler = {camera_rot}
|
||||
|
||||
# 设置为透视投影
|
||||
camera.data.type = 'PERSP'
|
||||
camera.data.lens = 35.0
|
||||
camera.data.sensor_width = 35.0
|
||||
camera.data.sensor_fit = 'AUTO'
|
||||
|
||||
# 设置为场景的活动摄像机
|
||||
bpy.context.scene.camera = camera
|
||||
print("摄像机设置完成")
|
||||
|
||||
# 隐藏ISO Emission灯光(如果存在)
|
||||
light_objects = ["ISO Emission Left", "ISO Emission Right"]
|
||||
for obj_name in light_objects:
|
||||
if obj_name in bpy.data.objects:
|
||||
light_obj = bpy.data.objects[obj_name]
|
||||
# 隐藏发光对象
|
||||
light_obj.hide_render = True
|
||||
light_obj.hide_viewport = True
|
||||
print(f"已隐藏 {{obj_name}}")
|
||||
|
||||
# 创建单个180W点光源
|
||||
try:
|
||||
# 计算点光源位置(房间中心上方)
|
||||
room_length = {request.room.length} # Y轴
|
||||
room_width = {request.room.width} # X轴
|
||||
|
||||
# 点光源位置:房间中心上方
|
||||
light_x = 0 # X轴中心
|
||||
light_y = 0 # Y轴中心
|
||||
light_z = light_height
|
||||
|
||||
# 创建点光源
|
||||
bpy.ops.object.light_add(type='POINT', location=(light_x, light_y, light_z))
|
||||
point_light = bpy.context.active_object
|
||||
point_light.name = "Main Point Light"
|
||||
point_light.data.energy = 180 # 180W
|
||||
|
||||
# 设置软衰减
|
||||
point_light.data.falloff_type = 'INVERSE_SQUARE' # 平方反比衰减(物理准确)
|
||||
|
||||
# 设置阴影
|
||||
point_light.data.use_shadow = True # 启用阴影
|
||||
point_light.data.shadow_soft_size = 0.5 # 0.5米半径,产生柔和阴影
|
||||
|
||||
# EEVEE渲染器的阴影设置
|
||||
if hasattr(point_light.data, 'shadow_buffer_size'):
|
||||
point_light.data.shadow_buffer_size = 2048 # 阴影贴图分辨率
|
||||
|
||||
# Cycles渲染器的阴影设置
|
||||
if hasattr(point_light.data, 'cycles'):
|
||||
point_light.data.cycles.cast_shadow = True # Cycles中启用阴影投射
|
||||
point_light.data.cycles.use_multiple_importance_sampling = True # 启用重要性采样
|
||||
|
||||
# 设置衰减距离(可选)
|
||||
# point_light.data.cutoff_distance = 15.0 # 15米后完全衰减
|
||||
# point_light.data.use_custom_distance = True
|
||||
|
||||
print(f"已创建180W点光源(软衰减+阴影)")
|
||||
print(f"点光源位置: x={{light_x}}, y={{light_y}}, z={{light_z}}")
|
||||
print("总照明功率: 180W")
|
||||
print("灯光类型: 点光源(软衰减+阴影)")
|
||||
print(f"衰减类型: 平方反比衰减")
|
||||
print(f"阴影设置: 启用,柔和阴影半径0.5米")
|
||||
print(f"灯光高度: {{light_height}}米 (房间高度减一米)")
|
||||
|
||||
except Exception as e:
|
||||
print(f"创建点光源时出错: {{e}}")
|
||||
print("将使用默认的ISO Emission灯光")
|
||||
|
||||
print("照明设置完成")
|
||||
|
||||
def render_scene():
|
||||
"""渲染场景"""
|
||||
print("开始渲染...")
|
||||
bpy.ops.render.render(write_still=True)
|
||||
print(f"渲染完成!")
|
||||
return bpy.context.scene.render.filepath
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("=" * 60)
|
||||
print("开始API渲染任务")
|
||||
print("=" * 60)
|
||||
|
||||
try:
|
||||
# 1. 创建房间
|
||||
print("1. 创建等轴测房间...")
|
||||
if not create_isometric_room():
|
||||
print("房间创建失败,停止执行")
|
||||
return False
|
||||
|
||||
# 2. 设置渲染参数
|
||||
print("2. 设置渲染参数...")
|
||||
output_path = setup_render_settings()
|
||||
|
||||
# 3. 设置摄像机和照明
|
||||
print("3. 设置摄像机和照明...")
|
||||
setup_camera_and_lighting()
|
||||
|
||||
# 4. 渲染场景
|
||||
print("4. 开始渲染...")
|
||||
final_path = render_scene()
|
||||
|
||||
print("=" * 60)
|
||||
print("API渲染任务完成!")
|
||||
print(f"输出文件: {{final_path}}")
|
||||
print("=" * 60)
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"执行过程中出现错误: {{str(e)}}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
# 执行主函数
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
'''
|
||||
|
||||
return script.strip(), output_file
|
||||
|
||||
async def render_room(self, task_id: str, request: RenderRequest) -> str:
|
||||
"""
|
||||
执行房间渲染
|
||||
|
||||
Args:
|
||||
task_id: 任务ID
|
||||
request: 渲染请求参数
|
||||
|
||||
Returns:
|
||||
输出文件路径
|
||||
"""
|
||||
try:
|
||||
# 生成Blender脚本
|
||||
script_content, output_file = self._generate_blender_script(
|
||||
task_id, request)
|
||||
|
||||
# 创建临时脚本文件
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
|
||||
f.write(script_content)
|
||||
script_path = f.name
|
||||
|
||||
try:
|
||||
# 构建Blender命令
|
||||
cmd = [
|
||||
self.blender_path,
|
||||
"--background",
|
||||
"--disable-crash-handler",
|
||||
"--python", script_path
|
||||
]
|
||||
|
||||
logger.info(f"执行Blender命令: {' '.join(cmd)}")
|
||||
|
||||
# 执行Blender渲染
|
||||
process = await asyncio.create_subprocess_exec(
|
||||
*cmd,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE
|
||||
)
|
||||
|
||||
stdout, stderr = await asyncio.wait_for(
|
||||
process.communicate(),
|
||||
timeout=300 # 5分钟超时
|
||||
)
|
||||
|
||||
# 检查执行结果
|
||||
if process.returncode == 0:
|
||||
if os.path.exists(output_file):
|
||||
logger.info(f"渲染成功: {output_file}")
|
||||
return output_file
|
||||
else:
|
||||
raise Exception("渲染完成但输出文件不存在")
|
||||
else:
|
||||
error_msg = stderr.decode('utf-8') if stderr else "未知错误"
|
||||
raise Exception(
|
||||
f"Blender执行失败 (返回码: {process.returncode}): {error_msg}")
|
||||
|
||||
finally:
|
||||
# 清理临时脚本文件
|
||||
try:
|
||||
os.unlink(script_path)
|
||||
except:
|
||||
pass
|
||||
|
||||
except asyncio.TimeoutError:
|
||||
raise Exception("渲染超时(5分钟)")
|
||||
except Exception as e:
|
||||
logger.error(f"渲染失败: {e}")
|
||||
raise
|
|
@ -0,0 +1,36 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
配置文件
|
||||
"""
|
||||
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class Settings:
|
||||
"""应用设置"""
|
||||
|
||||
# 服务配置
|
||||
HOST: str = "0.0.0.0"
|
||||
PORT: int = 8003
|
||||
DEBUG: bool = False
|
||||
|
||||
# Blender配置
|
||||
BLENDER_PATH: Optional[str] = os.getenv("BLENDER_PATH")
|
||||
BLENDER_TIMEOUT: int = 300 # 5分钟
|
||||
|
||||
# 文件存储配置
|
||||
RENDER_OUTPUT_DIR: str = "/data/Isometquick/"
|
||||
MAX_FILE_AGE_HOURS: int = 24 # 24小时后清理文件
|
||||
|
||||
# 日志配置
|
||||
LOG_LEVEL: str = "INFO"
|
||||
LOG_FORMAT: str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||
|
||||
# 限制配置
|
||||
MAX_CONCURRENT_RENDERS: int = 3
|
||||
MAX_QUEUE_SIZE: int = 10
|
||||
|
||||
|
||||
settings = Settings()
|
|
@ -0,0 +1,363 @@
|
|||
#!/bin/bash
|
||||
# irg.sh - Isometric Room Generator 一键服务管理脚本
|
||||
|
||||
set -e
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
# 打印函数
|
||||
info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
|
||||
warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
|
||||
error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
|
||||
# 脚本目录
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
# 配置变量
|
||||
PID_FILE="$SCRIPT_DIR/irg.pid"
|
||||
LOG_DIR="/data/Isometquick/logs"
|
||||
LOG_FILE="$LOG_DIR/isometquick.log"
|
||||
SERVICE_PORT=8003
|
||||
|
||||
# 创建日志目录
|
||||
create_log_dir() {
|
||||
if [ ! -d "$LOG_DIR" ]; then
|
||||
if sudo mkdir -p "$LOG_DIR" 2>/dev/null && sudo chown $USER:$USER "$LOG_DIR"; then
|
||||
info "日志目录创建成功: $LOG_DIR"
|
||||
else
|
||||
# 如果无法创建/data目录,使用本地目录
|
||||
LOG_DIR="$SCRIPT_DIR/logs"
|
||||
LOG_FILE="$LOG_DIR/isometquick.log"
|
||||
mkdir -p "$LOG_DIR"
|
||||
info "使用本地日志目录: $LOG_DIR"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# 检查服务是否运行
|
||||
is_running() {
|
||||
if [ -f "$PID_FILE" ]; then
|
||||
PID=$(cat "$PID_FILE")
|
||||
if ps -p "$PID" > /dev/null 2>&1; then
|
||||
return 0
|
||||
else
|
||||
rm -f "$PID_FILE"
|
||||
fi
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# 清理占用端口的进程
|
||||
cleanup_port() {
|
||||
info "🧹 清理端口 $SERVICE_PORT..."
|
||||
|
||||
# 查找占用端口的进程
|
||||
PIDS=$(sudo lsof -t -i:$SERVICE_PORT 2>/dev/null || true)
|
||||
|
||||
if [ -n "$PIDS" ]; then
|
||||
info "发现占用端口的进程: $PIDS"
|
||||
for PID in $PIDS; do
|
||||
info "杀死进程: $PID"
|
||||
sudo kill -9 "$PID" 2>/dev/null || true
|
||||
done
|
||||
sleep 1
|
||||
fi
|
||||
|
||||
# 再次检查
|
||||
if sudo lsof -i:$SERVICE_PORT >/dev/null 2>&1; then
|
||||
warning "端口 $SERVICE_PORT 仍被占用"
|
||||
return 1
|
||||
else
|
||||
success "端口 $SERVICE_PORT 已清理"
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
# 安装依赖
|
||||
install() {
|
||||
info "🔧 安装IRG服务依赖..."
|
||||
|
||||
# 检查Python
|
||||
if ! command -v python3 &> /dev/null; then
|
||||
error "未找到Python3,请先安装Python 3.8+"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 创建虚拟环境
|
||||
if [ ! -d "venv" ]; then
|
||||
info "创建虚拟环境..."
|
||||
python3 -m venv venv
|
||||
fi
|
||||
|
||||
# 激活虚拟环境并安装依赖
|
||||
. venv/bin/activate
|
||||
pip install --upgrade pip -q
|
||||
pip install -r requirements.txt -q
|
||||
|
||||
# 创建输出目录
|
||||
if [ ! -d "/data/Isometquick" ]; then
|
||||
if sudo mkdir -p /data/Isometquick 2>/dev/null && sudo chown $USER:$USER /data/Isometquick; then
|
||||
success "输出目录创建成功: /data/Isometquick"
|
||||
else
|
||||
mkdir -p ./renders
|
||||
success "输出目录: ./renders"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 创建日志目录
|
||||
create_log_dir
|
||||
|
||||
success "IRG服务依赖安装完成"
|
||||
}
|
||||
|
||||
# 启动服务
|
||||
start() {
|
||||
if is_running; then
|
||||
PID=$(cat "$PID_FILE")
|
||||
warning "IRG服务已在运行 (PID: $PID)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
info "🚀 启动IRG服务..."
|
||||
|
||||
# 创建日志目录
|
||||
create_log_dir
|
||||
|
||||
# 清理占用的端口
|
||||
cleanup_port || {
|
||||
error "无法清理端口 $SERVICE_PORT,请手动检查"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# 确保虚拟环境存在
|
||||
if [ ! -d "venv" ]; then
|
||||
install
|
||||
fi
|
||||
|
||||
# 启动服务
|
||||
nohup bash -c "
|
||||
cd '$SCRIPT_DIR'
|
||||
. venv/bin/activate
|
||||
python start.py
|
||||
" > "$LOG_FILE" 2>&1 &
|
||||
|
||||
echo $! > "$PID_FILE"
|
||||
sleep 2
|
||||
|
||||
if is_running; then
|
||||
PID=$(cat "$PID_FILE")
|
||||
success "IRG服务启动成功 (PID: $PID)"
|
||||
success "API地址: http://localhost:$SERVICE_PORT"
|
||||
success "API文档: http://localhost:$SERVICE_PORT/docs"
|
||||
success "日志文件: $LOG_FILE"
|
||||
else
|
||||
error "IRG服务启动失败"
|
||||
if [ -f "$LOG_FILE" ]; then
|
||||
echo "错误日志:"
|
||||
tail -10 "$LOG_FILE"
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 停止服务
|
||||
stop() {
|
||||
if ! is_running; then
|
||||
warning "IRG服务未运行"
|
||||
return 0
|
||||
fi
|
||||
|
||||
PID=$(cat "$PID_FILE")
|
||||
info "🛑 停止IRG服务 (PID: $PID)..."
|
||||
|
||||
# 优雅停止
|
||||
kill -TERM "$PID" 2>/dev/null || true
|
||||
|
||||
# 等待5秒
|
||||
for i in {1..5}; do
|
||||
if ! ps -p "$PID" > /dev/null 2>&1; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# 强制停止
|
||||
if ps -p "$PID" > /dev/null 2>&1; then
|
||||
kill -KILL "$PID" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
rm -f "$PID_FILE"
|
||||
|
||||
# 清理可能残留的端口占用
|
||||
cleanup_port
|
||||
|
||||
success "IRG服务已停止"
|
||||
}
|
||||
|
||||
# 重启服务
|
||||
restart() {
|
||||
info "🔄 重启IRG服务..."
|
||||
stop
|
||||
sleep 1
|
||||
start
|
||||
}
|
||||
|
||||
# 查看状态
|
||||
status() {
|
||||
echo "========================================"
|
||||
echo "📊 Isometric Room Generator 服务状态"
|
||||
echo "========================================"
|
||||
|
||||
if is_running; then
|
||||
PID=$(cat "$PID_FILE")
|
||||
MEMORY=$(ps -p "$PID" -o rss= 2>/dev/null | awk '{print int($1/1024)"MB"}' || echo "N/A")
|
||||
CPU=$(ps -p "$PID" -o pcpu= 2>/dev/null | awk '{print $1"%"}' || echo "N/A")
|
||||
|
||||
success "IRG服务正在运行"
|
||||
echo " PID: $PID"
|
||||
echo " 内存: $MEMORY"
|
||||
echo " CPU: $CPU"
|
||||
echo " 端口: $SERVICE_PORT"
|
||||
echo " 日志: $LOG_FILE"
|
||||
|
||||
# 检查端口
|
||||
if netstat -tlnp 2>/dev/null | grep -q ":$SERVICE_PORT"; then
|
||||
success "端口 $SERVICE_PORT 正在监听"
|
||||
else
|
||||
warning "端口 $SERVICE_PORT 未监听"
|
||||
fi
|
||||
|
||||
# 健康检查
|
||||
if command -v curl &> /dev/null; then
|
||||
if curl -s "http://localhost:$SERVICE_PORT/health" > /dev/null 2>&1; then
|
||||
success "API健康检查通过"
|
||||
else
|
||||
warning "API健康检查失败"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
warning "IRG服务未运行"
|
||||
fi
|
||||
}
|
||||
|
||||
# 查看日志
|
||||
logs() {
|
||||
if [ -f "$LOG_FILE" ]; then
|
||||
if [ "$1" = "-f" ]; then
|
||||
info "实时查看IRG服务日志 (Ctrl+C退出)..."
|
||||
tail -f "$LOG_FILE"
|
||||
else
|
||||
info "显示最近20行IRG服务日志..."
|
||||
tail -20 "$LOG_FILE"
|
||||
echo ""
|
||||
info "实时查看: $0 logs -f"
|
||||
fi
|
||||
else
|
||||
warning "日志文件不存在: $LOG_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
# 日志管理
|
||||
log_rotate() {
|
||||
info "🔄 IRG服务日志轮转..."
|
||||
|
||||
if [ -f "$LOG_FILE" ]; then
|
||||
# 获取当前日期
|
||||
DATE=$(date +%Y%m%d_%H%M%S)
|
||||
BACKUP_FILE="$LOG_DIR/isometquick_$DATE.log"
|
||||
|
||||
# 备份当前日志
|
||||
mv "$LOG_FILE" "$BACKUP_FILE"
|
||||
info "日志已备份到: $BACKUP_FILE"
|
||||
|
||||
# 清理7天前的日志
|
||||
find "$LOG_DIR" -name "isometquick_*.log" -mtime +7 -delete 2>/dev/null || true
|
||||
success "IRG服务日志轮转完成"
|
||||
else
|
||||
warning "没有找到日志文件"
|
||||
fi
|
||||
}
|
||||
|
||||
# 显示帮助
|
||||
help() {
|
||||
echo "========================================"
|
||||
echo "🏠 Isometric Room Generator 服务管理脚本"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
echo "用法: $0 [命令]"
|
||||
echo ""
|
||||
echo "命令:"
|
||||
echo " install 安装依赖"
|
||||
echo " start 启动IRG服务"
|
||||
echo " stop 停止IRG服务"
|
||||
echo " restart 重启IRG服务"
|
||||
echo " status 查看服务状态"
|
||||
echo " logs 查看日志"
|
||||
echo " logs -f 实时日志"
|
||||
echo " rotate 日志轮转"
|
||||
echo ""
|
||||
echo "示例:"
|
||||
echo " $0 start # 启动IRG服务"
|
||||
echo " $0 restart # 重启IRG服务"
|
||||
echo " $0 status # 查看服务状态"
|
||||
echo " $0 logs # 查看日志"
|
||||
echo " $0 rotate # 日志轮转"
|
||||
echo ""
|
||||
echo "首次使用请运行: $0 install"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
case "${1:-}" in
|
||||
"install")
|
||||
install
|
||||
;;
|
||||
"start")
|
||||
start
|
||||
;;
|
||||
"stop")
|
||||
stop
|
||||
;;
|
||||
"restart")
|
||||
restart
|
||||
;;
|
||||
"status")
|
||||
status
|
||||
;;
|
||||
"logs")
|
||||
logs "$2"
|
||||
;;
|
||||
"rotate")
|
||||
log_rotate
|
||||
;;
|
||||
"help"|"-h"|"--help")
|
||||
help
|
||||
;;
|
||||
"")
|
||||
# 默认:如果没有参数,显示状态或首次安装启动
|
||||
if [ -d "venv" ]; then
|
||||
status
|
||||
else
|
||||
info "首次使用,开始安装IRG服务..."
|
||||
install
|
||||
start
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
error "未知命令: $1"
|
||||
help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# 执行主函数
|
||||
main "$@"
|
|
@ -0,0 +1,234 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Isometquick Blender渲染微服务
|
||||
基于FastAPI的Blender等轴测房间渲染API服务
|
||||
"""
|
||||
|
||||
from fastapi import FastAPI, HTTPException, BackgroundTasks
|
||||
from fastapi.responses import JSONResponse, FileResponse
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional, Literal
|
||||
import os
|
||||
import asyncio
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
import logging
|
||||
|
||||
from blender_service import BlenderRenderService
|
||||
from models import RenderRequest, RenderResponse, RenderStatus
|
||||
|
||||
# 配置日志
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 创建FastAPI应用
|
||||
app = FastAPI(
|
||||
title="Isometquick Blender渲染服务",
|
||||
description="基于Blender的等轴测房间渲染API微服务",
|
||||
version="1.0.0",
|
||||
docs_url="/docs",
|
||||
redoc_url="/redoc"
|
||||
)
|
||||
|
||||
# 创建Blender服务实例
|
||||
blender_service = BlenderRenderService()
|
||||
|
||||
# 渲染任务状态存储(生产环境应使用Redis等)
|
||||
render_tasks = {}
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
"""健康检查接口"""
|
||||
return {
|
||||
"service": "Isometquick Blender渲染服务",
|
||||
"status": "running",
|
||||
"version": "1.0.0",
|
||||
"timestamp": datetime.now().isoformat()
|
||||
}
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
async def health_check():
|
||||
"""服务健康检查"""
|
||||
try:
|
||||
# 检查Blender是否可用
|
||||
blender_available = blender_service.check_blender_available()
|
||||
return {
|
||||
"status": "healthy" if blender_available else "unhealthy",
|
||||
"blender_available": blender_available,
|
||||
"timestamp": datetime.now().isoformat()
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"健康检查失败: {e}")
|
||||
return JSONResponse(
|
||||
status_code=500,
|
||||
content={"status": "unhealthy", "error": str(e)}
|
||||
)
|
||||
|
||||
|
||||
@app.post("/render", response_model=RenderResponse)
|
||||
async def create_render_task(
|
||||
request: RenderRequest,
|
||||
background_tasks: BackgroundTasks
|
||||
):
|
||||
"""
|
||||
创建渲染任务
|
||||
"""
|
||||
try:
|
||||
# 生成任务ID
|
||||
task_id = str(uuid.uuid4())
|
||||
|
||||
# 记录任务状态
|
||||
render_tasks[task_id] = {
|
||||
"status": "pending",
|
||||
"created_at": datetime.now().isoformat(),
|
||||
"request": request.dict(),
|
||||
"output_file": None,
|
||||
"error": None
|
||||
}
|
||||
|
||||
# 添加后台渲染任务
|
||||
background_tasks.add_task(
|
||||
execute_render_task,
|
||||
task_id,
|
||||
request
|
||||
)
|
||||
|
||||
logger.info(f"创建渲染任务: {task_id}")
|
||||
|
||||
return RenderResponse(
|
||||
task_id=task_id,
|
||||
status="pending",
|
||||
message="渲染任务已创建,正在处理中"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"创建渲染任务失败: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.get("/render/{task_id}", response_model=RenderStatus)
|
||||
async def get_render_status(task_id: str):
|
||||
"""
|
||||
获取渲染任务状态
|
||||
"""
|
||||
if task_id not in render_tasks:
|
||||
raise HTTPException(status_code=404, detail="任务不存在")
|
||||
|
||||
task_info = render_tasks[task_id]
|
||||
|
||||
return RenderStatus(
|
||||
task_id=task_id,
|
||||
status=task_info["status"],
|
||||
created_at=task_info["created_at"],
|
||||
output_file=task_info.get("output_file"),
|
||||
error=task_info.get("error")
|
||||
)
|
||||
|
||||
|
||||
@app.get("/render/{task_id}/download")
|
||||
async def download_render_result(task_id: str):
|
||||
"""
|
||||
下载渲染结果
|
||||
"""
|
||||
if task_id not in render_tasks:
|
||||
raise HTTPException(status_code=404, detail="任务不存在")
|
||||
|
||||
task_info = render_tasks[task_id]
|
||||
|
||||
if task_info["status"] != "completed":
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"任务状态: {task_info['status']}, 无法下载"
|
||||
)
|
||||
|
||||
output_file = task_info.get("output_file")
|
||||
if not output_file or not os.path.exists(output_file):
|
||||
raise HTTPException(status_code=404, detail="渲染文件不存在")
|
||||
|
||||
return FileResponse(
|
||||
path=output_file,
|
||||
filename=f"render_{task_id}.png",
|
||||
media_type="image/png"
|
||||
)
|
||||
|
||||
|
||||
@app.delete("/render/{task_id}")
|
||||
async def delete_render_task(task_id: str):
|
||||
"""
|
||||
删除渲染任务和相关文件
|
||||
"""
|
||||
if task_id not in render_tasks:
|
||||
raise HTTPException(status_code=404, detail="任务不存在")
|
||||
|
||||
task_info = render_tasks[task_id]
|
||||
|
||||
# 删除输出文件
|
||||
output_file = task_info.get("output_file")
|
||||
if output_file and os.path.exists(output_file):
|
||||
try:
|
||||
os.remove(output_file)
|
||||
logger.info(f"删除文件: {output_file}")
|
||||
except Exception as e:
|
||||
logger.warning(f"删除文件失败: {e}")
|
||||
|
||||
# 删除任务记录
|
||||
del render_tasks[task_id]
|
||||
|
||||
return {"message": f"任务 {task_id} 已删除"}
|
||||
|
||||
|
||||
@app.get("/tasks")
|
||||
async def list_render_tasks():
|
||||
"""
|
||||
列出所有渲染任务
|
||||
"""
|
||||
return {
|
||||
"total": len(render_tasks),
|
||||
"tasks": [
|
||||
{
|
||||
"task_id": task_id,
|
||||
"status": task_info["status"],
|
||||
"created_at": task_info["created_at"]
|
||||
}
|
||||
for task_id, task_info in render_tasks.items()
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
async def execute_render_task(task_id: str, request: RenderRequest):
|
||||
"""
|
||||
执行渲染任务(后台任务)
|
||||
"""
|
||||
try:
|
||||
# 更新任务状态
|
||||
render_tasks[task_id]["status"] = "processing"
|
||||
logger.info(f"开始处理渲染任务: {task_id}")
|
||||
|
||||
# 调用Blender服务执行渲染
|
||||
output_file = await blender_service.render_room(task_id, request)
|
||||
|
||||
# 更新任务状态
|
||||
render_tasks[task_id].update({
|
||||
"status": "completed",
|
||||
"output_file": output_file,
|
||||
"completed_at": datetime.now().isoformat()
|
||||
})
|
||||
|
||||
logger.info(f"渲染任务完成: {task_id}, 输出文件: {output_file}")
|
||||
|
||||
except Exception as e:
|
||||
# 更新错误状态
|
||||
render_tasks[task_id].update({
|
||||
"status": "failed",
|
||||
"error": str(e),
|
||||
"failed_at": datetime.now().isoformat()
|
||||
})
|
||||
|
||||
logger.error(f"渲染任务失败: {task_id}, 错误: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
uvicorn.run(app, host="0.0.0.0", port=8003)
|
|
@ -0,0 +1,101 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Isometquick API数据模型
|
||||
定义请求和响应的数据结构
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional, Literal
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class RoomDimensions(BaseModel):
|
||||
"""房间尺寸参数"""
|
||||
length: float = Field(default=4.0, ge=1.0, le=20.0, description="房间长度(X轴)")
|
||||
width: float = Field(default=4.0, ge=1.0, le=20.0, description="房间宽度(Y轴)")
|
||||
height: float = Field(default=3.0, ge=1.0, le=10.0, description="房间高度(Z轴)")
|
||||
prop_type: int = Field(default=0, ge=0, le=3,
|
||||
description="道具类型: 0=无, 1=窗, 2=拱门, 3=门")
|
||||
|
||||
|
||||
class CameraSettings(BaseModel):
|
||||
"""摄像机设置参数"""
|
||||
height: float = Field(default=1.3, ge=0.5, le=5.0,
|
||||
description="摄像机垂直高度(米)")
|
||||
view_type: Literal[1, 2] = Field(
|
||||
default=2, description="视图类型: 1=正视图, 2=侧视图")
|
||||
rotation_angle: float = Field(
|
||||
default=45.0, ge=0.0, le=360.0, description="摄像机旋转角度(度)")
|
||||
|
||||
|
||||
class RenderSettings(BaseModel):
|
||||
"""渲染设置参数"""
|
||||
resolution_x: int = Field(default=1080, ge=256,
|
||||
le=4096, description="渲染宽度")
|
||||
resolution_y: int = Field(default=2400, ge=256,
|
||||
le=4096, description="渲染高度")
|
||||
engine: Literal["workbench", "eevee", "cycles"] = Field(
|
||||
default="workbench", description="渲染引擎")
|
||||
|
||||
|
||||
class RenderRequest(BaseModel):
|
||||
"""渲染请求模型"""
|
||||
room: RoomDimensions = Field(
|
||||
default_factory=RoomDimensions, description="房间尺寸参数")
|
||||
camera: CameraSettings = Field(
|
||||
default_factory=CameraSettings, description="摄像机设置")
|
||||
render: RenderSettings = Field(
|
||||
default_factory=RenderSettings, description="渲染设置")
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"room": {
|
||||
"length": 4.0,
|
||||
"width": 4.0,
|
||||
"height": 3.0,
|
||||
"prop_type": 0
|
||||
},
|
||||
"camera": {
|
||||
"height": 1.3,
|
||||
"view_type": 2,
|
||||
"rotation_angle": 45.0
|
||||
},
|
||||
"render": {
|
||||
"resolution_x": 1080,
|
||||
"resolution_y": 2400,
|
||||
"engine": "workbench"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class RenderResponse(BaseModel):
|
||||
"""渲染响应模型"""
|
||||
task_id: str = Field(description="任务唯一标识符")
|
||||
status: str = Field(description="任务状态")
|
||||
message: str = Field(description="响应消息")
|
||||
created_at: Optional[datetime] = Field(default=None, description="创建时间")
|
||||
|
||||
|
||||
class RenderStatus(BaseModel):
|
||||
"""渲染状态模型"""
|
||||
task_id: str = Field(description="任务唯一标识符")
|
||||
status: str = Field(
|
||||
description="任务状态: pending/processing/completed/failed")
|
||||
created_at: str = Field(description="创建时间")
|
||||
completed_at: Optional[str] = Field(default=None, description="完成时间")
|
||||
output_file: Optional[str] = Field(default=None, description="输出文件路径")
|
||||
image_base64: Optional[str] = Field(
|
||||
default=None, description="图片的base64编码")
|
||||
error: Optional[str] = Field(default=None, description="错误信息")
|
||||
|
||||
|
||||
class ApiResponse(BaseModel):
|
||||
"""通用API响应模型"""
|
||||
success: bool = Field(description="操作是否成功")
|
||||
message: str = Field(description="响应消息")
|
||||
data: Optional[dict] = Field(default=None, description="响应数据")
|
||||
timestamp: datetime = Field(
|
||||
default_factory=datetime.now, description="响应时间")
|
Binary file not shown.
After Width: | Height: | Size: 2.3 MiB |
Binary file not shown.
After Width: | Height: | Size: 2.3 MiB |
|
@ -0,0 +1,6 @@
|
|||
fastapi==0.104.1
|
||||
uvicorn==0.24.0
|
||||
pydantic==2.5.0
|
||||
python-multipart==0.0.6
|
||||
aiofiles==23.2.1
|
||||
requests==2.31.0
|
|
@ -0,0 +1,39 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
服务启动脚本
|
||||
"""
|
||||
|
||||
import uvicorn
|
||||
import logging
|
||||
from config import settings
|
||||
|
||||
|
||||
def setup_logging():
|
||||
"""设置日志"""
|
||||
logging.basicConfig(
|
||||
level=getattr(logging, settings.LOG_LEVEL),
|
||||
format=settings.LOG_FORMAT
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
"""启动服务"""
|
||||
setup_logging()
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.info("正在启动Isometquick Blender渲染服务...")
|
||||
logger.info(f"服务地址: http://{settings.HOST}:{settings.PORT}")
|
||||
logger.info(f"API文档: http://{settings.HOST}:{settings.PORT}/docs")
|
||||
|
||||
uvicorn.run(
|
||||
"main:app",
|
||||
host=settings.HOST,
|
||||
port=settings.PORT,
|
||||
reload=settings.DEBUG,
|
||||
log_level=settings.LOG_LEVEL.lower()
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,341 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
API测试客户端
|
||||
用于测试Isometric Room Generator渲染服务
|
||||
"""
|
||||
|
||||
import requests
|
||||
import time
|
||||
import json
|
||||
import sys
|
||||
|
||||
|
||||
class IRGClient:
|
||||
"""Isometric Room Generator API客户端"""
|
||||
|
||||
def __init__(self, base_url: str = "http://localhost:8003"):
|
||||
self.base_url = base_url.rstrip('/')
|
||||
|
||||
def health_check(self):
|
||||
"""健康检查"""
|
||||
try:
|
||||
response = requests.get(f"{self.base_url}/health", timeout=10)
|
||||
return response.json()
|
||||
except Exception as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
def create_render_task(self, request_data: dict):
|
||||
"""创建渲染任务"""
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{self.base_url}/render",
|
||||
json=request_data,
|
||||
timeout=30
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except Exception as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
def get_task_status(self, task_id: str):
|
||||
"""获取任务状态"""
|
||||
try:
|
||||
response = requests.get(
|
||||
f"{self.base_url}/render/{task_id}", timeout=10)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except Exception as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
def download_result(self, task_id: str, output_file: str):
|
||||
"""下载渲染结果"""
|
||||
try:
|
||||
response = requests.get(
|
||||
f"{self.base_url}/render/{task_id}/download",
|
||||
timeout=60
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
with open(output_file, 'wb') as f:
|
||||
f.write(response.content)
|
||||
return {"success": True, "file": output_file}
|
||||
except Exception as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
def wait_for_completion(self, task_id: str, max_wait: int = 300):
|
||||
"""等待任务完成"""
|
||||
start_time = time.time()
|
||||
|
||||
while time.time() - start_time < max_wait:
|
||||
status_result = self.get_task_status(task_id)
|
||||
|
||||
if "error" in status_result:
|
||||
return status_result
|
||||
|
||||
status = status_result.get("status")
|
||||
print(f"任务状态: {status}")
|
||||
|
||||
if status == "completed":
|
||||
return status_result
|
||||
elif status == "failed":
|
||||
return status_result
|
||||
|
||||
time.sleep(2)
|
||||
|
||||
return {"error": "任务超时"}
|
||||
|
||||
|
||||
def test_basic_render():
|
||||
"""测试基本渲染功能"""
|
||||
print("=" * 60)
|
||||
print("测试基本渲染功能")
|
||||
print("=" * 60)
|
||||
|
||||
client = IRGClient()
|
||||
|
||||
# 1. 健康检查
|
||||
print("1. 健康检查...")
|
||||
health = client.health_check()
|
||||
print(f"健康状态: {json.dumps(health, indent=2, ensure_ascii=False)}")
|
||||
|
||||
if not health.get("blender_available"):
|
||||
print("❌ Blender不可用,请检查安装")
|
||||
return
|
||||
|
||||
# 2. 创建渲染任务
|
||||
print("\n2. 创建渲染任务...")
|
||||
request_data = {
|
||||
"room": {
|
||||
"length": 7.0,
|
||||
"width": 4.0,
|
||||
"height": 3.0,
|
||||
"prop_type": 0 # 0=无, 1=落地窗窗, 2=拱门, 3=门"
|
||||
},
|
||||
"camera": {
|
||||
"height": 1.3,
|
||||
"view_type": 2, #正视图:1, 侧视图:2
|
||||
"rotation_angle": 45.0
|
||||
},
|
||||
"render": {
|
||||
"resolution_x": 1080,
|
||||
"resolution_y": 2400,
|
||||
"engine": "eevee" # 固定为EEVEE
|
||||
}
|
||||
}
|
||||
|
||||
create_result = client.create_render_task(request_data)
|
||||
print(f"创建结果: {json.dumps(create_result, indent=2, ensure_ascii=False)}")
|
||||
|
||||
if "error" in create_result:
|
||||
print("❌ 任务创建失败")
|
||||
return
|
||||
|
||||
task_id = create_result.get("task_id")
|
||||
if not task_id:
|
||||
print("❌ 未获取到任务ID")
|
||||
return
|
||||
|
||||
# 3. 等待任务完成
|
||||
print(f"\n3. 等待任务完成 (ID: {task_id})...")
|
||||
completion_result = client.wait_for_completion(task_id)
|
||||
|
||||
if "error" in completion_result:
|
||||
print(f"❌ 任务失败: {completion_result['error']}")
|
||||
return
|
||||
|
||||
if completion_result.get("status") == "completed":
|
||||
print("✅ 任务完成!")
|
||||
|
||||
# 4. 下载结果
|
||||
print("\n4. 下载渲染结果...")
|
||||
output_file = f"test_render_{task_id}.png"
|
||||
download_result = client.download_result(task_id, output_file)
|
||||
|
||||
if "error" in download_result:
|
||||
print(f"❌ 下载失败: {download_result['error']}")
|
||||
else:
|
||||
print(f"✅ 渲染结果已保存到: {output_file}")
|
||||
else:
|
||||
print(f"❌ 任务失败: {completion_result.get('error')}")
|
||||
|
||||
|
||||
def test_different_views():
|
||||
"""测试不同视图"""
|
||||
print("=" * 60)
|
||||
print("测试不同视图")
|
||||
print("=" * 60)
|
||||
|
||||
client = IRGClient()
|
||||
|
||||
# 测试正视图和侧视图
|
||||
test_cases = [
|
||||
{
|
||||
"name": "正视图",
|
||||
"data": {
|
||||
"room": {"length": 4.0, "width": 4.0, "height": 3.0, "prop_type": 0},
|
||||
"camera": {"height": 1.3, "view_type": 1, "rotation_angle": 0.0},
|
||||
"render": {"resolution_x": 800, "resolution_y": 600, "engine": "eevee"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "侧视图-45度",
|
||||
"data": {
|
||||
"room": {"length": 4.0, "width": 4.0, "height": 3.0, "prop_type": 0},
|
||||
"camera": {"height": 1.3, "view_type": 2, "rotation_angle": 45.0},
|
||||
"render": {"resolution_x": 800, "resolution_y": 600, "engine": "eevee"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "侧视图-30度",
|
||||
"data": {
|
||||
"room": {"length": 4.0, "width": 4.0, "height": 3.0, "prop_type": 0},
|
||||
"camera": {"height": 1.3, "view_type": 2, "rotation_angle": 30.0},
|
||||
"render": {"resolution_x": 800, "resolution_y": 600, "engine": "eevee"}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
for i, test_case in enumerate(test_cases, 1):
|
||||
print(f"\n{i}. 测试{test_case['name']}...")
|
||||
|
||||
create_result = client.create_render_task(test_case['data'])
|
||||
if "error" in create_result:
|
||||
print(f"❌ 创建失败: {create_result['error']}")
|
||||
continue
|
||||
|
||||
task_id = create_result.get("task_id")
|
||||
print(f"任务ID: {task_id}")
|
||||
|
||||
# 快速检查(不等待完成)
|
||||
time.sleep(1)
|
||||
status = client.get_task_status(task_id)
|
||||
print(f"当前状态: {status.get('status', 'unknown')}")
|
||||
|
||||
|
||||
def test_prop_types():
|
||||
"""测试不同道具类型"""
|
||||
print("=" * 60)
|
||||
print("测试不同道具类型")
|
||||
print("=" * 60)
|
||||
|
||||
client = IRGClient()
|
||||
|
||||
# 测试不同道具类型
|
||||
test_cases = [
|
||||
{
|
||||
"name": "无道具",
|
||||
"data": {
|
||||
"room": {"length": 5.0, "width": 4.0, "height": 3.0, "prop_type": 0},
|
||||
"camera": {"height": 1.3, "view_type": 2, "rotation_angle": 45.0},
|
||||
"render": {"resolution_x": 800, "resolution_y": 600, "engine": "eevee"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "落地窗",
|
||||
"data": {
|
||||
"room": {"length": 5.0, "width": 4.0, "height": 3.0, "prop_type": 1},
|
||||
"camera": {"height": 1.3, "view_type": 2, "rotation_angle": 45.0},
|
||||
"render": {"resolution_x": 800, "resolution_y": 600, "engine": "eevee"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "拱门",
|
||||
"data": {
|
||||
"room": {"length": 5.0, "width": 4.0, "height": 3.0, "prop_type": 2},
|
||||
"camera": {"height": 1.3, "view_type": 2, "rotation_angle": 45.0},
|
||||
"render": {"resolution_x": 800, "resolution_y": 600, "engine": "eevee"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "门",
|
||||
"data": {
|
||||
"room": {"length": 5.0, "width": 4.0, "height": 3.0, "prop_type": 3},
|
||||
"camera": {"height": 1.3, "view_type": 2, "rotation_angle": 45.0},
|
||||
"render": {"resolution_x": 800, "resolution_y": 600, "engine": "eevee"}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
for i, test_case in enumerate(test_cases, 1):
|
||||
print(f"\n{i}. 测试{test_case['name']}...")
|
||||
|
||||
create_result = client.create_render_task(test_case['data'])
|
||||
if "error" in create_result:
|
||||
print(f"❌ 创建失败: {create_result['error']}")
|
||||
continue
|
||||
|
||||
task_id = create_result.get("task_id")
|
||||
print(f"任务ID: {task_id}")
|
||||
|
||||
# 快速检查(不等待完成)
|
||||
time.sleep(1)
|
||||
status = client.get_task_status(task_id)
|
||||
print(f"当前状态: {status.get('status', 'unknown')}")
|
||||
|
||||
|
||||
def test_room_dimensions():
|
||||
"""测试房间尺寸调整逻辑"""
|
||||
print("=" * 60)
|
||||
print("测试房间尺寸调整逻辑")
|
||||
print("=" * 60)
|
||||
|
||||
client = IRGClient()
|
||||
|
||||
# 测试房间尺寸调整(width > length 的情况)
|
||||
test_cases = [
|
||||
{
|
||||
"name": "正常尺寸 (length >= width)",
|
||||
"data": {
|
||||
"room": {"length": 6.0, "width": 4.0, "height": 3.0, "prop_type": 0},
|
||||
"camera": {"height": 1.3, "view_type": 2, "rotation_angle": 45.0},
|
||||
"render": {"resolution_x": 800, "resolution_y": 600, "engine": "eevee"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "需要调整 (width > length)",
|
||||
"data": {
|
||||
"room": {"length": 4.0, "width": 6.0, "height": 3.0, "prop_type": 0},
|
||||
"camera": {"height": 1.3, "view_type": 2, "rotation_angle": 45.0},
|
||||
"render": {"resolution_x": 800, "resolution_y": 600, "engine": "eevee"}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
for i, test_case in enumerate(test_cases, 1):
|
||||
print(f"\n{i}. 测试{test_case['name']}...")
|
||||
|
||||
create_result = client.create_render_task(test_case['data'])
|
||||
if "error" in create_result:
|
||||
print(f"❌ 创建失败: {create_result['error']}")
|
||||
continue
|
||||
|
||||
task_id = create_result.get("task_id")
|
||||
print(f"任务ID: {task_id}")
|
||||
|
||||
# 快速检查(不等待完成)
|
||||
time.sleep(1)
|
||||
status = client.get_task_status(task_id)
|
||||
print(f"当前状态: {status.get('status', 'unknown')}")
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
if len(sys.argv) > 1:
|
||||
if sys.argv[1] == "views":
|
||||
test_different_views()
|
||||
elif sys.argv[1] == "props":
|
||||
test_prop_types()
|
||||
elif sys.argv[1] == "dimensions":
|
||||
test_room_dimensions()
|
||||
else:
|
||||
print("用法: python test_client.py [views|props|dimensions]")
|
||||
print(" views - 测试不同视图")
|
||||
print(" props - 测试不同道具类型")
|
||||
print(" dimensions - 测试房间尺寸调整")
|
||||
else:
|
||||
test_basic_render()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,253 @@
|
|||
# Isometric Room Generator (IRG) 服务管理 - 使用说明
|
||||
|
||||
## 🚀 一键脚本 - irg.sh
|
||||
|
||||
这是一个简化的一键服务管理脚本,整合了所有功能。
|
||||
|
||||
### 基本用法
|
||||
|
||||
```bash
|
||||
# 进入项目目录
|
||||
cd Isometquick-server
|
||||
|
||||
# 添加执行权限(Linux/Mac)
|
||||
chmod +x irg.sh
|
||||
|
||||
# 首次使用(自动安装并启动)
|
||||
./irg.sh
|
||||
|
||||
# 或者手动安装
|
||||
./irg.sh install
|
||||
```
|
||||
|
||||
### 服务管理命令
|
||||
|
||||
```bash
|
||||
# 启动服务
|
||||
./irg.sh start
|
||||
|
||||
# 停止服务
|
||||
./irg.sh stop
|
||||
|
||||
# 重启服务
|
||||
./irg.sh restart
|
||||
|
||||
# 查看状态
|
||||
./irg.sh status
|
||||
|
||||
# 查看日志
|
||||
./irg.sh logs
|
||||
|
||||
# 实时查看日志
|
||||
./irg.sh logs -f
|
||||
|
||||
# 日志轮转
|
||||
./irg.sh rotate
|
||||
|
||||
# 显示帮助
|
||||
./irg.sh help
|
||||
```
|
||||
|
||||
### 服务状态说明
|
||||
|
||||
**正常运行时会显示:**
|
||||
- ✅ IRG服务正在运行
|
||||
- ✅ 端口 8003 正在监听
|
||||
- ✅ API健康检查通过
|
||||
- PID、内存、CPU使用情况
|
||||
|
||||
**服务地址:**
|
||||
- API地址: http://localhost:8003
|
||||
- API文档: http://localhost:8003/docs
|
||||
- 健康检查: http://localhost:8003/health
|
||||
|
||||
### 文件说明
|
||||
|
||||
- `irg.pid` - 进程ID文件
|
||||
- `/data/Isometquick/logs/isometquick.log` - 服务日志文件
|
||||
- `venv/` - Python虚拟环境目录
|
||||
- `/data/Isometquick/` - 渲染输出目录
|
||||
|
||||
### 故障排除
|
||||
|
||||
1. **服务启动失败**
|
||||
```bash
|
||||
./irg.sh logs
|
||||
```
|
||||
查看错误日志
|
||||
|
||||
2. **端口被占用**
|
||||
```bash
|
||||
netstat -tlnp | grep 8003
|
||||
```
|
||||
检查端口占用情况
|
||||
|
||||
3. **虚拟环境问题**
|
||||
```bash
|
||||
rm -rf venv
|
||||
./irg.sh install
|
||||
```
|
||||
重新创建虚拟环境
|
||||
|
||||
4. **权限问题**
|
||||
```bash
|
||||
sudo chown -R $USER:$USER /data/Isometquick/
|
||||
```
|
||||
修复输出目录权限
|
||||
|
||||
### 完整的部署流程
|
||||
|
||||
```bash
|
||||
# 1. 进入项目目录
|
||||
cd Isometquick-server
|
||||
|
||||
# 2. 首次部署(一键完成)
|
||||
./irg.sh
|
||||
|
||||
# 3. 检查状态
|
||||
./irg.sh status
|
||||
|
||||
# 4. 测试API
|
||||
curl http://localhost:8003/health
|
||||
```
|
||||
|
||||
### 日常维护
|
||||
|
||||
```bash
|
||||
# 重启服务
|
||||
./irg.sh restart
|
||||
|
||||
# 查看运行状态
|
||||
./irg.sh status
|
||||
|
||||
# 查看最新日志
|
||||
./irg.sh logs
|
||||
|
||||
# 停止服务
|
||||
./irg.sh stop
|
||||
|
||||
# 日志轮转(清理旧日志)
|
||||
./irg.sh rotate
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 测试客户端
|
||||
|
||||
使用 `test_client.py` 测试API功能:
|
||||
|
||||
```bash
|
||||
# 基本渲染测试
|
||||
python test_client.py
|
||||
|
||||
# 测试不同视图
|
||||
python test_client.py views
|
||||
|
||||
# 测试不同道具类型
|
||||
python test_client.py props
|
||||
|
||||
# 测试房间尺寸调整
|
||||
python test_client.py dimensions
|
||||
```
|
||||
|
||||
### 道具类型说明
|
||||
|
||||
- `prop_type: 0` - 无道具
|
||||
- `prop_type: 1` - 落地窗(使用archimesh插件)
|
||||
- `prop_type: 2` - 拱门(使用isometric_room_gen插件)
|
||||
- `prop_type: 3` - 门(使用archimesh插件)
|
||||
|
||||
### 视图类型说明
|
||||
|
||||
- `view_type: 1` - 正视图(相机位置:x=width/2, y=1, z=height)
|
||||
- `view_type: 2` - 侧视图(相机位置:x=width-1, y=1, z=height)
|
||||
|
||||
---
|
||||
|
||||
## 🔍 进程管理
|
||||
|
||||
### 查看进程
|
||||
|
||||
```bash
|
||||
# 查看服务进程
|
||||
ps aux | grep -i irg
|
||||
|
||||
# 查看Python进程
|
||||
ps aux | grep python | grep start.py
|
||||
|
||||
# 查看端口占用
|
||||
netstat -tlnp | grep 8003
|
||||
```
|
||||
|
||||
### 手动停止进程
|
||||
|
||||
```bash
|
||||
# 通过PID文件停止
|
||||
if [ -f "irg.pid" ]; then
|
||||
kill $(cat irg.pid)
|
||||
rm irg.pid
|
||||
fi
|
||||
|
||||
# 强制停止所有相关进程
|
||||
pkill -f "python.*start.py"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 💡 使用建议
|
||||
|
||||
1. **推荐使用 `irg.sh`** - 功能完整且简单易用
|
||||
2. **首次使用直接运行** `./irg.sh` 会自动安装并启动
|
||||
3. **定期重启服务** 保持最佳性能
|
||||
4. **监控日志文件** 及时发现问题
|
||||
5. **备份配置文件** 避免意外丢失
|
||||
6. **使用道具功能** 创建更丰富的房间场景
|
||||
|
||||
---
|
||||
|
||||
## 🎯 快速上手
|
||||
|
||||
```bash
|
||||
# 一键启动(推荐)
|
||||
cd Isometquick-server && ./irg.sh
|
||||
|
||||
# 检查状态
|
||||
./irg.sh status
|
||||
|
||||
# 测试API
|
||||
python test_client.py
|
||||
|
||||
# 访问API文档
|
||||
# 浏览器打开: http://localhost:8003/docs
|
||||
```
|
||||
|
||||
### 示例API请求
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
# 创建渲染任务
|
||||
request_data = {
|
||||
"room": {
|
||||
"length": 7.0, # 房间长度(Y轴)
|
||||
"width": 4.0, # 房间宽度(X轴)
|
||||
"height": 3.0, # 房间高度(Z轴)
|
||||
"prop_type": 1 # 1=落地窗
|
||||
},
|
||||
"camera": {
|
||||
"height": 1.3, # 相机高度
|
||||
"view_type": 2, # 2=侧视图
|
||||
"rotation_angle": 45.0
|
||||
},
|
||||
"render": {
|
||||
"resolution_x": 1080,
|
||||
"resolution_y": 2400,
|
||||
"engine": "eevee" # 固定为EEVEE
|
||||
}
|
||||
}
|
||||
|
||||
response = requests.post("http://localhost:8003/render", json=request_data)
|
||||
print(response.json())
|
||||
```
|
||||
|
||||
就这么简单!🎉
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
@ -0,0 +1,770 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Blender 4.2兼容版自动化脚本(白模版本)
|
||||
自动创建等轴测房间并渲染白模
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Blender可执行文件路径
|
||||
BLENDER_PATH = r"D:\Program Files\Blender Foundation\blender-4.2.11-windows-x64\blender.exe"
|
||||
|
||||
# 简化版Blender脚本(只渲染白模)
|
||||
BLENDER_SIMPLE_SCRIPT = '''
|
||||
import bpy
|
||||
import bmesh
|
||||
import os
|
||||
import math
|
||||
import time
|
||||
|
||||
def disable_problematic_addons():
|
||||
"""禁用可能导致问题的插件"""
|
||||
try:
|
||||
# 禁用可能冲突的插件
|
||||
problematic_addons = ['bl_tool']
|
||||
for addon in problematic_addons:
|
||||
if addon in bpy.context.preferences.addons:
|
||||
bpy.ops.preferences.addon_disable(module=addon)
|
||||
print(f"已禁用插件: {addon}")
|
||||
except:
|
||||
pass
|
||||
|
||||
def create_isometric_room():
|
||||
"""创建等轴测房间,使用isometric_room_gen插件"""
|
||||
try:
|
||||
# 禁用问题插件
|
||||
#disable_problematic_addons()
|
||||
|
||||
# 清除默认立方体
|
||||
if "Cube" in bpy.data.objects:
|
||||
bpy.data.objects.remove(bpy.data.objects["Cube"], do_unlink=True)
|
||||
|
||||
# 设置3D游标位置到原点
|
||||
bpy.context.scene.cursor.location = (0.0, 0.0, 0.0)
|
||||
|
||||
# 检查isometric_room_gen插件
|
||||
if not hasattr(bpy.context.scene, 'sna_room_width'):
|
||||
print("错误: isometric_room_gen插件未正确加载")
|
||||
return False
|
||||
|
||||
# 设置isometric_room_gen插件参数
|
||||
# 房间设置
|
||||
bpy.context.scene.sna_wall_thickness = 0.10 # 墙体厚度10cm
|
||||
bpy.context.scene.sna_room_width = 5.0 # 房间宽度8米
|
||||
bpy.context.scene.sna_room_depth = 8.0 # 房间深度4米
|
||||
bpy.context.scene.sna_room_height = 3.0 # 房间高度3米
|
||||
|
||||
# 窗户设置 - 不设置窗户
|
||||
bpy.context.scene.sna_windows_enum = 'NONE' # 无窗户
|
||||
|
||||
# 设置房间类型为方形
|
||||
bpy.context.scene.sna_style = 'Square'
|
||||
|
||||
# 找到3D视图区域
|
||||
view3d_area = None
|
||||
for area in bpy.context.screen.areas:
|
||||
if area.type == 'VIEW_3D':
|
||||
view3d_area = area
|
||||
break
|
||||
|
||||
if view3d_area is None:
|
||||
print("错误: 找不到3D视图区域")
|
||||
return False
|
||||
|
||||
# 临时切换到3D视图上下文
|
||||
with bpy.context.temp_override(area=view3d_area):
|
||||
# 使用isometric_room_gen插件创建房间
|
||||
result = bpy.ops.sna.gen_room_1803a()
|
||||
|
||||
if result == {'FINISHED'}:
|
||||
print("等轴测房间创建完成!")
|
||||
print(f"房间尺寸: {bpy.context.scene.sna_room_width}m x {bpy.context.scene.sna_room_depth}m x {bpy.context.scene.sna_room_height}m")
|
||||
print(f"墙体厚度: {bpy.context.scene.sna_wall_thickness}m")
|
||||
|
||||
# 将生成的房间在Z轴上向下移动墙体厚度的距离
|
||||
wall_thickness = bpy.context.scene.sna_wall_thickness
|
||||
|
||||
# 查找生成的房间对象
|
||||
room_object = None
|
||||
for obj in bpy.data.objects:
|
||||
if obj.name == "IRG_IsoRoom_WithCeiling":
|
||||
room_object = obj
|
||||
break
|
||||
|
||||
|
||||
return True
|
||||
else:
|
||||
print("房间创建失败")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"创建房间时出现错误: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
def create_window_with_archimesh():
|
||||
"""使用archimesh插件创建落地窗"""
|
||||
try:
|
||||
print("开始创建archimesh落地窗...")
|
||||
|
||||
# 检查archimesh插件是否可用
|
||||
if not hasattr(bpy.ops.mesh, 'archimesh_winpanel'):
|
||||
print("错误: archimesh插件未正确加载")
|
||||
print("请确保archimesh插件已安装并启用")
|
||||
return False
|
||||
|
||||
# 设置3D游标到原点
|
||||
bpy.context.scene.cursor.location = (0.0, 0.0, 0.0)
|
||||
|
||||
# 找到3D视图区域
|
||||
view3d_area = None
|
||||
for area in bpy.context.screen.areas:
|
||||
if area.type == 'VIEW_3D':
|
||||
view3d_area = area
|
||||
break
|
||||
|
||||
if view3d_area is None:
|
||||
print("错误: 找不到3D视图区域")
|
||||
return False
|
||||
|
||||
# 临时切换到3D视图上下文
|
||||
with bpy.context.temp_override(area=view3d_area):
|
||||
# 创建窗户
|
||||
result = bpy.ops.mesh.archimesh_winpanel()
|
||||
|
||||
if result == {'FINISHED'}:
|
||||
print("archimesh落地窗创建完成!")
|
||||
|
||||
# 等待一帧以确保对象创建完成
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
# 找到刚创建的窗户组对象(Window_Group)
|
||||
window_group = None
|
||||
for obj in bpy.data.objects:
|
||||
if obj.name == "Window_Group":
|
||||
window_group = obj
|
||||
break
|
||||
|
||||
if window_group:
|
||||
print(f"找到窗户组: {window_group.name}")
|
||||
|
||||
# 查找主窗户对象(Window)
|
||||
main_window_obj = None
|
||||
for obj in bpy.data.objects:
|
||||
if obj.name == "Window":
|
||||
main_window_obj = obj
|
||||
break
|
||||
|
||||
if main_window_obj and hasattr(main_window_obj, 'WindowPanelGenerator'):
|
||||
# 获取窗户属性
|
||||
window_props = main_window_obj.WindowPanelGenerator[0]
|
||||
|
||||
print("设置窗户属性...")
|
||||
print(f"找到主窗户对象: {main_window_obj.name}")
|
||||
|
||||
# 设置窗户的基本属性
|
||||
window_props.gen = 4 # 4扇窗户(水平)
|
||||
window_props.yuk = 1 # 1行(垂直)
|
||||
window_props.kl1 = 5 # 外框厚度5cm
|
||||
window_props.kl2 = 5 # 竖框厚度5cm
|
||||
window_props.fk = 2 # 内框厚度2cm
|
||||
|
||||
# 设置窗户尺寸
|
||||
window_props.gnx0 = 80 # 第1扇窗户宽度80cm
|
||||
window_props.gnx1 = 80 # 第2扇窗户宽度80cm
|
||||
window_props.gnx2 = 80 # 第3扇窗户宽度80cm
|
||||
window_props.gnx3 = 80 # 第4扇窗户宽度80cm
|
||||
window_props.gny0 = 250 # 窗户高度250cm(落地窗)
|
||||
|
||||
# 设置窗户类型为平窗
|
||||
window_props.UST = '1' # 平窗
|
||||
window_props.r = 0 # 旋转角度0度
|
||||
|
||||
# 设置窗台
|
||||
window_props.mr = True # 启用窗台
|
||||
window_props.mr1 = 4 # 窗台高度4cm
|
||||
window_props.mr2 = 4 # 窗台深度4cm
|
||||
window_props.mr3 = 20 # 窗台宽度20cm
|
||||
window_props.mr4 = 0 # 窗台延伸0cm
|
||||
|
||||
# 设置材质
|
||||
window_props.mt1 = '1' # 外框材质PVC
|
||||
window_props.mt2 = '1' # 内框材质塑料
|
||||
|
||||
# 设置窗户开启状态
|
||||
window_props.k00 = True # 第1扇窗户开启
|
||||
window_props.k01 = True # 第2扇窗户开启
|
||||
window_props.k02 = True # 第3扇窗户开启
|
||||
window_props.k03 = True # 第4扇窗户开启(如果有的话)
|
||||
|
||||
print(f"窗户属性设置完成:")
|
||||
print(f" - 水平扇数: {window_props.gen}")
|
||||
print(f" - 垂直行数: {window_props.yuk}")
|
||||
print(f" - 外框厚度: {window_props.kl1}cm")
|
||||
print(f" - 竖框厚度: {window_props.kl2}cm")
|
||||
print(f" - 内框厚度: {window_props.fk}cm")
|
||||
print(f" - 窗户高度: {window_props.gny0}cm")
|
||||
print(f" - 窗户类型: 平窗")
|
||||
print(f" - 窗台: 启用")
|
||||
print(f" - 窗户开启状态: k00={window_props.k00}, k01={window_props.k01}, k02={window_props.k02}, k03={window_props.k03}")
|
||||
|
||||
|
||||
# 修复窗户玻璃材质
|
||||
print("修复窗户玻璃材质...")
|
||||
fix_window_glass_material()
|
||||
|
||||
# 等待更新完成
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
# 强制触发更新(如果需要)
|
||||
try:
|
||||
# 重新设置属性以触发更新
|
||||
current_gen = window_props.gen
|
||||
current_gny0 = window_props.gny0
|
||||
|
||||
window_props.gen = 2 # 临时设置为其他值
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
window_props.gen = current_gen # 设置回目标值
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
print("已强制触发窗户更新")
|
||||
|
||||
except Exception as update_error:
|
||||
print(f"强制更新时出现错误: {update_error}")
|
||||
|
||||
# 在修改落地窗全部属性后进行位移和旋转
|
||||
print("开始移动和旋转窗户组...")
|
||||
try:
|
||||
window_group1 = None
|
||||
for obj in bpy.data.objects:
|
||||
if obj.name == "Window_Group":
|
||||
window_group1 = obj
|
||||
break
|
||||
# 设置位置
|
||||
window_group1.location = (0, 4.0, 0.0)
|
||||
print(f"已将窗户组移动到位置: {window_group1.location}")
|
||||
|
||||
# 设置旋转
|
||||
window_group1.rotation_euler = (math.radians(0), math.radians(0), math.radians(90)) # 旋转(0, 0, 90度)
|
||||
print(f"已将窗户组旋转到: (0°, 0°, 90°)")
|
||||
|
||||
# 强制更新
|
||||
bpy.context.view_layer.update()
|
||||
print("窗户组位移和旋转完成")
|
||||
|
||||
except Exception as move_error:
|
||||
print(f"移动和旋转窗户组时出现错误: {move_error}")
|
||||
|
||||
else:
|
||||
print("警告: 未找到主窗户对象或WindowPanelGenerator属性")
|
||||
# 尝试查找其他可能的窗户属性
|
||||
for obj in bpy.data.objects:
|
||||
if hasattr(obj, 'archimesh_window'):
|
||||
print(f"找到archimesh_window属性在对象: {obj.name}")
|
||||
|
||||
# 打印窗户组的所有子对象信息
|
||||
print("窗户组包含的对象:")
|
||||
for obj in bpy.data.objects:
|
||||
if obj.parent == window_group:
|
||||
print(f" - {obj.name} (位置: {obj.location})")
|
||||
|
||||
return True
|
||||
else:
|
||||
print("警告: 未找到Window_Group对象")
|
||||
# 尝试查找其他可能的窗户对象
|
||||
window_objects = [obj for obj in bpy.data.objects if "Window" in obj.name]
|
||||
if window_objects:
|
||||
print("找到的窗户相关对象:")
|
||||
for obj in window_objects:
|
||||
print(f" - {obj.name} (类型: {obj.type})")
|
||||
|
||||
# 尝试移动第一个找到的窗户对象
|
||||
first_window = window_objects[0]
|
||||
first_window.location = (0.0, 1.96, 0.0)
|
||||
print(f"已移动 {first_window.name} 到位置: {first_window.location}")
|
||||
return True
|
||||
else:
|
||||
print("未找到任何窗户相关对象")
|
||||
return False
|
||||
|
||||
|
||||
|
||||
else:
|
||||
print("窗户创建失败")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"创建窗户时出现错误: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
|
||||
def fix_window_glass_material():
|
||||
"""修复窗户玻璃材质,将其改为高透BSDF"""
|
||||
try:
|
||||
print("开始修复窗户玻璃材质...")
|
||||
|
||||
# 查找Window_Group下的Window对象
|
||||
window_group = None
|
||||
for obj in bpy.data.objects:
|
||||
if obj.name == "Window_Group":
|
||||
window_group = obj
|
||||
break
|
||||
|
||||
if not window_group:
|
||||
print("未找到Window_Group对象")
|
||||
return False
|
||||
|
||||
# 查找Window对象(Window_Group的子对象)
|
||||
window_obj = None
|
||||
|
||||
for obj in bpy.data.objects:
|
||||
if obj.name == "Window" and obj.parent == window_group:
|
||||
window_obj = obj
|
||||
break
|
||||
|
||||
if not window_obj:
|
||||
print("未找到Window对象")
|
||||
return False
|
||||
|
||||
print(f"找到Window对象: {window_obj.name}")
|
||||
|
||||
# 检查Window对象的材质
|
||||
if not window_obj.material_slots:
|
||||
print("Window对象没有材质槽")
|
||||
return False
|
||||
|
||||
# 遍历所有材质槽
|
||||
for slot in window_obj.material_slots:
|
||||
if slot.material and "Glass" in slot.material.name:
|
||||
print(f"找到Glass材质: {slot.material.name}")
|
||||
|
||||
# 启用节点编辑
|
||||
slot.material.use_nodes = True
|
||||
|
||||
# 清除所有现有节点
|
||||
slot.material.node_tree.nodes.clear()
|
||||
|
||||
# 创建半透BSDF节点
|
||||
translucent_bsdf = slot.material.node_tree.nodes.new(type='ShaderNodeBsdfTranslucent')
|
||||
translucent_bsdf.location = (0, 0)
|
||||
|
||||
# 设置半透BSDF参数
|
||||
translucent_bsdf.inputs['Color'].default_value = (0.95, 0.98, 1.0, 1.0) # 几乎无色
|
||||
# 半透BSDF节点没有Weight参数,只有Color参数
|
||||
|
||||
# 创建材质输出节点
|
||||
material_output = slot.material.node_tree.nodes.new(type='ShaderNodeOutputMaterial')
|
||||
material_output.location = (300, 0)
|
||||
|
||||
# 连接节点
|
||||
slot.material.node_tree.links.new(
|
||||
translucent_bsdf.outputs['BSDF'],
|
||||
material_output.inputs['Surface']
|
||||
)
|
||||
|
||||
# 设置材质混合模式为Alpha Blend
|
||||
slot.material.blend_method = 'BLEND'
|
||||
|
||||
print(f"已成功修改Glass材质为半透BSDF")
|
||||
print(f" - 半透颜色: 几乎无色")
|
||||
print(f" - 混合模式: Alpha Blend")
|
||||
|
||||
return True
|
||||
|
||||
print("未找到Glass材质")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"修复玻璃材质时出现错误: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
def setup_render_settings():
|
||||
"""设置渲染参数(EEVEE渲染,优化速度)"""
|
||||
scene = bpy.context.scene
|
||||
|
||||
# 设置渲染引擎为EEVEE
|
||||
scene.render.engine = 'BLENDER_EEVEE_NEXT'
|
||||
print("已设置渲染引擎为EEVEE")
|
||||
|
||||
# 设置世界环境
|
||||
if hasattr(scene, 'world') and scene.world:
|
||||
# 启用世界节点
|
||||
scene.world.use_nodes = True
|
||||
|
||||
# 清除现有节点
|
||||
scene.world.node_tree.nodes.clear()
|
||||
|
||||
# 创建自发光节点
|
||||
emission_node = scene.world.node_tree.nodes.new(type='ShaderNodeEmission')
|
||||
emission_node.location = (0, 0)
|
||||
|
||||
# 设置HSV颜色:色相0,饱和度0,明度0.051,Alpha 1
|
||||
# 转换为RGB:HSV(0, 0, 0.051) = RGB(0.051, 0.051, 0.051)
|
||||
emission_node.inputs['Color'].default_value = (0.051, 0.051, 0.051, 1.0) # 深灰色
|
||||
emission_node.inputs['Strength'].default_value = 7.0 # 强度7
|
||||
|
||||
# 创建世界输出节点
|
||||
world_output = scene.world.node_tree.nodes.new(type='ShaderNodeOutputWorld')
|
||||
world_output.location = (300, 0)
|
||||
|
||||
# 连接节点
|
||||
scene.world.node_tree.links.new(
|
||||
emission_node.outputs['Emission'],
|
||||
world_output.inputs['Surface']
|
||||
)
|
||||
|
||||
print("已设置世界环境为自发光")
|
||||
print(f" - 颜色: HSV(0, 0, 0.051) = RGB(0.051, 0.051, 0.051)")
|
||||
print(f" - 强度: 7.0")
|
||||
|
||||
# 设置EEVEE采样(降低采样数提高速度)
|
||||
try:
|
||||
if hasattr(scene.eevee, 'taa_render_samples'):
|
||||
scene.eevee.taa_render_samples = 32 # 从64降低到32
|
||||
print(f"已设置EEVEE渲染采样: {scene.eevee.taa_render_samples}")
|
||||
elif hasattr(scene.eevee, 'taa_samples'):
|
||||
scene.eevee.taa_samples = 32 # 从64降低到32
|
||||
print(f"已设置EEVEE采样: {scene.eevee.taa_samples}")
|
||||
else:
|
||||
print("警告: 无法找到EEVEE采样设置,使用默认值")
|
||||
|
||||
# 启用屏幕空间反射(简化设置)
|
||||
if hasattr(scene.eevee, 'use_ssr'):
|
||||
scene.eevee.use_ssr = True
|
||||
print("已启用屏幕空间反射")
|
||||
|
||||
# 启用环境光遮蔽(简化设置)
|
||||
if hasattr(scene.eevee, 'use_gtao'):
|
||||
scene.eevee.use_gtao = True
|
||||
print("已启用环境光遮蔽")
|
||||
|
||||
# 启用透明渲染(必要)
|
||||
if hasattr(scene.eevee, 'use_transparent'):
|
||||
scene.eevee.use_transparent = True
|
||||
print("已启用透明渲染")
|
||||
|
||||
# 设置透明混合模式(必要)
|
||||
if hasattr(scene.render, 'film_transparent'):
|
||||
scene.render.film_transparent = True
|
||||
print("已启用透明背景")
|
||||
|
||||
except AttributeError as e:
|
||||
print(f"EEVEE设置警告: {e}")
|
||||
print("使用默认EEVEE渲染设置")
|
||||
|
||||
# 设置分辨率
|
||||
scene.render.resolution_x = 1080 # 宽度:1080px
|
||||
scene.render.resolution_y = 2400 # 高度:2400px
|
||||
scene.render.resolution_percentage = 100
|
||||
|
||||
# 设置输出格式
|
||||
scene.render.image_settings.file_format = 'PNG'
|
||||
scene.render.image_settings.color_mode = 'RGBA' # 支持透明
|
||||
|
||||
# 设置输出路径到桌面
|
||||
desktop_path = os.path.join(os.path.expanduser("~"), "Desktop")
|
||||
timestamp = time.strftime("%m%d_%H%M%S") # 日期_时分秒格式
|
||||
filename = f"isometric_{timestamp}.png"
|
||||
scene.render.filepath = os.path.join(desktop_path, filename)
|
||||
|
||||
print(f"EEVEE渲染设置完成,输出路径: {scene.render.filepath}")
|
||||
return scene.render.filepath
|
||||
|
||||
def setup_camera_and_lighting():
|
||||
"""设置摄像机和照明(160W点光源 + 150W日光)"""
|
||||
# 计算灯光高度(房间高度减一米)
|
||||
room_height = 3.0
|
||||
light_height = room_height - 1.0
|
||||
|
||||
# 设置摄像机
|
||||
camera = None
|
||||
if "Camera" in bpy.data.objects:
|
||||
camera = bpy.data.objects["Camera"]
|
||||
elif "Isometric Camera" in bpy.data.objects:
|
||||
camera = bpy.data.objects["Isometric Camera"]
|
||||
else:
|
||||
# 创建新摄像机
|
||||
bpy.ops.object.camera_add(location=(7, -7, 5))
|
||||
camera = bpy.context.active_object
|
||||
camera.name = "Isometric Camera"
|
||||
|
||||
# 设置摄像机位置和旋转
|
||||
# 房间宽度5.0米,相机位置调整为(房间宽度-1, 1, 1.3)
|
||||
room_width = 5.0
|
||||
camera.location = (room_width - 1, 1, 1.3) # 相机位置:(4, 1, 1.3)
|
||||
camera.rotation_euler = (math.radians(90), math.radians(0), math.radians(45)) # 相机旋转
|
||||
|
||||
# 设置为透视投影
|
||||
camera.data.type = 'PERSP'
|
||||
camera.data.lens = 35.0 # 35mm焦距
|
||||
camera.data.sensor_width = 35.0 # 35mm传感器
|
||||
camera.data.sensor_fit = 'AUTO' # 自动适配
|
||||
|
||||
# 设置为场景的活动摄像机
|
||||
bpy.context.scene.camera = camera
|
||||
print("摄像机设置完成")
|
||||
print(f"相机位置: ({camera.location.x}, {camera.location.y}, {camera.location.z})")
|
||||
|
||||
# 隐藏ISO Emission灯光(如果存在)
|
||||
light_objects = ["ISO Emission Left", "ISO Emission Right"]
|
||||
for obj_name in light_objects:
|
||||
if obj_name in bpy.data.objects:
|
||||
light_obj = bpy.data.objects[obj_name]
|
||||
# 隐藏发光对象
|
||||
light_obj.hide_render = True
|
||||
light_obj.hide_viewport = True
|
||||
print(f"已隐藏 {obj_name}")
|
||||
|
||||
# 创建第一个160W点光源(房间中心)
|
||||
# try:
|
||||
# # 计算点光源位置(房间中心上方)
|
||||
# room_length = 4.0 # Y轴
|
||||
# room_width = 8.0 # X轴(4+4)
|
||||
|
||||
# # 第一个点光源位置:房间中心上方
|
||||
# light_x = 0 # X轴中心
|
||||
# light_y = 0 # Y轴中心
|
||||
# light_z = light_height
|
||||
|
||||
# # 创建第一个点光源
|
||||
# bpy.ops.object.light_add(type='POINT', location=(light_x, light_y, light_z))
|
||||
# point_light1 = bpy.context.active_object
|
||||
# point_light1.name = "Main Point Light"
|
||||
# point_light1.data.energy = 160 # 160W
|
||||
|
||||
# # 设置软衰减(简化设置)
|
||||
# point_light1.data.shadow_soft_size = 0.5 # 0.5米半径,产生柔和阴影
|
||||
# point_light1.data.use_shadow = True # 启用阴影
|
||||
|
||||
# print(f"已创建第一个点光源(160W)")
|
||||
# print(f"点光源位置: x={light_x}, y={light_y}, z={light_z}")
|
||||
|
||||
# except Exception as e:
|
||||
# print(f"创建第一个点光源时出错: {e}")
|
||||
|
||||
# 创建第二个150W日光(位置12, 7, 6)
|
||||
try:
|
||||
# 第二个光源位置 - 调整到窗户后方更远的位置
|
||||
light2_x = -6
|
||||
light2_y = 7 # 让日光更远
|
||||
light2_z = 6 # 提高高度
|
||||
|
||||
# 创建日光
|
||||
bpy.ops.object.light_add(type='SUN', location=(light2_x, light2_y, light2_z))
|
||||
sun_light = bpy.context.active_object
|
||||
sun_light.name = "Sun Light"
|
||||
sun_light.data.energy = 20
|
||||
|
||||
# 调整日光旋转角度,让光线更直接地照射窗户
|
||||
sun_light.rotation_euler = (math.radians(0), math.radians(-60), math.radians(0)) # 简化旋转角度
|
||||
|
||||
# 设置日光属性(简化设置)
|
||||
sun_light.data.angle = 0.05 # 从0.1改为0.05,让阴影更锐利
|
||||
sun_light.data.use_shadow = True # 启用阴影
|
||||
|
||||
print(f"已创建日光(20W)")
|
||||
print(f"日光位置: x={light2_x}, y={light2_y}, z={light2_z}")
|
||||
print(f"日光旋转: x={-70}°, y={0}°, z={0}°")
|
||||
|
||||
except Exception as e:
|
||||
print(f"创建日光时出错: {e}")
|
||||
|
||||
print("照明设置完成")
|
||||
print("灯光类型: 点光源 + 日光")
|
||||
print(f"衰减类型: 点光源软衰减 + 日光平行光")
|
||||
print(f"阴影设置: 启用,柔和阴影")
|
||||
print(f"主灯光高度: {light_height}米 (房间高度减一米)")
|
||||
print(f"日光位置: (0, 10, 6)米,旋转: (-70°, 0°, 0°)")
|
||||
|
||||
def render_scene():
|
||||
"""渲染场景"""
|
||||
print("开始EEVEE渲染...")
|
||||
|
||||
# 执行渲染
|
||||
bpy.ops.render.render(write_still=True)
|
||||
print(f"EEVEE渲染完成! 图片保存在: {bpy.context.scene.render.filepath}")
|
||||
return bpy.context.scene.render.filepath
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("=" * 60)
|
||||
print("开始自动创建等轴测房间并EEVEE渲染")
|
||||
print("=" * 60)
|
||||
|
||||
try:
|
||||
# 1. 创建房间
|
||||
print("1. 创建等轴测房间...")
|
||||
if not create_isometric_room():
|
||||
print("房间创建失败,停止执行")
|
||||
return False
|
||||
|
||||
# 2. 创建落地窗
|
||||
print("2. 创建archimesh落地窗...")
|
||||
if not create_window_with_archimesh():
|
||||
print("落地窗创建失败,但继续执行")
|
||||
# 不中断执行,窗户创建失败不影响渲染
|
||||
|
||||
# 3. 设置渲染参数(EEVEE)
|
||||
print("3. 设置EEVEE渲染参数...")
|
||||
output_path = setup_render_settings()
|
||||
|
||||
# 4. 设置摄像机和照明
|
||||
print("4. 设置摄像机和照明...")
|
||||
setup_camera_and_lighting()
|
||||
|
||||
# 5. 渲染场景
|
||||
print("5. 开始EEVEE渲染...")
|
||||
final_path = render_scene()
|
||||
|
||||
print("=" * 60)
|
||||
print("EEVEE渲染完成!")
|
||||
print(f"渲染图片已保存到桌面: {final_path}")
|
||||
print("=" * 60)
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"执行过程中出现错误: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
# 执行主函数
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
'''
|
||||
|
||||
|
||||
def check_blender_exists():
|
||||
"""检查Blender是否存在于指定路径"""
|
||||
if not os.path.exists(BLENDER_PATH):
|
||||
print(f"错误: 在路径 '{BLENDER_PATH}' 找不到Blender")
|
||||
print("请检查Blender安装路径是否正确")
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def create_temp_script():
|
||||
"""创建临时的Blender脚本文件"""
|
||||
script_path = os.path.join(os.getcwd(), "temp_simple_blender_script.py")
|
||||
with open(script_path, 'w', encoding='utf-8') as f:
|
||||
f.write(BLENDER_SIMPLE_SCRIPT)
|
||||
return script_path
|
||||
|
||||
|
||||
def launch_blender_simple():
|
||||
"""启动Blender并执行简化版自动化脚本"""
|
||||
if not check_blender_exists():
|
||||
return False
|
||||
|
||||
# 创建临时脚本文件
|
||||
script_path = create_temp_script()
|
||||
|
||||
try:
|
||||
print("正在启动Blender(白模版本)...")
|
||||
print(f"Blender路径: {BLENDER_PATH}")
|
||||
print("特点:")
|
||||
print("- 不设置材质,使用默认白色材质")
|
||||
print("- 快速渲染")
|
||||
print("- 兼容Blender 4.2")
|
||||
print("- 自动保存到桌面")
|
||||
|
||||
# 构建命令行参数
|
||||
cmd = [
|
||||
BLENDER_PATH,
|
||||
"--background", # 后台运行模式
|
||||
"--disable-crash-handler", # 禁用崩溃处理器
|
||||
"--python", script_path # 执行Python脚本
|
||||
]
|
||||
|
||||
print("\n开始执行白模渲染流程...")
|
||||
|
||||
# 启动Blender并等待完成
|
||||
process = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding='utf-8',
|
||||
timeout=300 # 5分钟超时
|
||||
)
|
||||
|
||||
# 输出结果
|
||||
if process.stdout:
|
||||
print("Blender输出:")
|
||||
# 过滤掉重复的错误信息
|
||||
lines = process.stdout.split('\n')
|
||||
filtered_lines = []
|
||||
for line in lines:
|
||||
if not ("AttributeError: 'NoneType' object has no attribute 'idname'" in line or
|
||||
"Error in bpy.app.handlers.depsgraph_update_post" in line):
|
||||
filtered_lines.append(line)
|
||||
print('\n'.join(filtered_lines))
|
||||
|
||||
if process.stderr and not "rotate_tool.py" in process.stderr:
|
||||
print("重要错误信息:")
|
||||
print(process.stderr)
|
||||
|
||||
if process.returncode == 0:
|
||||
print("\n✅ 白模渲染执行成功!")
|
||||
print("白模图片应该已经保存到桌面")
|
||||
return True
|
||||
else:
|
||||
print(f"\n❌ 执行失败,返回码: {process.returncode}")
|
||||
return False
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
print("\n⏰ 执行超时(5分钟),可能Blender仍在运行")
|
||||
print("请检查桌面是否有生成的图片")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"启动Blender时出现错误: {str(e)}")
|
||||
return False
|
||||
|
||||
finally:
|
||||
# 清理临时文件
|
||||
try:
|
||||
if os.path.exists(script_path):
|
||||
os.remove(script_path)
|
||||
print(f"已清理临时文件: {script_path}")
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("=" * 70)
|
||||
print("Blender 4.2兼容版自动化脚本 - 白模渲染")
|
||||
print("=" * 70)
|
||||
print("特点:")
|
||||
print("✓ 移除所有材质设置")
|
||||
print("✓ 使用默认白色材质")
|
||||
print("✓ 快速渲染")
|
||||
print("✓ 兼容Blender 4.2")
|
||||
print("✓ 自动保存到桌面")
|
||||
print("=" * 70)
|
||||
|
||||
success = launch_blender_simple()
|
||||
|
||||
if success:
|
||||
print("\n🎉 白模渲染完成!")
|
||||
print("请检查桌面上的渲染图片")
|
||||
print("图片文件名格式: isometric_room_white_[时间戳].png")
|
||||
else:
|
||||
print("\n❌ 执行失败!")
|
||||
print("可能的解决方案:")
|
||||
print("1. 确保Isometquick插件已正确安装")
|
||||
print("2. 禁用可能冲突的其他插件")
|
||||
print("3. 检查磁盘空间是否足够")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,775 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Blender 4.2兼容版自动化脚本(白模版本)
|
||||
自动创建等轴测房间并渲染白模
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Blender可执行文件路径
|
||||
BLENDER_PATH = r"D:\Program Files\Blender Foundation\blender-4.2.11-windows-x64\blender.exe"
|
||||
|
||||
# 简化版Blender脚本(只渲染白模)
|
||||
BLENDER_SIMPLE_SCRIPT = '''
|
||||
import bpy
|
||||
import bmesh
|
||||
import os
|
||||
import math
|
||||
import time
|
||||
|
||||
def disable_problematic_addons():
|
||||
"""禁用可能导致问题的插件"""
|
||||
try:
|
||||
# 禁用可能冲突的插件
|
||||
problematic_addons = ['bl_tool']
|
||||
for addon in problematic_addons:
|
||||
if addon in bpy.context.preferences.addons:
|
||||
bpy.ops.preferences.addon_disable(module=addon)
|
||||
print(f"已禁用插件: {addon}")
|
||||
except:
|
||||
pass
|
||||
|
||||
def create_isometric_room():
|
||||
"""创建等轴测房间,使用isometric_room_gen插件"""
|
||||
try:
|
||||
# 禁用问题插件
|
||||
#disable_problematic_addons()
|
||||
|
||||
# 清除默认立方体
|
||||
if "Cube" in bpy.data.objects:
|
||||
bpy.data.objects.remove(bpy.data.objects["Cube"], do_unlink=True)
|
||||
|
||||
# 设置3D游标位置到原点
|
||||
bpy.context.scene.cursor.location = (0.0, 0.0, 0.0)
|
||||
|
||||
# 检查isometric_room_gen插件
|
||||
if not hasattr(bpy.context.scene, 'sna_room_width'):
|
||||
print("错误: isometric_room_gen插件未正确加载")
|
||||
return False
|
||||
|
||||
# 设置isometric_room_gen插件参数
|
||||
# 房间设置
|
||||
bpy.context.scene.sna_wall_thickness = 0.10 # 墙体厚度10cm
|
||||
bpy.context.scene.sna_room_width = 5.0 # 房间宽度8米
|
||||
bpy.context.scene.sna_room_depth = 8.0 # 房间深度4米
|
||||
bpy.context.scene.sna_room_height = 3.0 # 房间高度3米
|
||||
|
||||
# 窗户设置 - 不设置窗户
|
||||
bpy.context.scene.sna_windows_enum = 'NONE' # 无窗户
|
||||
|
||||
# 设置房间类型为方形
|
||||
bpy.context.scene.sna_style = 'Square'
|
||||
|
||||
# 找到3D视图区域
|
||||
view3d_area = None
|
||||
for area in bpy.context.screen.areas:
|
||||
if area.type == 'VIEW_3D':
|
||||
view3d_area = area
|
||||
break
|
||||
|
||||
if view3d_area is None:
|
||||
print("错误: 找不到3D视图区域")
|
||||
return False
|
||||
|
||||
# 临时切换到3D视图上下文
|
||||
with bpy.context.temp_override(area=view3d_area):
|
||||
# 使用isometric_room_gen插件创建房间
|
||||
result = bpy.ops.sna.gen_room_1803a()
|
||||
|
||||
if result == {'FINISHED'}:
|
||||
print("等轴测房间创建完成!")
|
||||
print(f"房间尺寸: {bpy.context.scene.sna_room_width}m x {bpy.context.scene.sna_room_depth}m x {bpy.context.scene.sna_room_height}m")
|
||||
print(f"墙体厚度: {bpy.context.scene.sna_wall_thickness}m")
|
||||
|
||||
# 将生成的房间在Z轴上向下移动墙体厚度的距离
|
||||
wall_thickness = bpy.context.scene.sna_wall_thickness
|
||||
|
||||
# 查找生成的房间对象
|
||||
room_object = None
|
||||
for obj in bpy.data.objects:
|
||||
if obj.name == "IRG_IsoRoom_WithCeiling":
|
||||
room_object = obj
|
||||
break
|
||||
|
||||
if room_object:
|
||||
# 获取当前位置
|
||||
current_location = room_object.location.copy()
|
||||
# 在Z轴上向下移动墙体厚度的距离
|
||||
room_object.location.z = current_location.z - wall_thickness
|
||||
print(f"已将房间在Z轴上向下移动 {wall_thickness}m")
|
||||
print(f"房间新位置: {room_object.location}")
|
||||
else:
|
||||
print("警告: 未找到生成的房间对象")
|
||||
|
||||
return True
|
||||
else:
|
||||
print("房间创建失败")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"创建房间时出现错误: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
def create_window_with_archimesh():
|
||||
"""使用archimesh插件创建落地窗"""
|
||||
try:
|
||||
print("开始创建archimesh落地窗...")
|
||||
|
||||
# 检查archimesh插件是否可用
|
||||
if not hasattr(bpy.ops.mesh, 'archimesh_winpanel'):
|
||||
print("错误: archimesh插件未正确加载")
|
||||
print("请确保archimesh插件已安装并启用")
|
||||
return False
|
||||
|
||||
# 设置3D游标到原点
|
||||
bpy.context.scene.cursor.location = (0.0, 0.0, 0.0)
|
||||
|
||||
# 找到3D视图区域
|
||||
view3d_area = None
|
||||
for area in bpy.context.screen.areas:
|
||||
if area.type == 'VIEW_3D':
|
||||
view3d_area = area
|
||||
break
|
||||
|
||||
if view3d_area is None:
|
||||
print("错误: 找不到3D视图区域")
|
||||
return False
|
||||
|
||||
# 临时切换到3D视图上下文
|
||||
with bpy.context.temp_override(area=view3d_area):
|
||||
# 创建窗户
|
||||
result = bpy.ops.mesh.archimesh_winpanel()
|
||||
|
||||
if result == {'FINISHED'}:
|
||||
print("archimesh落地窗创建完成!")
|
||||
|
||||
# 等待一帧以确保对象创建完成
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
# 找到刚创建的窗户组对象(Window_Group)
|
||||
window_group = None
|
||||
for obj in bpy.data.objects:
|
||||
if obj.name == "Window_Group":
|
||||
window_group = obj
|
||||
break
|
||||
|
||||
if window_group:
|
||||
print(f"找到窗户组: {window_group.name}")
|
||||
|
||||
# 查找主窗户对象(Window)
|
||||
main_window_obj = None
|
||||
for obj in bpy.data.objects:
|
||||
if obj.name == "Window":
|
||||
main_window_obj = obj
|
||||
break
|
||||
|
||||
if main_window_obj and hasattr(main_window_obj, 'WindowPanelGenerator'):
|
||||
# 获取窗户属性
|
||||
window_props = main_window_obj.WindowPanelGenerator[0]
|
||||
|
||||
print("设置窗户属性...")
|
||||
print(f"找到主窗户对象: {main_window_obj.name}")
|
||||
|
||||
# 设置窗户的基本属性
|
||||
window_props.gen = 4 # 4扇窗户(水平)
|
||||
window_props.yuk = 1 # 1行(垂直)
|
||||
window_props.kl1 = 5 # 外框厚度5cm
|
||||
window_props.kl2 = 5 # 竖框厚度5cm
|
||||
window_props.fk = 2 # 内框厚度2cm
|
||||
|
||||
# 设置窗户尺寸
|
||||
window_props.gnx0 = 80 # 第1扇窗户宽度80cm
|
||||
window_props.gnx1 = 80 # 第2扇窗户宽度80cm
|
||||
window_props.gnx2 = 80 # 第3扇窗户宽度80cm
|
||||
window_props.gnx3 = 80 # 第4扇窗户宽度80cm
|
||||
window_props.gny0 = 250 # 窗户高度250cm(落地窗)
|
||||
|
||||
# 设置窗户类型为平窗
|
||||
window_props.UST = '1' # 平窗
|
||||
window_props.r = 0 # 旋转角度0度
|
||||
|
||||
# 设置窗台
|
||||
window_props.mr = True # 启用窗台
|
||||
window_props.mr1 = 4 # 窗台高度4cm
|
||||
window_props.mr2 = 4 # 窗台深度4cm
|
||||
window_props.mr3 = 20 # 窗台宽度20cm
|
||||
window_props.mr4 = 0 # 窗台延伸0cm
|
||||
|
||||
# 设置材质
|
||||
window_props.mt1 = '1' # 外框材质PVC
|
||||
window_props.mt2 = '1' # 内框材质塑料
|
||||
|
||||
# 设置窗户开启状态
|
||||
window_props.k00 = True # 第1扇窗户开启
|
||||
window_props.k01 = True # 第2扇窗户开启
|
||||
window_props.k02 = True # 第3扇窗户开启
|
||||
window_props.k03 = True # 第4扇窗户开启(如果有的话)
|
||||
|
||||
print(f"窗户属性设置完成:")
|
||||
print(f" - 水平扇数: {window_props.gen}")
|
||||
print(f" - 垂直行数: {window_props.yuk}")
|
||||
print(f" - 外框厚度: {window_props.kl1}cm")
|
||||
print(f" - 竖框厚度: {window_props.kl2}cm")
|
||||
print(f" - 内框厚度: {window_props.fk}cm")
|
||||
print(f" - 窗户高度: {window_props.gny0}cm")
|
||||
print(f" - 窗户类型: 平窗")
|
||||
print(f" - 窗台: 启用")
|
||||
print(f" - 窗户开启状态: k00={window_props.k00}, k01={window_props.k01}, k02={window_props.k02}, k03={window_props.k03}")
|
||||
|
||||
|
||||
# 修复窗户玻璃材质
|
||||
print("修复窗户玻璃材质...")
|
||||
fix_window_glass_material()
|
||||
|
||||
# 等待更新完成
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
# 强制触发更新(如果需要)
|
||||
try:
|
||||
# 重新设置属性以触发更新
|
||||
current_gen = window_props.gen
|
||||
current_gny0 = window_props.gny0
|
||||
|
||||
window_props.gen = 2 # 临时设置为其他值
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
window_props.gen = current_gen # 设置回目标值
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
print("已强制触发窗户更新")
|
||||
|
||||
except Exception as update_error:
|
||||
print(f"强制更新时出现错误: {update_error}")
|
||||
|
||||
# 在修改落地窗全部属性后进行位移和旋转
|
||||
print("开始移动和旋转窗户组...")
|
||||
try:
|
||||
window_group1 = None
|
||||
for obj in bpy.data.objects:
|
||||
if obj.name == "Window_Group":
|
||||
window_group1 = obj
|
||||
break
|
||||
# 设置位置
|
||||
window_group1.location = (5, 4.0, 0.0)
|
||||
print(f"已将窗户组移动到位置: {window_group1.location}")
|
||||
|
||||
# 设置旋转
|
||||
window_group1.rotation_euler = (math.radians(0), math.radians(0), math.radians(90)) # 旋转(0, 0, 90度)
|
||||
print(f"已将窗户组旋转到: (0°, 0°, 90°)")
|
||||
|
||||
# 强制更新
|
||||
bpy.context.view_layer.update()
|
||||
print("窗户组位移和旋转完成")
|
||||
|
||||
except Exception as move_error:
|
||||
print(f"移动和旋转窗户组时出现错误: {move_error}")
|
||||
|
||||
else:
|
||||
print("警告: 未找到主窗户对象或WindowPanelGenerator属性")
|
||||
# 尝试查找其他可能的窗户属性
|
||||
for obj in bpy.data.objects:
|
||||
if hasattr(obj, 'archimesh_window'):
|
||||
print(f"找到archimesh_window属性在对象: {obj.name}")
|
||||
|
||||
# 打印窗户组的所有子对象信息
|
||||
print("窗户组包含的对象:")
|
||||
for obj in bpy.data.objects:
|
||||
if obj.parent == window_group:
|
||||
print(f" - {obj.name} (位置: {obj.location})")
|
||||
|
||||
return True
|
||||
else:
|
||||
print("警告: 未找到Window_Group对象")
|
||||
# 尝试查找其他可能的窗户对象
|
||||
window_objects = [obj for obj in bpy.data.objects if "Window" in obj.name]
|
||||
if window_objects:
|
||||
print("找到的窗户相关对象:")
|
||||
for obj in window_objects:
|
||||
print(f" - {obj.name} (类型: {obj.type})")
|
||||
|
||||
# 尝试移动第一个找到的窗户对象
|
||||
first_window = window_objects[0]
|
||||
first_window.location = (0.0, 1.96, 0.0)
|
||||
print(f"已移动 {first_window.name} 到位置: {first_window.location}")
|
||||
return True
|
||||
else:
|
||||
print("未找到任何窗户相关对象")
|
||||
return False
|
||||
|
||||
|
||||
|
||||
else:
|
||||
print("窗户创建失败")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"创建窗户时出现错误: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
|
||||
def fix_window_glass_material():
|
||||
"""修复窗户玻璃材质,将其改为高透BSDF"""
|
||||
try:
|
||||
print("开始修复窗户玻璃材质...")
|
||||
|
||||
# 查找Window_Group下的Window对象
|
||||
window_group = None
|
||||
for obj in bpy.data.objects:
|
||||
if obj.name == "Window_Group":
|
||||
window_group = obj
|
||||
break
|
||||
|
||||
if not window_group:
|
||||
print("未找到Window_Group对象")
|
||||
return False
|
||||
|
||||
# 查找Window对象(Window_Group的子对象)
|
||||
window_obj = None
|
||||
for obj in bpy.data.objects:
|
||||
if obj.name == "Window" and obj.parent == window_group:
|
||||
window_obj = obj
|
||||
break
|
||||
|
||||
if not window_obj:
|
||||
print("未找到Window对象")
|
||||
return False
|
||||
|
||||
print(f"找到Window对象: {window_obj.name}")
|
||||
|
||||
# 检查Window对象的材质
|
||||
if not window_obj.material_slots:
|
||||
print("Window对象没有材质槽")
|
||||
return False
|
||||
|
||||
# 遍历所有材质槽
|
||||
for slot in window_obj.material_slots:
|
||||
if slot.material and "Glass" in slot.material.name:
|
||||
print(f"找到Glass材质: {slot.material.name}")
|
||||
|
||||
# 启用节点编辑
|
||||
slot.material.use_nodes = True
|
||||
|
||||
# 清除所有现有节点
|
||||
slot.material.node_tree.nodes.clear()
|
||||
|
||||
# 创建半透BSDF节点
|
||||
translucent_bsdf = slot.material.node_tree.nodes.new(type='ShaderNodeBsdfTranslucent')
|
||||
translucent_bsdf.location = (0, 0)
|
||||
|
||||
# 设置半透BSDF参数
|
||||
translucent_bsdf.inputs['Color'].default_value = (0.95, 0.98, 1.0, 1.0) # 几乎无色
|
||||
# 半透BSDF节点没有Weight参数,只有Color参数
|
||||
|
||||
# 创建材质输出节点
|
||||
material_output = slot.material.node_tree.nodes.new(type='ShaderNodeOutputMaterial')
|
||||
material_output.location = (300, 0)
|
||||
|
||||
# 连接节点
|
||||
slot.material.node_tree.links.new(
|
||||
translucent_bsdf.outputs['BSDF'],
|
||||
material_output.inputs['Surface']
|
||||
)
|
||||
|
||||
# 设置材质混合模式为Alpha Blend
|
||||
slot.material.blend_method = 'BLEND'
|
||||
|
||||
print(f"已成功修改Glass材质为半透BSDF")
|
||||
print(f" - 半透颜色: 几乎无色")
|
||||
print(f" - 混合模式: Alpha Blend")
|
||||
|
||||
return True
|
||||
|
||||
print("未找到Glass材质")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"修复玻璃材质时出现错误: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
def setup_render_settings():
|
||||
"""设置渲染参数(EEVEE渲染,优化速度)"""
|
||||
scene = bpy.context.scene
|
||||
|
||||
# 设置渲染引擎为EEVEE
|
||||
scene.render.engine = 'BLENDER_EEVEE_NEXT'
|
||||
print("已设置渲染引擎为EEVEE")
|
||||
|
||||
# 设置世界环境
|
||||
if hasattr(scene, 'world') and scene.world:
|
||||
# 启用世界节点
|
||||
scene.world.use_nodes = True
|
||||
|
||||
# 清除现有节点
|
||||
scene.world.node_tree.nodes.clear()
|
||||
|
||||
# 创建自发光节点
|
||||
emission_node = scene.world.node_tree.nodes.new(type='ShaderNodeEmission')
|
||||
emission_node.location = (0, 0)
|
||||
|
||||
# 设置HSV颜色:色相0,饱和度0,明度0.051,Alpha 1
|
||||
# 转换为RGB:HSV(0, 0, 0.051) = RGB(0.051, 0.051, 0.051)
|
||||
emission_node.inputs['Color'].default_value = (0.051, 0.051, 0.051, 1.0) # 深灰色
|
||||
emission_node.inputs['Strength'].default_value = 7.0 # 强度7
|
||||
|
||||
# 创建世界输出节点
|
||||
world_output = scene.world.node_tree.nodes.new(type='ShaderNodeOutputWorld')
|
||||
world_output.location = (300, 0)
|
||||
|
||||
# 连接节点
|
||||
scene.world.node_tree.links.new(
|
||||
emission_node.outputs['Emission'],
|
||||
world_output.inputs['Surface']
|
||||
)
|
||||
|
||||
print("已设置世界环境为自发光")
|
||||
print(f" - 颜色: HSV(0, 0, 0.051) = RGB(0.051, 0.051, 0.051)")
|
||||
print(f" - 强度: 7.0")
|
||||
|
||||
# 设置EEVEE采样(降低采样数提高速度)
|
||||
try:
|
||||
if hasattr(scene.eevee, 'taa_render_samples'):
|
||||
scene.eevee.taa_render_samples = 32 # 从64降低到32
|
||||
print(f"已设置EEVEE渲染采样: {scene.eevee.taa_render_samples}")
|
||||
elif hasattr(scene.eevee, 'taa_samples'):
|
||||
scene.eevee.taa_samples = 32 # 从64降低到32
|
||||
print(f"已设置EEVEE采样: {scene.eevee.taa_samples}")
|
||||
else:
|
||||
print("警告: 无法找到EEVEE采样设置,使用默认值")
|
||||
|
||||
# 启用屏幕空间反射(简化设置)
|
||||
if hasattr(scene.eevee, 'use_ssr'):
|
||||
scene.eevee.use_ssr = True
|
||||
print("已启用屏幕空间反射")
|
||||
|
||||
# 启用环境光遮蔽(简化设置)
|
||||
if hasattr(scene.eevee, 'use_gtao'):
|
||||
scene.eevee.use_gtao = True
|
||||
print("已启用环境光遮蔽")
|
||||
|
||||
# 启用透明渲染(必要)
|
||||
if hasattr(scene.eevee, 'use_transparent'):
|
||||
scene.eevee.use_transparent = True
|
||||
print("已启用透明渲染")
|
||||
|
||||
# 设置透明混合模式(必要)
|
||||
if hasattr(scene.render, 'film_transparent'):
|
||||
scene.render.film_transparent = True
|
||||
print("已启用透明背景")
|
||||
|
||||
except AttributeError as e:
|
||||
print(f"EEVEE设置警告: {e}")
|
||||
print("使用默认EEVEE渲染设置")
|
||||
|
||||
# 设置分辨率
|
||||
scene.render.resolution_x = 1080 # 宽度:1080px
|
||||
scene.render.resolution_y = 2400 # 高度:2400px
|
||||
scene.render.resolution_percentage = 100
|
||||
|
||||
# 设置输出格式
|
||||
scene.render.image_settings.file_format = 'PNG'
|
||||
scene.render.image_settings.color_mode = 'RGBA' # 支持透明
|
||||
|
||||
# 设置输出路径到桌面
|
||||
desktop_path = os.path.join(os.path.expanduser("~"), "Desktop")
|
||||
timestamp = time.strftime("%m%d_%H%M%S") # 日期_时分秒格式
|
||||
filename = f"isometric_{timestamp}.png"
|
||||
scene.render.filepath = os.path.join(desktop_path, filename)
|
||||
|
||||
print(f"EEVEE渲染设置完成,输出路径: {scene.render.filepath}")
|
||||
return scene.render.filepath
|
||||
|
||||
def setup_camera_and_lighting():
|
||||
"""设置摄像机和照明(160W点光源 + 150W日光)"""
|
||||
# 计算灯光高度(房间高度减一米)
|
||||
room_height = 3.0
|
||||
light_height = room_height - 1.0
|
||||
|
||||
# 设置摄像机
|
||||
camera = None
|
||||
if "Camera" in bpy.data.objects:
|
||||
camera = bpy.data.objects["Camera"]
|
||||
elif "Isometric Camera" in bpy.data.objects:
|
||||
camera = bpy.data.objects["Isometric Camera"]
|
||||
else:
|
||||
# 创建新摄像机
|
||||
bpy.ops.object.camera_add(location=(7, -7, 5))
|
||||
camera = bpy.context.active_object
|
||||
camera.name = "Isometric Camera"
|
||||
|
||||
# 设置摄像机位置和旋转
|
||||
camera.location = (1, 1, 1.3) # 相机位置
|
||||
camera.rotation_euler = (math.radians(90), math.radians(0), math.radians(45)) # 相机旋转
|
||||
|
||||
# 设置为透视投影
|
||||
camera.data.type = 'PERSP'
|
||||
camera.data.lens = 35.0 # 35mm焦距
|
||||
camera.data.sensor_width = 35.0 # 35mm传感器
|
||||
camera.data.sensor_fit = 'AUTO' # 自动适配
|
||||
|
||||
# 设置为场景的活动摄像机
|
||||
bpy.context.scene.camera = camera
|
||||
print("摄像机设置完成")
|
||||
|
||||
# 隐藏ISO Emission灯光(如果存在)
|
||||
light_objects = ["ISO Emission Left", "ISO Emission Right"]
|
||||
for obj_name in light_objects:
|
||||
if obj_name in bpy.data.objects:
|
||||
light_obj = bpy.data.objects[obj_name]
|
||||
# 隐藏发光对象
|
||||
light_obj.hide_render = True
|
||||
light_obj.hide_viewport = True
|
||||
print(f"已隐藏 {obj_name}")
|
||||
|
||||
# 创建第一个160W点光源(房间中心)
|
||||
# try:
|
||||
# # 计算点光源位置(房间中心上方)
|
||||
# room_length = 4.0 # Y轴
|
||||
# room_width = 8.0 # X轴(4+4)
|
||||
|
||||
# # 第一个点光源位置:房间中心上方
|
||||
# light_x = 0 # X轴中心
|
||||
# light_y = 0 # Y轴中心
|
||||
# light_z = light_height
|
||||
|
||||
# # 创建第一个点光源
|
||||
# bpy.ops.object.light_add(type='POINT', location=(light_x, light_y, light_z))
|
||||
# point_light1 = bpy.context.active_object
|
||||
# point_light1.name = "Main Point Light"
|
||||
# point_light1.data.energy = 160 # 160W
|
||||
|
||||
# # 设置软衰减(简化设置)
|
||||
# point_light1.data.shadow_soft_size = 0.5 # 0.5米半径,产生柔和阴影
|
||||
# point_light1.data.use_shadow = True # 启用阴影
|
||||
|
||||
# print(f"已创建第一个点光源(160W)")
|
||||
# print(f"点光源位置: x={light_x}, y={light_y}, z={light_z}")
|
||||
|
||||
# except Exception as e:
|
||||
# print(f"创建第一个点光源时出错: {e}")
|
||||
|
||||
# 创建第二个150W日光(位置12, 7, 6)
|
||||
try:
|
||||
# 第二个光源位置 - 调整到窗户后方更远的位置
|
||||
light2_x = 12
|
||||
light2_y = 7 # 让日光更远
|
||||
light2_z = 6 # 提高高度
|
||||
|
||||
# 创建日光
|
||||
bpy.ops.object.light_add(type='SUN', location=(light2_x, light2_y, light2_z))
|
||||
sun_light = bpy.context.active_object
|
||||
sun_light.name = "Sun Light"
|
||||
sun_light.data.energy = 20
|
||||
|
||||
# 调整日光旋转角度,让光线更直接地照射窗户
|
||||
sun_light.rotation_euler = (math.radians(0), math.radians(60), math.radians(0)) # 简化旋转角度
|
||||
|
||||
# 设置日光属性(简化设置)
|
||||
sun_light.data.angle = 0.05 # 从0.1改为0.05,让阴影更锐利
|
||||
sun_light.data.use_shadow = True # 启用阴影
|
||||
|
||||
print(f"已创建日光(20W)")
|
||||
print(f"日光位置: x={light2_x}, y={light2_y}, z={light2_z}")
|
||||
print(f"日光旋转: x={-70}°, y={0}°, z={0}°")
|
||||
|
||||
except Exception as e:
|
||||
print(f"创建日光时出错: {e}")
|
||||
|
||||
print("照明设置完成")
|
||||
print("灯光类型: 点光源 + 日光")
|
||||
print(f"衰减类型: 点光源软衰减 + 日光平行光")
|
||||
print(f"阴影设置: 启用,柔和阴影")
|
||||
print(f"主灯光高度: {light_height}米 (房间高度减一米)")
|
||||
print(f"日光位置: (0, 10, 6)米,旋转: (-70°, 0°, 0°)")
|
||||
|
||||
def render_scene():
|
||||
"""渲染场景"""
|
||||
print("开始EEVEE渲染...")
|
||||
|
||||
# 执行渲染
|
||||
bpy.ops.render.render(write_still=True)
|
||||
print(f"EEVEE渲染完成! 图片保存在: {bpy.context.scene.render.filepath}")
|
||||
return bpy.context.scene.render.filepath
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("=" * 60)
|
||||
print("开始自动创建等轴测房间并EEVEE渲染")
|
||||
print("=" * 60)
|
||||
|
||||
try:
|
||||
# 1. 创建房间
|
||||
print("1. 创建等轴测房间...")
|
||||
if not create_isometric_room():
|
||||
print("房间创建失败,停止执行")
|
||||
return False
|
||||
|
||||
# 2. 创建落地窗
|
||||
print("2. 创建archimesh落地窗...")
|
||||
if not create_window_with_archimesh():
|
||||
print("落地窗创建失败,但继续执行")
|
||||
# 不中断执行,窗户创建失败不影响渲染
|
||||
|
||||
# 3. 设置渲染参数(EEVEE)
|
||||
print("3. 设置EEVEE渲染参数...")
|
||||
output_path = setup_render_settings()
|
||||
|
||||
# 4. 设置摄像机和照明
|
||||
print("4. 设置摄像机和照明...")
|
||||
setup_camera_and_lighting()
|
||||
|
||||
# 5. 渲染场景
|
||||
print("5. 开始EEVEE渲染...")
|
||||
final_path = render_scene()
|
||||
|
||||
print("=" * 60)
|
||||
print("EEVEE渲染完成!")
|
||||
print(f"渲染图片已保存到桌面: {final_path}")
|
||||
print("=" * 60)
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"执行过程中出现错误: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
# 执行主函数
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
'''
|
||||
|
||||
|
||||
def check_blender_exists():
|
||||
"""检查Blender是否存在于指定路径"""
|
||||
if not os.path.exists(BLENDER_PATH):
|
||||
print(f"错误: 在路径 '{BLENDER_PATH}' 找不到Blender")
|
||||
print("请检查Blender安装路径是否正确")
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def create_temp_script():
|
||||
"""创建临时的Blender脚本文件"""
|
||||
script_path = os.path.join(os.getcwd(), "temp_simple_blender_script.py")
|
||||
with open(script_path, 'w', encoding='utf-8') as f:
|
||||
f.write(BLENDER_SIMPLE_SCRIPT)
|
||||
return script_path
|
||||
|
||||
|
||||
def launch_blender_simple():
|
||||
"""启动Blender并执行简化版自动化脚本"""
|
||||
if not check_blender_exists():
|
||||
return False
|
||||
|
||||
# 创建临时脚本文件
|
||||
script_path = create_temp_script()
|
||||
|
||||
try:
|
||||
print("正在启动Blender(白模版本)...")
|
||||
print(f"Blender路径: {BLENDER_PATH}")
|
||||
print("特点:")
|
||||
print("- 不设置材质,使用默认白色材质")
|
||||
print("- 快速渲染")
|
||||
print("- 兼容Blender 4.2")
|
||||
print("- 自动保存到桌面")
|
||||
|
||||
# 构建命令行参数
|
||||
cmd = [
|
||||
BLENDER_PATH,
|
||||
"--background", # 后台运行模式
|
||||
"--disable-crash-handler", # 禁用崩溃处理器
|
||||
"--python", script_path # 执行Python脚本
|
||||
]
|
||||
|
||||
print("\n开始执行白模渲染流程...")
|
||||
|
||||
# 启动Blender并等待完成
|
||||
process = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding='utf-8',
|
||||
timeout=300 # 5分钟超时
|
||||
)
|
||||
|
||||
# 输出结果
|
||||
if process.stdout:
|
||||
print("Blender输出:")
|
||||
# 过滤掉重复的错误信息
|
||||
lines = process.stdout.split('\n')
|
||||
filtered_lines = []
|
||||
for line in lines:
|
||||
if not ("AttributeError: 'NoneType' object has no attribute 'idname'" in line or
|
||||
"Error in bpy.app.handlers.depsgraph_update_post" in line):
|
||||
filtered_lines.append(line)
|
||||
print('\n'.join(filtered_lines))
|
||||
|
||||
if process.stderr and not "rotate_tool.py" in process.stderr:
|
||||
print("重要错误信息:")
|
||||
print(process.stderr)
|
||||
|
||||
if process.returncode == 0:
|
||||
print("\n✅ 白模渲染执行成功!")
|
||||
print("白模图片应该已经保存到桌面")
|
||||
return True
|
||||
else:
|
||||
print(f"\n❌ 执行失败,返回码: {process.returncode}")
|
||||
return False
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
print("\n⏰ 执行超时(5分钟),可能Blender仍在运行")
|
||||
print("请检查桌面是否有生成的图片")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"启动Blender时出现错误: {str(e)}")
|
||||
return False
|
||||
|
||||
finally:
|
||||
# 清理临时文件
|
||||
try:
|
||||
if os.path.exists(script_path):
|
||||
os.remove(script_path)
|
||||
print(f"已清理临时文件: {script_path}")
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("=" * 70)
|
||||
print("Blender 4.2兼容版自动化脚本 - 白模渲染")
|
||||
print("=" * 70)
|
||||
print("特点:")
|
||||
print("✓ 移除所有材质设置")
|
||||
print("✓ 使用默认白色材质")
|
||||
print("✓ 快速渲染")
|
||||
print("✓ 兼容Blender 4.2")
|
||||
print("✓ 自动保存到桌面")
|
||||
print("=" * 70)
|
||||
|
||||
success = launch_blender_simple()
|
||||
|
||||
if success:
|
||||
print("\n🎉 白模渲染完成!")
|
||||
print("请检查桌面上的渲染图片")
|
||||
print("图片文件名格式: isometric_room_white_[时间戳].png")
|
||||
else:
|
||||
print("\n❌ 执行失败!")
|
||||
print("可能的解决方案:")
|
||||
print("1. 确保Isometquick插件已正确安装")
|
||||
print("2. 禁用可能冲突的其他插件")
|
||||
print("3. 检查磁盘空间是否足够")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,773 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Blender 4.2兼容版自动化脚本(白模版本)
|
||||
自动创建等轴测房间并渲染白模
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Blender可执行文件路径
|
||||
BLENDER_PATH = r"D:\Program Files\Blender Foundation\blender-4.2.11-windows-x64\blender.exe"
|
||||
|
||||
# 简化版Blender脚本(只渲染白模)
|
||||
BLENDER_SIMPLE_SCRIPT = '''
|
||||
import bpy
|
||||
import bmesh
|
||||
import os
|
||||
import math
|
||||
import time
|
||||
|
||||
def disable_problematic_addons():
|
||||
"""禁用可能导致问题的插件"""
|
||||
try:
|
||||
# 禁用可能冲突的插件
|
||||
problematic_addons = ['bl_tool']
|
||||
for addon in problematic_addons:
|
||||
if addon in bpy.context.preferences.addons:
|
||||
bpy.ops.preferences.addon_disable(module=addon)
|
||||
print(f"已禁用插件: {addon}")
|
||||
except:
|
||||
pass
|
||||
|
||||
def create_isometric_room():
|
||||
"""创建等轴测房间,使用isometric_room_gen插件"""
|
||||
try:
|
||||
# 禁用问题插件
|
||||
#disable_problematic_addons()
|
||||
|
||||
# 清除默认立方体
|
||||
if "Cube" in bpy.data.objects:
|
||||
bpy.data.objects.remove(bpy.data.objects["Cube"], do_unlink=True)
|
||||
|
||||
# 设置3D游标位置到原点
|
||||
bpy.context.scene.cursor.location = (0.0, 0.0, 0.0)
|
||||
|
||||
# 检查isometric_room_gen插件
|
||||
if not hasattr(bpy.context.scene, 'sna_room_width'):
|
||||
print("错误: isometric_room_gen插件未正确加载")
|
||||
return False
|
||||
|
||||
# 设置isometric_room_gen插件参数
|
||||
# 房间设置
|
||||
bpy.context.scene.sna_wall_thickness = 0.10 # 墙体厚度10cm
|
||||
bpy.context.scene.sna_room_width = 5.0 # 房间宽度8米
|
||||
bpy.context.scene.sna_room_depth = 8.0 # 房间深度4米
|
||||
bpy.context.scene.sna_room_height = 3.0 # 房间高度3米
|
||||
|
||||
# 窗户设置 - 不设置窗户
|
||||
bpy.context.scene.sna_windows_enum = 'NONE' # 无窗户
|
||||
|
||||
# 设置房间类型为方形
|
||||
bpy.context.scene.sna_style = 'Square'
|
||||
|
||||
# 找到3D视图区域
|
||||
view3d_area = None
|
||||
for area in bpy.context.screen.areas:
|
||||
if area.type == 'VIEW_3D':
|
||||
view3d_area = area
|
||||
break
|
||||
|
||||
if view3d_area is None:
|
||||
print("错误: 找不到3D视图区域")
|
||||
return False
|
||||
|
||||
# 临时切换到3D视图上下文
|
||||
with bpy.context.temp_override(area=view3d_area):
|
||||
# 使用isometric_room_gen插件创建房间
|
||||
result = bpy.ops.sna.gen_room_1803a()
|
||||
|
||||
if result == {'FINISHED'}:
|
||||
print("等轴测房间创建完成!")
|
||||
print(f"房间尺寸: {bpy.context.scene.sna_room_width}m x {bpy.context.scene.sna_room_depth}m x {bpy.context.scene.sna_room_height}m")
|
||||
print(f"墙体厚度: {bpy.context.scene.sna_wall_thickness}m")
|
||||
|
||||
# 将生成的房间在Z轴上向下移动墙体厚度的距离
|
||||
wall_thickness = bpy.context.scene.sna_wall_thickness
|
||||
|
||||
# 查找生成的房间对象
|
||||
room_object = None
|
||||
for obj in bpy.data.objects:
|
||||
if obj.name == "IRG_IsoRoom_WithCeiling":
|
||||
room_object = obj
|
||||
break
|
||||
|
||||
|
||||
return True
|
||||
else:
|
||||
print("房间创建失败")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"创建房间时出现错误: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
def create_window_with_archimesh():
|
||||
"""使用archimesh插件创建落地窗"""
|
||||
try:
|
||||
print("开始创建archimesh落地窗...")
|
||||
|
||||
# 检查archimesh插件是否可用
|
||||
if not hasattr(bpy.ops.mesh, 'archimesh_winpanel'):
|
||||
print("错误: archimesh插件未正确加载")
|
||||
print("请确保archimesh插件已安装并启用")
|
||||
return False
|
||||
|
||||
# 设置3D游标到原点
|
||||
bpy.context.scene.cursor.location = (0.0, 0.0, 0.0)
|
||||
|
||||
# 找到3D视图区域
|
||||
view3d_area = None
|
||||
for area in bpy.context.screen.areas:
|
||||
if area.type == 'VIEW_3D':
|
||||
view3d_area = area
|
||||
break
|
||||
|
||||
if view3d_area is None:
|
||||
print("错误: 找不到3D视图区域")
|
||||
return False
|
||||
|
||||
# 临时切换到3D视图上下文
|
||||
with bpy.context.temp_override(area=view3d_area):
|
||||
# 创建窗户
|
||||
result = bpy.ops.mesh.archimesh_winpanel()
|
||||
|
||||
if result == {'FINISHED'}:
|
||||
print("archimesh落地窗创建完成!")
|
||||
|
||||
# 等待一帧以确保对象创建完成
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
# 找到刚创建的窗户组对象(Window_Group)
|
||||
window_group = None
|
||||
for obj in bpy.data.objects:
|
||||
if obj.name == "Window_Group":
|
||||
window_group = obj
|
||||
break
|
||||
|
||||
if window_group:
|
||||
print(f"找到窗户组: {window_group.name}")
|
||||
|
||||
# 查找主窗户对象(Window)
|
||||
main_window_obj = None
|
||||
for obj in bpy.data.objects:
|
||||
if obj.name == "Window":
|
||||
main_window_obj = obj
|
||||
break
|
||||
|
||||
if main_window_obj and hasattr(main_window_obj, 'WindowPanelGenerator'):
|
||||
# 获取窗户属性
|
||||
window_props = main_window_obj.WindowPanelGenerator[0]
|
||||
|
||||
print("设置窗户属性...")
|
||||
print(f"找到主窗户对象: {main_window_obj.name}")
|
||||
|
||||
# 设置窗户的基本属性
|
||||
window_props.gen = 4 # 4扇窗户(水平)
|
||||
window_props.yuk = 1 # 1行(垂直)
|
||||
window_props.kl1 = 5 # 外框厚度5cm
|
||||
window_props.kl2 = 5 # 竖框厚度5cm
|
||||
window_props.fk = 2 # 内框厚度2cm
|
||||
|
||||
# 设置窗户尺寸
|
||||
window_props.gnx0 = 80 # 第1扇窗户宽度80cm
|
||||
window_props.gnx1 = 80 # 第2扇窗户宽度80cm
|
||||
window_props.gnx2 = 80 # 第3扇窗户宽度80cm
|
||||
window_props.gnx3 = 80 # 第4扇窗户宽度80cm
|
||||
window_props.gny0 = 250 # 窗户高度250cm(落地窗)
|
||||
|
||||
# 设置窗户类型为平窗
|
||||
window_props.UST = '1' # 平窗
|
||||
window_props.r = 0 # 旋转角度0度
|
||||
|
||||
# 设置窗台
|
||||
window_props.mr = True # 启用窗台
|
||||
window_props.mr1 = 4 # 窗台高度4cm
|
||||
window_props.mr2 = 4 # 窗台深度4cm
|
||||
window_props.mr3 = 20 # 窗台宽度20cm
|
||||
window_props.mr4 = 0 # 窗台延伸0cm
|
||||
|
||||
# 设置材质
|
||||
window_props.mt1 = '1' # 外框材质PVC
|
||||
window_props.mt2 = '1' # 内框材质塑料
|
||||
|
||||
# 设置窗户开启状态
|
||||
window_props.k00 = True # 第1扇窗户开启
|
||||
window_props.k01 = True # 第2扇窗户开启
|
||||
window_props.k02 = True # 第3扇窗户开启
|
||||
window_props.k03 = True # 第4扇窗户开启(如果有的话)
|
||||
|
||||
print(f"窗户属性设置完成:")
|
||||
print(f" - 水平扇数: {window_props.gen}")
|
||||
print(f" - 垂直行数: {window_props.yuk}")
|
||||
print(f" - 外框厚度: {window_props.kl1}cm")
|
||||
print(f" - 竖框厚度: {window_props.kl2}cm")
|
||||
print(f" - 内框厚度: {window_props.fk}cm")
|
||||
print(f" - 窗户高度: {window_props.gny0}cm")
|
||||
print(f" - 窗户类型: 平窗")
|
||||
print(f" - 窗台: 启用")
|
||||
print(f" - 窗户开启状态: k00={window_props.k00}, k01={window_props.k01}, k02={window_props.k02}, k03={window_props.k03}")
|
||||
|
||||
|
||||
# 修复窗户玻璃材质
|
||||
print("修复窗户玻璃材质...")
|
||||
fix_window_glass_material()
|
||||
|
||||
# 等待更新完成
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
# 强制触发更新(如果需要)
|
||||
try:
|
||||
# 重新设置属性以触发更新
|
||||
current_gen = window_props.gen
|
||||
current_gny0 = window_props.gny0
|
||||
|
||||
window_props.gen = 2 # 临时设置为其他值
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
window_props.gen = current_gen # 设置回目标值
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
print("已强制触发窗户更新")
|
||||
|
||||
except Exception as update_error:
|
||||
print(f"强制更新时出现错误: {update_error}")
|
||||
|
||||
# 在修改落地窗全部属性后进行位移和旋转
|
||||
print("开始移动和旋转窗户组...")
|
||||
try:
|
||||
window_group1 = None
|
||||
|
||||
for obj in bpy.data.objects:
|
||||
if obj.name == "Window_Group":
|
||||
window_group1 = obj
|
||||
break
|
||||
|
||||
# 设置位置
|
||||
window_group1.location = (0, 4.0, 0.0)
|
||||
print(f"已将窗户组移动到位置: {window_group1.location}")
|
||||
|
||||
# 设置旋转
|
||||
window_group1.rotation_euler = (math.radians(0), math.radians(0), math.radians(90)) # 旋转(0, 0, 90度)
|
||||
print(f"已将窗户组旋转到: (0°, 0°, 90°)")
|
||||
|
||||
# 强制更新
|
||||
bpy.context.view_layer.update()
|
||||
print("窗户组位移和旋转完成")
|
||||
|
||||
except Exception as move_error:
|
||||
print(f"移动和旋转窗户组时出现错误: {move_error}")
|
||||
|
||||
else:
|
||||
print("警告: 未找到主窗户对象或WindowPanelGenerator属性")
|
||||
# 尝试查找其他可能的窗户属性
|
||||
for obj in bpy.data.objects:
|
||||
if hasattr(obj, 'archimesh_window'):
|
||||
print(f"找到archimesh_window属性在对象: {obj.name}")
|
||||
|
||||
# 打印窗户组的所有子对象信息
|
||||
print("窗户组包含的对象:")
|
||||
for obj in bpy.data.objects:
|
||||
if obj.parent == window_group:
|
||||
print(f" - {obj.name} (位置: {obj.location})")
|
||||
|
||||
|
||||
return True
|
||||
else:
|
||||
print("警告: 未找到Window_Group对象")
|
||||
# 尝试查找其他可能的窗户对象
|
||||
window_objects = [obj for obj in bpy.data.objects if "Window" in obj.name]
|
||||
if window_objects:
|
||||
print("找到的窗户相关对象:")
|
||||
for obj in window_objects:
|
||||
print(f" - {obj.name} (类型: {obj.type})")
|
||||
|
||||
# 尝试移动第一个找到的窗户对象
|
||||
first_window = window_objects[0]
|
||||
first_window.location = (0.0, 1.96, 0.0)
|
||||
print(f"已移动 {first_window.name} 到位置: {first_window.location}")
|
||||
return True
|
||||
else:
|
||||
print("未找到任何窗户相关对象")
|
||||
return False
|
||||
|
||||
|
||||
|
||||
else:
|
||||
print("窗户创建失败")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"创建窗户时出现错误: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
|
||||
def fix_window_glass_material():
|
||||
"""修复窗户玻璃材质,将其改为高透BSDF"""
|
||||
try:
|
||||
print("开始修复窗户玻璃材质...")
|
||||
|
||||
# 查找Window_Group下的Window对象
|
||||
window_group = None
|
||||
for obj in bpy.data.objects:
|
||||
if obj.name == "Window_Group":
|
||||
window_group = obj
|
||||
break
|
||||
|
||||
if not window_group:
|
||||
print("未找到Window_Group对象")
|
||||
return False
|
||||
|
||||
# 查找Window对象(Window_Group的子对象)
|
||||
window_obj = None
|
||||
for obj in bpy.data.objects:
|
||||
if obj.name == "Window" and obj.parent == window_group:
|
||||
window_obj = obj
|
||||
break
|
||||
|
||||
|
||||
if not window_obj:
|
||||
print("未找到Window对象")
|
||||
return False
|
||||
|
||||
print(f"找到Window对象: {window_obj.name}")
|
||||
|
||||
# 检查Window对象的材质
|
||||
if not window_obj.material_slots:
|
||||
print("Window对象没有材质槽")
|
||||
return False
|
||||
|
||||
# 遍历所有材质槽
|
||||
for slot in window_obj.material_slots:
|
||||
if slot.material and "Glass" in slot.material.name:
|
||||
print(f"找到Glass材质: {slot.material.name}")
|
||||
|
||||
# 启用节点编辑
|
||||
slot.material.use_nodes = True
|
||||
|
||||
# 清除所有现有节点
|
||||
slot.material.node_tree.nodes.clear()
|
||||
|
||||
# 创建半透BSDF节点
|
||||
translucent_bsdf = slot.material.node_tree.nodes.new(type='ShaderNodeBsdfTranslucent')
|
||||
translucent_bsdf.location = (0, 0)
|
||||
|
||||
# 设置半透BSDF参数
|
||||
translucent_bsdf.inputs['Color'].default_value = (0.95, 0.98, 1.0, 1.0) # 几乎无色
|
||||
# 半透BSDF节点没有Weight参数,只有Color参数
|
||||
|
||||
# 创建材质输出节点
|
||||
material_output = slot.material.node_tree.nodes.new(type='ShaderNodeOutputMaterial')
|
||||
material_output.location = (300, 0)
|
||||
|
||||
# 连接节点
|
||||
slot.material.node_tree.links.new(
|
||||
translucent_bsdf.outputs['BSDF'],
|
||||
material_output.inputs['Surface']
|
||||
)
|
||||
|
||||
# 设置材质混合模式为Alpha Blend
|
||||
slot.material.blend_method = 'BLEND'
|
||||
|
||||
print(f"已成功修改Glass材质为半透BSDF")
|
||||
print(f" - 半透颜色: 几乎无色")
|
||||
print(f" - 混合模式: Alpha Blend")
|
||||
|
||||
return True
|
||||
|
||||
print("未找到Glass材质")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"修复玻璃材质时出现错误: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
def setup_render_settings():
|
||||
"""设置渲染参数(EEVEE渲染,优化速度)"""
|
||||
scene = bpy.context.scene
|
||||
|
||||
# 设置渲染引擎为EEVEE
|
||||
scene.render.engine = 'BLENDER_EEVEE_NEXT'
|
||||
print("已设置渲染引擎为EEVEE")
|
||||
|
||||
# 设置世界环境
|
||||
if hasattr(scene, 'world') and scene.world:
|
||||
# 启用世界节点
|
||||
scene.world.use_nodes = True
|
||||
|
||||
# 清除现有节点
|
||||
scene.world.node_tree.nodes.clear()
|
||||
|
||||
# 创建自发光节点
|
||||
emission_node = scene.world.node_tree.nodes.new(type='ShaderNodeEmission')
|
||||
emission_node.location = (0, 0)
|
||||
|
||||
# 设置HSV颜色:色相0,饱和度0,明度0.051,Alpha 1
|
||||
# 转换为RGB:HSV(0, 0, 0.051) = RGB(0.051, 0.051, 0.051)
|
||||
emission_node.inputs['Color'].default_value = (0.051, 0.051, 0.051, 1.0) # 深灰色
|
||||
emission_node.inputs['Strength'].default_value = 7.0 # 强度7
|
||||
|
||||
# 创建世界输出节点
|
||||
world_output = scene.world.node_tree.nodes.new(type='ShaderNodeOutputWorld')
|
||||
world_output.location = (300, 0)
|
||||
|
||||
# 连接节点
|
||||
scene.world.node_tree.links.new(
|
||||
emission_node.outputs['Emission'],
|
||||
world_output.inputs['Surface']
|
||||
)
|
||||
|
||||
print("已设置世界环境为自发光")
|
||||
print(f" - 颜色: HSV(0, 0, 0.051) = RGB(0.051, 0.051, 0.051)")
|
||||
print(f" - 强度: 7.0")
|
||||
|
||||
# 设置EEVEE采样(降低采样数提高速度)
|
||||
try:
|
||||
if hasattr(scene.eevee, 'taa_render_samples'):
|
||||
scene.eevee.taa_render_samples = 32 # 从64降低到32
|
||||
print(f"已设置EEVEE渲染采样: {scene.eevee.taa_render_samples}")
|
||||
elif hasattr(scene.eevee, 'taa_samples'):
|
||||
scene.eevee.taa_samples = 32 # 从64降低到32
|
||||
print(f"已设置EEVEE采样: {scene.eevee.taa_samples}")
|
||||
else:
|
||||
print("警告: 无法找到EEVEE采样设置,使用默认值")
|
||||
|
||||
# 启用屏幕空间反射(简化设置)
|
||||
if hasattr(scene.eevee, 'use_ssr'):
|
||||
scene.eevee.use_ssr = True
|
||||
print("已启用屏幕空间反射")
|
||||
|
||||
# 启用环境光遮蔽(简化设置)
|
||||
if hasattr(scene.eevee, 'use_gtao'):
|
||||
scene.eevee.use_gtao = True
|
||||
print("已启用环境光遮蔽")
|
||||
|
||||
# 启用透明渲染(必要)
|
||||
if hasattr(scene.eevee, 'use_transparent'):
|
||||
scene.eevee.use_transparent = True
|
||||
print("已启用透明渲染")
|
||||
|
||||
# 设置透明混合模式(必要)
|
||||
if hasattr(scene.render, 'film_transparent'):
|
||||
scene.render.film_transparent = True
|
||||
print("已启用透明背景")
|
||||
|
||||
except AttributeError as e:
|
||||
print(f"EEVEE设置警告: {e}")
|
||||
print("使用默认EEVEE渲染设置")
|
||||
|
||||
# 设置分辨率
|
||||
scene.render.resolution_x = 1080 # 宽度:1080px
|
||||
scene.render.resolution_y = 2400 # 高度:2400px
|
||||
scene.render.resolution_percentage = 100
|
||||
|
||||
# 设置输出格式
|
||||
scene.render.image_settings.file_format = 'PNG'
|
||||
scene.render.image_settings.color_mode = 'RGBA' # 支持透明
|
||||
|
||||
# 设置输出路径到桌面
|
||||
desktop_path = os.path.join(os.path.expanduser("~"), "Desktop")
|
||||
timestamp = time.strftime("%m%d_%H%M%S") # 日期_时分秒格式
|
||||
filename = f"isometric_{timestamp}.png"
|
||||
scene.render.filepath = os.path.join(desktop_path, filename)
|
||||
|
||||
print(f"EEVEE渲染设置完成,输出路径: {scene.render.filepath}")
|
||||
return scene.render.filepath
|
||||
|
||||
def setup_camera_and_lighting():
|
||||
"""设置摄像机和照明(160W点光源 + 150W日光)"""
|
||||
# 计算灯光高度(房间高度减一米)
|
||||
room_height = 3.0
|
||||
light_height = room_height - 1.0
|
||||
|
||||
# 设置摄像机
|
||||
camera = None
|
||||
if "Camera" in bpy.data.objects:
|
||||
camera = bpy.data.objects["Camera"]
|
||||
elif "Isometric Camera" in bpy.data.objects:
|
||||
camera = bpy.data.objects["Isometric Camera"]
|
||||
else:
|
||||
# 创建新摄像机
|
||||
bpy.ops.object.camera_add(location=(7, -7, 5))
|
||||
camera = bpy.context.active_object
|
||||
camera.name = "Isometric Camera"
|
||||
|
||||
# 设置摄像机位置和旋转
|
||||
# 房间宽度5.0米,相机位置调整为(房间宽度-1, 1, 1.3)
|
||||
room_width = 5.0
|
||||
camera.location = (room_width - 1, 1, 1.3) # 相机位置:(4, 1, 1.3)
|
||||
camera.rotation_euler = (math.radians(90), math.radians(0), math.radians(45)) # 相机旋转
|
||||
|
||||
# 设置为透视投影
|
||||
camera.data.type = 'PERSP'
|
||||
camera.data.lens = 35.0 # 35mm焦距
|
||||
camera.data.sensor_width = 35.0 # 35mm传感器
|
||||
camera.data.sensor_fit = 'AUTO' # 自动适配
|
||||
|
||||
# 设置为场景的活动摄像机
|
||||
bpy.context.scene.camera = camera
|
||||
print("摄像机设置完成")
|
||||
print(f"相机位置: ({camera.location.x}, {camera.location.y}, {camera.location.z})")
|
||||
|
||||
# 隐藏ISO Emission灯光(如果存在)
|
||||
light_objects = ["ISO Emission Left", "ISO Emission Right"]
|
||||
for obj_name in light_objects:
|
||||
if obj_name in bpy.data.objects:
|
||||
light_obj = bpy.data.objects[obj_name]
|
||||
# 隐藏发光对象
|
||||
light_obj.hide_render = True
|
||||
light_obj.hide_viewport = True
|
||||
print(f"已隐藏 {obj_name}")
|
||||
|
||||
# 创建第一个160W点光源(房间中心)
|
||||
# try:
|
||||
# # 计算点光源位置(房间中心上方)
|
||||
# room_length = 4.0 # Y轴
|
||||
# room_width = 8.0 # X轴(4+4)
|
||||
|
||||
# # 第一个点光源位置:房间中心上方
|
||||
# light_x = 0 # X轴中心
|
||||
# light_y = 0 # Y轴中心
|
||||
# light_z = light_height
|
||||
|
||||
# # 创建第一个点光源
|
||||
# bpy.ops.object.light_add(type='POINT', location=(light_x, light_y, light_z))
|
||||
# point_light1 = bpy.context.active_object
|
||||
# point_light1.name = "Main Point Light"
|
||||
# point_light1.data.energy = 160 # 160W
|
||||
|
||||
# # 设置软衰减(简化设置)
|
||||
# point_light1.data.shadow_soft_size = 0.5 # 0.5米半径,产生柔和阴影
|
||||
# point_light1.data.use_shadow = True # 启用阴影
|
||||
|
||||
# print(f"已创建第一个点光源(160W)")
|
||||
# print(f"点光源位置: x={light_x}, y={light_y}, z={light_z}")
|
||||
|
||||
# except Exception as e:
|
||||
# print(f"创建第一个点光源时出错: {e}")
|
||||
|
||||
# 创建第二个150W日光(位置12, 7, 6)
|
||||
try:
|
||||
# 第二个光源位置 - 调整到窗户后方更远的位置
|
||||
light2_x = -6
|
||||
light2_y = 7 # 让日光更远
|
||||
light2_z = 6 # 提高高度
|
||||
|
||||
# 创建日光
|
||||
bpy.ops.object.light_add(type='SUN', location=(light2_x, light2_y, light2_z))
|
||||
sun_light = bpy.context.active_object
|
||||
sun_light.name = "Sun Light"
|
||||
sun_light.data.energy = 20
|
||||
|
||||
# 调整日光旋转角度,让光线更直接地照射窗户
|
||||
sun_light.rotation_euler = (math.radians(0), math.radians(-60), math.radians(0)) # 简化旋转角度
|
||||
|
||||
# 设置日光属性(简化设置)
|
||||
sun_light.data.angle = 0.05 # 从0.1改为0.05,让阴影更锐利
|
||||
sun_light.data.use_shadow = True # 启用阴影
|
||||
|
||||
print(f"已创建日光(20W)")
|
||||
print(f"日光位置: x={light2_x}, y={light2_y}, z={light2_z}")
|
||||
print(f"日光旋转: x={-70}°, y={0}°, z={0}°")
|
||||
|
||||
except Exception as e:
|
||||
print(f"创建日光时出错: {e}")
|
||||
|
||||
print("照明设置完成")
|
||||
print("灯光类型: 点光源 + 日光")
|
||||
print(f"衰减类型: 点光源软衰减 + 日光平行光")
|
||||
print(f"阴影设置: 启用,柔和阴影")
|
||||
print(f"主灯光高度: {light_height}米 (房间高度减一米)")
|
||||
print(f"日光位置: (0, 10, 6)米,旋转: (-70°, 0°, 0°)")
|
||||
|
||||
def render_scene():
|
||||
"""渲染场景"""
|
||||
print("开始EEVEE渲染...")
|
||||
|
||||
# 执行渲染
|
||||
bpy.ops.render.render(write_still=True)
|
||||
print(f"EEVEE渲染完成! 图片保存在: {bpy.context.scene.render.filepath}")
|
||||
return bpy.context.scene.render.filepath
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("=" * 60)
|
||||
print("开始自动创建等轴测房间并EEVEE渲染")
|
||||
print("=" * 60)
|
||||
|
||||
try:
|
||||
# 1. 创建房间
|
||||
print("1. 创建等轴测房间...")
|
||||
if not create_isometric_room():
|
||||
print("房间创建失败,停止执行")
|
||||
return False
|
||||
|
||||
# 2. 创建落地窗
|
||||
print("2. 创建archimesh落地窗...")
|
||||
if not create_window_with_archimesh():
|
||||
print("落地窗创建失败,但继续执行")
|
||||
# 不中断执行,窗户创建失败不影响渲染
|
||||
|
||||
# 3. 设置渲染参数(EEVEE)
|
||||
print("3. 设置EEVEE渲染参数...")
|
||||
output_path = setup_render_settings()
|
||||
|
||||
# 4. 设置摄像机和照明
|
||||
print("4. 设置摄像机和照明...")
|
||||
setup_camera_and_lighting()
|
||||
|
||||
# 5. 渲染场景
|
||||
print("5. 开始EEVEE渲染...")
|
||||
final_path = render_scene()
|
||||
|
||||
print("=" * 60)
|
||||
print("EEVEE渲染完成!")
|
||||
print(f"渲染图片已保存到桌面: {final_path}")
|
||||
print("=" * 60)
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"执行过程中出现错误: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
# 执行主函数
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
'''
|
||||
|
||||
|
||||
def check_blender_exists():
|
||||
"""检查Blender是否存在于指定路径"""
|
||||
if not os.path.exists(BLENDER_PATH):
|
||||
print(f"错误: 在路径 '{BLENDER_PATH}' 找不到Blender")
|
||||
print("请检查Blender安装路径是否正确")
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def create_temp_script():
|
||||
"""创建临时的Blender脚本文件"""
|
||||
script_path = os.path.join(os.getcwd(), "temp_simple_blender_script.py")
|
||||
with open(script_path, 'w', encoding='utf-8') as f:
|
||||
f.write(BLENDER_SIMPLE_SCRIPT)
|
||||
return script_path
|
||||
|
||||
|
||||
def launch_blender_simple():
|
||||
"""启动Blender并执行简化版自动化脚本"""
|
||||
if not check_blender_exists():
|
||||
return False
|
||||
|
||||
# 创建临时脚本文件
|
||||
script_path = create_temp_script()
|
||||
|
||||
try:
|
||||
print("正在启动Blender(白模版本)...")
|
||||
print(f"Blender路径: {BLENDER_PATH}")
|
||||
print("特点:")
|
||||
print("- 不设置材质,使用默认白色材质")
|
||||
print("- 快速渲染")
|
||||
print("- 兼容Blender 4.2")
|
||||
print("- 自动保存到桌面")
|
||||
|
||||
# 构建命令行参数
|
||||
cmd = [
|
||||
BLENDER_PATH,
|
||||
"--background", # 后台运行模式
|
||||
"--disable-crash-handler", # 禁用崩溃处理器
|
||||
"--python", script_path # 执行Python脚本
|
||||
]
|
||||
|
||||
print("\n开始执行白模渲染流程...")
|
||||
|
||||
# 启动Blender并等待完成
|
||||
process = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding='utf-8',
|
||||
timeout=300 # 5分钟超时
|
||||
)
|
||||
|
||||
# 输出结果
|
||||
if process.stdout:
|
||||
print("Blender输出:")
|
||||
# 过滤掉重复的错误信息
|
||||
lines = process.stdout.split('\n')
|
||||
filtered_lines = []
|
||||
for line in lines:
|
||||
if not ("AttributeError: 'NoneType' object has no attribute 'idname'" in line or
|
||||
"Error in bpy.app.handlers.depsgraph_update_post" in line):
|
||||
filtered_lines.append(line)
|
||||
print('\n'.join(filtered_lines))
|
||||
|
||||
if process.stderr and not "rotate_tool.py" in process.stderr:
|
||||
print("重要错误信息:")
|
||||
print(process.stderr)
|
||||
|
||||
if process.returncode == 0:
|
||||
print("\n✅ 白模渲染执行成功!")
|
||||
print("白模图片应该已经保存到桌面")
|
||||
return True
|
||||
else:
|
||||
print(f"\n❌ 执行失败,返回码: {process.returncode}")
|
||||
return False
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
print("\n⏰ 执行超时(5分钟),可能Blender仍在运行")
|
||||
print("请检查桌面是否有生成的图片")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"启动Blender时出现错误: {str(e)}")
|
||||
return False
|
||||
|
||||
finally:
|
||||
# 清理临时文件
|
||||
try:
|
||||
if os.path.exists(script_path):
|
||||
os.remove(script_path)
|
||||
print(f"已清理临时文件: {script_path}")
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("=" * 70)
|
||||
print("Blender 4.2兼容版自动化脚本 - 白模渲染")
|
||||
print("=" * 70)
|
||||
print("特点:")
|
||||
print("✓ 移除所有材质设置")
|
||||
print("✓ 使用默认白色材质")
|
||||
print("✓ 快速渲染")
|
||||
print("✓ 兼容Blender 4.2")
|
||||
print("✓ 自动保存到桌面")
|
||||
print("=" * 70)
|
||||
|
||||
success = launch_blender_simple()
|
||||
|
||||
if success:
|
||||
print("\n🎉 白模渲染完成!")
|
||||
print("请检查桌面上的渲染图片")
|
||||
print("图片文件名格式: isometric_room_white_[时间戳].png")
|
||||
else:
|
||||
print("\n❌ 执行失败!")
|
||||
print("可能的解决方案:")
|
||||
print("1. 确保Isometquick插件已正确安装")
|
||||
print("2. 禁用可能冲突的其他插件")
|
||||
print("3. 检查磁盘空间是否足够")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,976 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Blender 4.2兼容版自动化脚本(白模版本)
|
||||
自动创建等轴测房间并渲染白模
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Blender可执行文件路径
|
||||
BLENDER_PATH = r"D:\Program Files\Blender Foundation\blender-4.2.11-windows-x64\blender.exe"
|
||||
|
||||
# 简化版Blender脚本(只渲染白模)
|
||||
BLENDER_SIMPLE_SCRIPT = '''
|
||||
import bpy
|
||||
import bmesh
|
||||
import os
|
||||
import math
|
||||
import time
|
||||
|
||||
def disable_problematic_addons():
|
||||
"""禁用可能导致问题的插件"""
|
||||
try:
|
||||
# 禁用可能冲突的插件
|
||||
problematic_addons = ['bl_tool']
|
||||
for addon in problematic_addons:
|
||||
if addon in bpy.context.preferences.addons:
|
||||
bpy.ops.preferences.addon_disable(module=addon)
|
||||
print(f"已禁用插件: {addon}")
|
||||
except:
|
||||
pass
|
||||
|
||||
def create_isometric_room():
|
||||
"""创建等轴测房间,使用isometric_room_gen插件"""
|
||||
try:
|
||||
# 禁用问题插件
|
||||
#disable_problematic_addons()
|
||||
|
||||
# 清除默认立方体
|
||||
if "Cube" in bpy.data.objects:
|
||||
bpy.data.objects.remove(bpy.data.objects["Cube"], do_unlink=True)
|
||||
|
||||
# 设置3D游标位置到原点
|
||||
bpy.context.scene.cursor.location = (0.0, 0.0, 0.0)
|
||||
|
||||
# 检查isometric_room_gen插件
|
||||
if not hasattr(bpy.context.scene, 'sna_room_width'):
|
||||
print("错误: isometric_room_gen插件未正确加载")
|
||||
return False
|
||||
|
||||
# 设置isometric_room_gen插件参数
|
||||
# 房间设置
|
||||
bpy.context.scene.sna_wall_thickness = 0.10 # 墙体厚度10cm
|
||||
bpy.context.scene.sna_room_width = 5.0 # 房间宽度8米
|
||||
bpy.context.scene.sna_room_depth = 8.0 # 房间深度4米
|
||||
bpy.context.scene.sna_room_height = 3.0 # 房间高度3米
|
||||
|
||||
# 窗户设置 - 不设置窗户
|
||||
bpy.context.scene.sna_windows_enum = 'NONE' # 无窗户
|
||||
|
||||
# 设置房间类型为方形
|
||||
bpy.context.scene.sna_style = 'Square'
|
||||
|
||||
# 设置拱门参数
|
||||
bpy.context.scene.sna_arch_settings = True # 启用拱门设置
|
||||
bpy.context.scene.sna_arch_placement = 'NONE' # 设置为后墙拱门BACK 不设置拱门NONE
|
||||
bpy.context.scene.sna_arch_width = 1.2 # 拱门宽度1.2米
|
||||
bpy.context.scene.sna_arch_height = 2.4 # 拱门高度2.4米
|
||||
bpy.context.scene.sna_arch_thickness = 0.10 # 拱门厚度0.1米
|
||||
|
||||
# 找到3D视图区域
|
||||
view3d_area = None
|
||||
for area in bpy.context.screen.areas:
|
||||
if area.type == 'VIEW_3D':
|
||||
view3d_area = area
|
||||
break
|
||||
|
||||
if view3d_area is None:
|
||||
print("错误: 找不到3D视图区域")
|
||||
return False
|
||||
|
||||
# 临时切换到3D视图上下文
|
||||
with bpy.context.temp_override(area=view3d_area):
|
||||
# 使用isometric_room_gen插件创建房间
|
||||
result = bpy.ops.sna.gen_room_1803a()
|
||||
|
||||
if result == {'FINISHED'}:
|
||||
print("等轴测房间创建完成!")
|
||||
print(f"房间尺寸: {bpy.context.scene.sna_room_width}m x {bpy.context.scene.sna_room_depth}m x {bpy.context.scene.sna_room_height}m")
|
||||
print(f"墙体厚度: {bpy.context.scene.sna_wall_thickness}m")
|
||||
|
||||
# 将生成的房间在Z轴上向下移动墙体厚度的距离
|
||||
wall_thickness = bpy.context.scene.sna_wall_thickness
|
||||
|
||||
# 查找生成的房间对象
|
||||
room_object = None
|
||||
for obj in bpy.data.objects:
|
||||
if obj.name == "IRG_IsoRoom_WithCeiling":
|
||||
room_object = obj
|
||||
break
|
||||
|
||||
|
||||
return True
|
||||
else:
|
||||
print("房间创建失败")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"创建房间时出现错误: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
|
||||
def create_door_with_archimesh():
|
||||
"""使用archimesh插件创建门"""
|
||||
try:
|
||||
print("开始创建archimesh门...")
|
||||
|
||||
# 检查archimesh插件是否可用
|
||||
if not hasattr(bpy.ops.mesh, 'archimesh_door'):
|
||||
print("错误: archimesh插件未正确加载")
|
||||
print("请确保archimesh插件已安装并启用")
|
||||
return False
|
||||
|
||||
# 设置3D游标到原点
|
||||
bpy.context.scene.cursor.location = (0.0, 0.0, 0.0)
|
||||
|
||||
# 找到3D视图区域
|
||||
view3d_area = None
|
||||
for area in bpy.context.screen.areas:
|
||||
if area.type == 'VIEW_3D':
|
||||
view3d_area = area
|
||||
break
|
||||
|
||||
if view3d_area is None:
|
||||
print("错误: 找不到3D视图区域")
|
||||
return False
|
||||
|
||||
# 临时切换到3D视图上下文
|
||||
with bpy.context.temp_override(area=view3d_area):
|
||||
# 先创建门(使用默认参数)
|
||||
result = bpy.ops.mesh.archimesh_door()
|
||||
|
||||
if result == {'FINISHED'}:
|
||||
print("archimesh门创建完成!")
|
||||
|
||||
# 等待一帧以确保对象创建完成
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
# 找到刚创建的门组对象(Door_Group)
|
||||
door_group = None
|
||||
for obj in bpy.data.objects:
|
||||
if obj.name == "Door_Group":
|
||||
door_group = obj
|
||||
break
|
||||
|
||||
if door_group:
|
||||
print(f"找到门组: {door_group.name}")
|
||||
|
||||
# 查找主门对象(DoorFrame)
|
||||
main_door_obj = None
|
||||
for obj in bpy.data.objects:
|
||||
if obj.name == "DoorFrame":
|
||||
main_door_obj = obj
|
||||
break
|
||||
|
||||
if main_door_obj and hasattr(main_door_obj, 'DoorObjectGenerator'):
|
||||
# 获取门属性
|
||||
door_props = main_door_obj.DoorObjectGenerator[0]
|
||||
|
||||
print("设置门属性...")
|
||||
print(f"找到主门对象: {main_door_obj.name}")
|
||||
|
||||
# 先设置基本属性
|
||||
door_props.frame_width = 1.0 # 门框宽度
|
||||
door_props.frame_height = 2.1 # 门框高度
|
||||
door_props.frame_thick = 0.08 # 门框厚度
|
||||
door_props.openside = '1' # 右侧开门
|
||||
|
||||
# 设置门模型和把手(这些会触发update_object回调)
|
||||
print("设置门模型为2...")
|
||||
door_props.model = '2' # 门模型2
|
||||
|
||||
print("设置门把手为1...")
|
||||
door_props.handle = '1' # 门把手1
|
||||
|
||||
print(f"门属性设置完成:")
|
||||
print(f" - 门框宽度: {door_props.frame_width}")
|
||||
print(f" - 门框高度: {door_props.frame_height}")
|
||||
print(f" - 门框厚度: {door_props.frame_thick}")
|
||||
print(f" - 开门方向: {door_props.openside}")
|
||||
print(f" - 门模型: {door_props.model}")
|
||||
print(f" - 门把手: {door_props.handle}")
|
||||
|
||||
# 等待更新完成
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
# 验证设置是否生效
|
||||
print("验证门属性设置:")
|
||||
print(f" - 当前门模型: {door_props.model}")
|
||||
print(f" - 当前门把手: {door_props.handle}")
|
||||
|
||||
# 强制触发更新(如果需要)
|
||||
try:
|
||||
# 重新设置属性以触发更新
|
||||
current_model = door_props.model
|
||||
current_handle = door_props.handle
|
||||
|
||||
door_props.model = '1' # 临时设置为其他值
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
door_props.model = current_model # 设置回目标值
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
print("已强制触发门模型更新")
|
||||
|
||||
except Exception as update_error:
|
||||
print(f"强制更新时出现错误: {update_error}")
|
||||
|
||||
# 在修改门全部属性后进行位移和旋转
|
||||
print("开始移动和旋转门组...")
|
||||
try:
|
||||
door_group1 = None
|
||||
for obj in bpy.data.objects:
|
||||
if obj.name == "Door_Group":
|
||||
door_group1 = obj
|
||||
break
|
||||
# 设置位置
|
||||
door_group1.location = (0.0, 6.0, 0.0)
|
||||
print(f"已将门组移动到位置: {door_group1.location}")
|
||||
|
||||
# 设置旋转
|
||||
door_group1.rotation_euler = (math.radians(0), math.radians(0), math.radians(90)) # 旋转(0, 0, 90度)
|
||||
print(f"已将门组旋转到: (0°, 0°, 90°)")
|
||||
|
||||
# 强制更新
|
||||
bpy.context.view_layer.update()
|
||||
print("门组位移和旋转完成")
|
||||
|
||||
except Exception as move_error:
|
||||
print(f"移动和旋转门组时出现错误: {move_error}")
|
||||
|
||||
else:
|
||||
print("警告: 未找到主门对象或DoorObjectGenerator属性")
|
||||
# 尝试查找其他可能的门属性
|
||||
for obj in bpy.data.objects:
|
||||
if hasattr(obj, 'archimesh_door'):
|
||||
print(f"找到archimesh_door属性在对象: {obj.name}")
|
||||
|
||||
# 打印门组的所有子对象信息
|
||||
print("门组包含的对象:")
|
||||
for obj in bpy.data.objects:
|
||||
if obj.parent == door_group:
|
||||
print(f" - {obj.name} (位置: {obj.location})")
|
||||
|
||||
return True
|
||||
else:
|
||||
print("警告: 未找到Door_Group对象")
|
||||
# 尝试查找其他可能的门对象
|
||||
door_objects = [obj for obj in bpy.data.objects if "Door" in obj.name]
|
||||
if door_objects:
|
||||
print("找到的门相关对象:")
|
||||
for obj in door_objects:
|
||||
print(f" - {obj.name} (类型: {obj.type})")
|
||||
|
||||
# 尝试移动第一个找到的门对象
|
||||
first_door = door_objects[0]
|
||||
first_door.location = (0.0, 2.0, 0.0)
|
||||
print(f"已移动 {first_door.name} 到位置: {first_door.location}")
|
||||
return True
|
||||
else:
|
||||
print("未找到任何门相关对象")
|
||||
return False
|
||||
else:
|
||||
print("门创建失败")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"创建门时出现错误: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
|
||||
def create_window_with_archimesh():
|
||||
"""使用archimesh插件创建落地窗"""
|
||||
try:
|
||||
print("开始创建archimesh落地窗...")
|
||||
|
||||
# 检查archimesh插件是否可用
|
||||
if not hasattr(bpy.ops.mesh, 'archimesh_winpanel'):
|
||||
print("错误: archimesh插件未正确加载")
|
||||
print("请确保archimesh插件已安装并启用")
|
||||
return False
|
||||
|
||||
# 设置3D游标到原点
|
||||
bpy.context.scene.cursor.location = (0.0, 0.0, 0.0)
|
||||
|
||||
# 找到3D视图区域
|
||||
view3d_area = None
|
||||
for area in bpy.context.screen.areas:
|
||||
if area.type == 'VIEW_3D':
|
||||
view3d_area = area
|
||||
break
|
||||
|
||||
if view3d_area is None:
|
||||
print("错误: 找不到3D视图区域")
|
||||
return False
|
||||
|
||||
# 临时切换到3D视图上下文
|
||||
with bpy.context.temp_override(area=view3d_area):
|
||||
# 创建窗户
|
||||
result = bpy.ops.mesh.archimesh_winpanel()
|
||||
|
||||
if result == {'FINISHED'}:
|
||||
print("archimesh落地窗创建完成!")
|
||||
|
||||
# 等待一帧以确保对象创建完成
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
# 找到刚创建的窗户组对象(Window_Group)
|
||||
window_group = None
|
||||
for obj in bpy.data.objects:
|
||||
if obj.name == "Window_Group":
|
||||
window_group = obj
|
||||
break
|
||||
|
||||
if window_group:
|
||||
print(f"找到窗户组: {window_group.name}")
|
||||
|
||||
# 查找主窗户对象(Window)
|
||||
main_window_obj = None
|
||||
for obj in bpy.data.objects:
|
||||
if obj.name == "Window":
|
||||
main_window_obj = obj
|
||||
break
|
||||
|
||||
if main_window_obj and hasattr(main_window_obj, 'WindowPanelGenerator'):
|
||||
# 获取窗户属性
|
||||
window_props = main_window_obj.WindowPanelGenerator[0]
|
||||
|
||||
print("设置窗户属性...")
|
||||
print(f"找到主窗户对象: {main_window_obj.name}")
|
||||
|
||||
# 设置窗户的基本属性
|
||||
window_props.gen = 4 # 4扇窗户(水平)
|
||||
window_props.yuk = 1 # 1行(垂直)
|
||||
window_props.kl1 = 5 # 外框厚度5cm
|
||||
window_props.kl2 = 5 # 竖框厚度5cm
|
||||
window_props.fk = 2 # 内框厚度2cm
|
||||
|
||||
# 设置窗户尺寸
|
||||
window_props.gnx0 = 80 # 第1扇窗户宽度80cm
|
||||
window_props.gnx1 = 80 # 第2扇窗户宽度80cm
|
||||
window_props.gnx2 = 80 # 第3扇窗户宽度80cm
|
||||
window_props.gnx3 = 80 # 第4扇窗户宽度80cm
|
||||
window_props.gny0 = 250 # 窗户高度250cm(落地窗)
|
||||
|
||||
# 设置窗户类型为平窗
|
||||
window_props.UST = '1' # 平窗
|
||||
window_props.r = 0 # 旋转角度0度
|
||||
|
||||
# 设置窗台
|
||||
window_props.mr = False # 启用窗台
|
||||
#window_props.mr1 = 4 # 窗台高度4cm
|
||||
#window_props.mr2 = 4 # 窗台深度4cm
|
||||
#window_props.mr3 = 20 # 窗台宽度20cm
|
||||
#window_props.mr4 = 0 # 窗台延伸0cm
|
||||
|
||||
# 设置材质
|
||||
window_props.mt1 = '1' # 外框材质PVC
|
||||
window_props.mt2 = '1' # 内框材质塑料
|
||||
|
||||
# 设置窗户开启状态
|
||||
window_props.k00 = True # 第1扇窗户开启
|
||||
window_props.k01 = True # 第2扇窗户开启
|
||||
window_props.k02 = True # 第3扇窗户开启
|
||||
window_props.k03 = True # 第4扇窗户开启(如果有的话)
|
||||
|
||||
print(f"窗户属性设置完成:")
|
||||
print(f" - 水平扇数: {window_props.gen}")
|
||||
print(f" - 垂直行数: {window_props.yuk}")
|
||||
print(f" - 外框厚度: {window_props.kl1}cm")
|
||||
print(f" - 竖框厚度: {window_props.kl2}cm")
|
||||
print(f" - 内框厚度: {window_props.fk}cm")
|
||||
print(f" - 窗户高度: {window_props.gny0}cm")
|
||||
print(f" - 窗户类型: 平窗")
|
||||
print(f" - 窗台: 启用")
|
||||
print(f" - 窗户开启状态: k00={window_props.k00}, k01={window_props.k01}, k02={window_props.k02}, k03={window_props.k03}")
|
||||
|
||||
|
||||
# 修复窗户玻璃材质
|
||||
print("修复窗户玻璃材质...")
|
||||
fix_window_glass_material()
|
||||
|
||||
# 等待更新完成
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
# 强制触发更新(如果需要)
|
||||
try:
|
||||
# 重新设置属性以触发更新
|
||||
current_gen = window_props.gen
|
||||
current_gny0 = window_props.gny0
|
||||
|
||||
window_props.gen = 2 # 临时设置为其他值
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
window_props.gen = current_gen # 设置回目标值
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
print("已强制触发窗户更新")
|
||||
|
||||
except Exception as update_error:
|
||||
print(f"强制更新时出现错误: {update_error}")
|
||||
|
||||
# 在修改落地窗全部属性后进行位移和旋转
|
||||
print("开始移动和旋转窗户组...")
|
||||
try:
|
||||
window_group1 = None
|
||||
|
||||
for obj in bpy.data.objects:
|
||||
if obj.name == "Window_Group":
|
||||
window_group1 = obj
|
||||
break
|
||||
|
||||
# 设置位置
|
||||
window_group1.location = (0, 4.0, 0.0)
|
||||
print(f"已将窗户组移动到位置: {window_group1.location}")
|
||||
|
||||
# 设置旋转
|
||||
window_group1.rotation_euler = (math.radians(0), math.radians(0), math.radians(90)) # 旋转(0, 0, 90度)
|
||||
print(f"已将窗户组旋转到: (0°, 0°, 90°)")
|
||||
|
||||
# 强制更新
|
||||
bpy.context.view_layer.update()
|
||||
print("窗户组位移和旋转完成")
|
||||
|
||||
except Exception as move_error:
|
||||
print(f"移动和旋转窗户组时出现错误: {move_error}")
|
||||
|
||||
else:
|
||||
print("警告: 未找到主窗户对象或WindowPanelGenerator属性")
|
||||
# 尝试查找其他可能的窗户属性
|
||||
for obj in bpy.data.objects:
|
||||
if hasattr(obj, 'archimesh_window'):
|
||||
print(f"找到archimesh_window属性在对象: {obj.name}")
|
||||
|
||||
# 打印窗户组的所有子对象信息
|
||||
print("窗户组包含的对象:")
|
||||
for obj in bpy.data.objects:
|
||||
if obj.parent == window_group:
|
||||
print(f" - {obj.name} (位置: {obj.location})")
|
||||
|
||||
|
||||
return True
|
||||
else:
|
||||
print("警告: 未找到Window_Group对象")
|
||||
# 尝试查找其他可能的窗户对象
|
||||
window_objects = [obj for obj in bpy.data.objects if "Window" in obj.name]
|
||||
if window_objects:
|
||||
print("找到的窗户相关对象:")
|
||||
for obj in window_objects:
|
||||
print(f" - {obj.name} (类型: {obj.type})")
|
||||
|
||||
# 尝试移动第一个找到的窗户对象
|
||||
first_window = window_objects[0]
|
||||
first_window.location = (0.0, 1.96, 0.0)
|
||||
print(f"已移动 {first_window.name} 到位置: {first_window.location}")
|
||||
return True
|
||||
else:
|
||||
print("未找到任何窗户相关对象")
|
||||
return False
|
||||
|
||||
|
||||
|
||||
else:
|
||||
print("窗户创建失败")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"创建窗户时出现错误: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
|
||||
def fix_window_glass_material():
|
||||
"""修复窗户玻璃材质,将其改为高透BSDF"""
|
||||
try:
|
||||
print("开始修复窗户玻璃材质...")
|
||||
|
||||
# 查找Window_Group下的Window对象
|
||||
window_group = None
|
||||
for obj in bpy.data.objects:
|
||||
if obj.name == "Window_Group":
|
||||
window_group = obj
|
||||
break
|
||||
|
||||
if not window_group:
|
||||
print("未找到Window_Group对象")
|
||||
return False
|
||||
|
||||
# 查找Window对象(Window_Group的子对象)
|
||||
window_obj = None
|
||||
for obj in bpy.data.objects:
|
||||
if obj.name == "Window" and obj.parent == window_group:
|
||||
window_obj = obj
|
||||
break
|
||||
|
||||
|
||||
if not window_obj:
|
||||
print("未找到Window对象")
|
||||
return False
|
||||
|
||||
print(f"找到Window对象: {window_obj.name}")
|
||||
|
||||
# 检查Window对象的材质
|
||||
if not window_obj.material_slots:
|
||||
print("Window对象没有材质槽")
|
||||
return False
|
||||
|
||||
# 遍历所有材质槽
|
||||
for slot in window_obj.material_slots:
|
||||
if slot.material and "Glass" in slot.material.name:
|
||||
print(f"找到Glass材质: {slot.material.name}")
|
||||
|
||||
# 启用节点编辑
|
||||
slot.material.use_nodes = True
|
||||
|
||||
# 清除所有现有节点
|
||||
slot.material.node_tree.nodes.clear()
|
||||
|
||||
# 创建半透BSDF节点
|
||||
translucent_bsdf = slot.material.node_tree.nodes.new(type='ShaderNodeBsdfTranslucent')
|
||||
translucent_bsdf.location = (0, 0)
|
||||
|
||||
# 设置半透BSDF参数
|
||||
translucent_bsdf.inputs['Color'].default_value = (0.95, 0.98, 1.0, 1.0) # 几乎无色
|
||||
# 半透BSDF节点没有Weight参数,只有Color参数
|
||||
|
||||
# 创建材质输出节点
|
||||
material_output = slot.material.node_tree.nodes.new(type='ShaderNodeOutputMaterial')
|
||||
material_output.location = (300, 0)
|
||||
|
||||
# 连接节点
|
||||
slot.material.node_tree.links.new(
|
||||
translucent_bsdf.outputs['BSDF'],
|
||||
material_output.inputs['Surface']
|
||||
)
|
||||
|
||||
# 设置材质混合模式为Alpha Blend
|
||||
slot.material.blend_method = 'BLEND'
|
||||
|
||||
print(f"已成功修改Glass材质为半透BSDF")
|
||||
print(f" - 半透颜色: 几乎无色")
|
||||
print(f" - 混合模式: Alpha Blend")
|
||||
|
||||
return True
|
||||
|
||||
print("未找到Glass材质")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"修复玻璃材质时出现错误: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
def setup_render_settings():
|
||||
"""设置渲染参数(EEVEE渲染,优化速度)"""
|
||||
scene = bpy.context.scene
|
||||
|
||||
# 设置渲染引擎为EEVEE
|
||||
scene.render.engine = 'BLENDER_EEVEE_NEXT'
|
||||
print("已设置渲染引擎为EEVEE")
|
||||
|
||||
# 启用Freestyle线条渲染
|
||||
scene.render.use_freestyle = True
|
||||
print("已启用Freestyle线条渲染")
|
||||
|
||||
# 配置Freestyle线条设置
|
||||
if hasattr(scene, 'view_layers'):
|
||||
view_layer = scene.view_layers[0] # 获取第一个视图层
|
||||
view_layer.use_freestyle = True
|
||||
|
||||
# 获取Freestyle线条设置
|
||||
freestyle = view_layer.freestyle_settings
|
||||
|
||||
# 启用线条渲染
|
||||
freestyle.use_smoothness = True
|
||||
freestyle.use_culling = True
|
||||
|
||||
# 设置线条宽度 - 使用正确的API 没有生效
|
||||
|
||||
|
||||
# 设置世界环境
|
||||
if hasattr(scene, 'world') and scene.world:
|
||||
# 启用世界节点
|
||||
scene.world.use_nodes = True
|
||||
|
||||
# 清除现有节点
|
||||
scene.world.node_tree.nodes.clear()
|
||||
|
||||
# 创建自发光节点
|
||||
emission_node = scene.world.node_tree.nodes.new(type='ShaderNodeEmission')
|
||||
emission_node.location = (0, 0)
|
||||
|
||||
# 设置HSV颜色:色相0,饱和度0,明度0.051,Alpha 1
|
||||
# 转换为RGB:HSV(0, 0, 0.051) = RGB(0.051, 0.051, 0.051)
|
||||
emission_node.inputs['Color'].default_value = (0.051, 0.051, 0.051, 1.0) # 深灰色
|
||||
emission_node.inputs['Strength'].default_value = 8.0 # 强度8
|
||||
|
||||
# 创建世界输出节点
|
||||
world_output = scene.world.node_tree.nodes.new(type='ShaderNodeOutputWorld')
|
||||
world_output.location = (300, 0)
|
||||
|
||||
# 连接节点
|
||||
scene.world.node_tree.links.new(
|
||||
emission_node.outputs['Emission'],
|
||||
world_output.inputs['Surface']
|
||||
)
|
||||
|
||||
print("已设置世界环境为自发光")
|
||||
print(f" - 颜色: HSV(0, 0, 0.051) = RGB(0.051, 0.051, 0.051)")
|
||||
print(f" - 强度: 8.0")
|
||||
|
||||
# 设置EEVEE采样(降低采样数提高速度)
|
||||
try:
|
||||
if hasattr(scene.eevee, 'taa_render_samples'):
|
||||
scene.eevee.taa_render_samples = 32 # 从64降低到32
|
||||
print(f"已设置EEVEE渲染采样: {scene.eevee.taa_render_samples}")
|
||||
elif hasattr(scene.eevee, 'taa_samples'):
|
||||
scene.eevee.taa_samples = 32 # 从64降低到32
|
||||
print(f"已设置EEVEE采样: {scene.eevee.taa_samples}")
|
||||
else:
|
||||
print("警告: 无法找到EEVEE采样设置,使用默认值")
|
||||
|
||||
# 启用屏幕空间反射(简化设置)
|
||||
if hasattr(scene.eevee, 'use_ssr'):
|
||||
scene.eevee.use_ssr = True
|
||||
print("已启用屏幕空间反射")
|
||||
|
||||
# 启用环境光遮蔽(简化设置)
|
||||
if hasattr(scene.eevee, 'use_gtao'):
|
||||
scene.eevee.use_gtao = True
|
||||
print("已启用环境光遮蔽")
|
||||
|
||||
# 启用透明渲染(必要)
|
||||
if hasattr(scene.eevee, 'use_transparent'):
|
||||
scene.eevee.use_transparent = True
|
||||
print("已启用透明渲染")
|
||||
|
||||
# 设置透明混合模式(必要)
|
||||
if hasattr(scene.render, 'film_transparent'):
|
||||
scene.render.film_transparent = True
|
||||
print("已启用透明背景")
|
||||
|
||||
except AttributeError as e:
|
||||
print(f"EEVEE设置警告: {e}")
|
||||
print("使用默认EEVEE渲染设置")
|
||||
|
||||
# 设置分辨率
|
||||
scene.render.resolution_x = 1080 # 宽度:1080px
|
||||
scene.render.resolution_y = 2400 # 高度:2400px
|
||||
scene.render.resolution_percentage = 100
|
||||
|
||||
# 设置输出格式
|
||||
scene.render.image_settings.file_format = 'PNG'
|
||||
scene.render.image_settings.color_mode = 'RGBA' # 支持透明
|
||||
|
||||
# 设置输出路径到桌面
|
||||
desktop_path = os.path.join(os.path.expanduser("~"), "Desktop")
|
||||
timestamp = time.strftime("%m%d_%H%M%S") # 日期_时分秒格式
|
||||
filename = f"isometric_{timestamp}.png"
|
||||
scene.render.filepath = os.path.join(desktop_path, filename)
|
||||
|
||||
print(f"EEVEE渲染设置完成,输出路径: {scene.render.filepath}")
|
||||
return scene.render.filepath
|
||||
|
||||
def setup_camera_and_lighting():
|
||||
"""设置摄像机和照明(160W点光源 + 150W日光)"""
|
||||
# 计算灯光高度(房间高度减一米)
|
||||
room_height = 3.0
|
||||
light_height = room_height - 1.0
|
||||
|
||||
# 设置摄像机
|
||||
camera = None
|
||||
if "Camera" in bpy.data.objects:
|
||||
camera = bpy.data.objects["Camera"]
|
||||
elif "Isometric Camera" in bpy.data.objects:
|
||||
camera = bpy.data.objects["Isometric Camera"]
|
||||
else:
|
||||
# 创建新摄像机
|
||||
bpy.ops.object.camera_add(location=(7, -7, 5))
|
||||
camera = bpy.context.active_object
|
||||
camera.name = "Isometric Camera"
|
||||
|
||||
# 设置摄像机位置和旋转
|
||||
# 房间宽度5.0米,相机位置调整为(房间宽度-1, 1, 1.3)
|
||||
room_width = 5.0
|
||||
camera.location = (room_width - 1, 1, 1.3) # 相机位置:(4, 1, 1.3)
|
||||
camera.rotation_euler = (math.radians(90), math.radians(0), math.radians(30)) # 相机旋转
|
||||
|
||||
# 设置为透视投影
|
||||
camera.data.type = 'PERSP'
|
||||
camera.data.lens = 35.0 # 35mm焦距
|
||||
camera.data.sensor_width = 35.0 # 35mm传感器
|
||||
camera.data.sensor_fit = 'AUTO' # 自动适配
|
||||
|
||||
# 设置为场景的活动摄像机
|
||||
bpy.context.scene.camera = camera
|
||||
print("摄像机设置完成")
|
||||
print(f"相机位置: ({camera.location.x}, {camera.location.y}, {camera.location.z})")
|
||||
|
||||
# 隐藏ISO Emission灯光(如果存在)
|
||||
light_objects = ["ISO Emission Left", "ISO Emission Right"]
|
||||
for obj_name in light_objects:
|
||||
if obj_name in bpy.data.objects:
|
||||
light_obj = bpy.data.objects[obj_name]
|
||||
# 隐藏发光对象
|
||||
light_obj.hide_render = True
|
||||
light_obj.hide_viewport = True
|
||||
print(f"已隐藏 {obj_name}")
|
||||
|
||||
# 创建第一个160W点光源(房间中心)
|
||||
# try:
|
||||
# # 计算点光源位置(房间中心上方)
|
||||
# room_length = 4.0 # Y轴
|
||||
# room_width = 8.0 # X轴(4+4)
|
||||
|
||||
# # 第一个点光源位置:房间中心上方
|
||||
# light_x = 0 # X轴中心
|
||||
# light_y = 0 # Y轴中心
|
||||
# light_z = light_height
|
||||
|
||||
# # 创建第一个点光源
|
||||
# bpy.ops.object.light_add(type='POINT', location=(light_x, light_y, light_z))
|
||||
# point_light1 = bpy.context.active_object
|
||||
# point_light1.name = "Main Point Light"
|
||||
# point_light1.data.energy = 160 # 160W
|
||||
|
||||
# # 设置软衰减(简化设置)
|
||||
# point_light1.data.shadow_soft_size = 0.5 # 0.5米半径,产生柔和阴影
|
||||
# point_light1.data.use_shadow = True # 启用阴影
|
||||
|
||||
# print(f"已创建第一个点光源(160W)")
|
||||
# print(f"点光源位置: x={light_x}, y={light_y}, z={light_z}")
|
||||
|
||||
# except Exception as e:
|
||||
# print(f"创建第一个点光源时出错: {e}")
|
||||
|
||||
# 创建第二个150W日光(位置12, 7, 6)
|
||||
# try:
|
||||
# # 第二个光源位置 - 调整到窗户后方更远的位置
|
||||
# light2_x = -6
|
||||
# light2_y = 7 # 让日光更远
|
||||
# light2_z = 6 # 提高高度
|
||||
|
||||
# # 创建日光
|
||||
# bpy.ops.object.light_add(type='SUN', location=(light2_x, light2_y, light2_z))
|
||||
# sun_light = bpy.context.active_object
|
||||
# sun_light.name = "Sun Light"
|
||||
# sun_light.data.energy = 20
|
||||
|
||||
# # 调整日光旋转角度,让光线更直接地照射窗户
|
||||
# sun_light.rotation_euler = (math.radians(0), math.radians(-60), math.radians(0)) # 简化旋转角度
|
||||
|
||||
# # 设置日光属性(简化设置)
|
||||
# sun_light.data.angle = 0.05 # 从0.1改为0.05,让阴影更锐利
|
||||
# sun_light.data.use_shadow = True # 启用阴影
|
||||
|
||||
# print(f"已创建日光(20W)")
|
||||
# print(f"日光位置: x={light2_x}, y={light2_y}, z={light2_z}")
|
||||
# print(f"日光旋转: x={-70}°, y={0}°, z={0}°")
|
||||
|
||||
# except Exception as e:
|
||||
# print(f"创建日光时出错: {e}")
|
||||
|
||||
print("照明设置完成")
|
||||
print("灯光类型: 点光源 + 日光")
|
||||
print(f"衰减类型: 点光源软衰减 + 日光平行光")
|
||||
print(f"阴影设置: 启用,柔和阴影")
|
||||
print(f"主灯光高度: {light_height}米 (房间高度减一米)")
|
||||
print(f"日光位置: (0, 10, 6)米,旋转: (-70°, 0°, 0°)")
|
||||
|
||||
def render_scene():
|
||||
"""渲染场景"""
|
||||
print("开始EEVEE渲染...")
|
||||
|
||||
# 执行渲染
|
||||
bpy.ops.render.render(write_still=True)
|
||||
print(f"EEVEE渲染完成! 图片保存在: {bpy.context.scene.render.filepath}")
|
||||
return bpy.context.scene.render.filepath
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("=" * 60)
|
||||
print("开始自动创建等轴测房间并EEVEE渲染")
|
||||
print("=" * 60)
|
||||
|
||||
try:
|
||||
# 1. 创建房间
|
||||
print("1. 创建等轴测房间...")
|
||||
if not create_isometric_room():
|
||||
print("房间创建失败,停止执行")
|
||||
return False
|
||||
|
||||
# 2. 创建落地窗
|
||||
print("2. 创建archimesh落地窗...")
|
||||
if not create_window_with_archimesh():
|
||||
print("落地窗创建失败,但继续执行")
|
||||
# 不中断执行,窗户创建失败不影响渲染
|
||||
# 2. 创建门
|
||||
print("2. 创建archimesh门...")
|
||||
if not create_door_with_archimesh():
|
||||
print("落地窗创建失败,但继续执行")
|
||||
# 不中断执行,窗户创建失败不影响渲染
|
||||
|
||||
# 3. 设置渲染参数(EEVEE)
|
||||
print("3. 设置EEVEE渲染参数...")
|
||||
output_path = setup_render_settings()
|
||||
|
||||
# 4. 设置摄像机和照明
|
||||
print("4. 设置摄像机和照明...")
|
||||
setup_camera_and_lighting()
|
||||
|
||||
# 5. 渲染场景
|
||||
print("5. 开始EEVEE渲染...")
|
||||
final_path = render_scene()
|
||||
|
||||
print("=" * 60)
|
||||
print("EEVEE渲染完成!")
|
||||
print(f"渲染图片已保存到桌面: {final_path}")
|
||||
print("=" * 60)
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"执行过程中出现错误: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
# 执行主函数
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
'''
|
||||
|
||||
|
||||
def check_blender_exists():
|
||||
"""检查Blender是否存在于指定路径"""
|
||||
if not os.path.exists(BLENDER_PATH):
|
||||
print(f"错误: 在路径 '{BLENDER_PATH}' 找不到Blender")
|
||||
print("请检查Blender安装路径是否正确")
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def create_temp_script():
|
||||
"""创建临时的Blender脚本文件"""
|
||||
script_path = os.path.join(os.getcwd(), "temp_simple_blender_script.py")
|
||||
with open(script_path, 'w', encoding='utf-8') as f:
|
||||
f.write(BLENDER_SIMPLE_SCRIPT)
|
||||
return script_path
|
||||
|
||||
|
||||
def launch_blender_simple():
|
||||
"""启动Blender并执行简化版自动化脚本"""
|
||||
if not check_blender_exists():
|
||||
return False
|
||||
|
||||
# 创建临时脚本文件
|
||||
script_path = create_temp_script()
|
||||
|
||||
try:
|
||||
print("正在启动Blender(白模版本)...")
|
||||
print(f"Blender路径: {BLENDER_PATH}")
|
||||
print("特点:")
|
||||
print("- 不设置材质,使用默认白色材质")
|
||||
print("- 快速渲染")
|
||||
print("- 兼容Blender 4.2")
|
||||
print("- 自动保存到桌面")
|
||||
|
||||
# 构建命令行参数
|
||||
cmd = [
|
||||
BLENDER_PATH,
|
||||
"--background", # 后台运行模式
|
||||
"--disable-crash-handler", # 禁用崩溃处理器
|
||||
"--python", script_path # 执行Python脚本
|
||||
]
|
||||
|
||||
print("\n开始执行白模渲染流程...")
|
||||
|
||||
# 启动Blender并等待完成
|
||||
process = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding='utf-8',
|
||||
timeout=300 # 5分钟超时
|
||||
)
|
||||
|
||||
# 输出结果
|
||||
if process.stdout:
|
||||
print("Blender输出:")
|
||||
# 过滤掉重复的错误信息
|
||||
lines = process.stdout.split('\n')
|
||||
filtered_lines = []
|
||||
for line in lines:
|
||||
if not ("AttributeError: 'NoneType' object has no attribute 'idname'" in line or
|
||||
"Error in bpy.app.handlers.depsgraph_update_post" in line):
|
||||
filtered_lines.append(line)
|
||||
print('\n'.join(filtered_lines))
|
||||
|
||||
if process.stderr and not "rotate_tool.py" in process.stderr:
|
||||
print("重要错误信息:")
|
||||
print(process.stderr)
|
||||
|
||||
if process.returncode == 0:
|
||||
print("\n✅ 白模渲染执行成功!")
|
||||
print("白模图片应该已经保存到桌面")
|
||||
return True
|
||||
else:
|
||||
print(f"\n❌ 执行失败,返回码: {process.returncode}")
|
||||
return False
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
print("\n⏰ 执行超时(5分钟),可能Blender仍在运行")
|
||||
print("请检查桌面是否有生成的图片")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"启动Blender时出现错误: {str(e)}")
|
||||
return False
|
||||
|
||||
finally:
|
||||
# 清理临时文件
|
||||
try:
|
||||
if os.path.exists(script_path):
|
||||
os.remove(script_path)
|
||||
print(f"已清理临时文件: {script_path}")
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("=" * 70)
|
||||
print("Blender 4.2兼容版自动化脚本 - 白模渲染")
|
||||
print("=" * 70)
|
||||
print("特点:")
|
||||
print("✓ 移除所有材质设置")
|
||||
print("✓ 使用默认白色材质")
|
||||
print("✓ 快速渲染")
|
||||
print("✓ 兼容Blender 4.2")
|
||||
print("✓ 自动保存到桌面")
|
||||
print("=" * 70)
|
||||
|
||||
success = launch_blender_simple()
|
||||
|
||||
if success:
|
||||
print("\n🎉 白模渲染完成!")
|
||||
print("请检查桌面上的渲染图片")
|
||||
print("图片文件名格式: isometric_room_white_[时间戳].png")
|
||||
else:
|
||||
print("\n❌ 执行失败!")
|
||||
print("可能的解决方案:")
|
||||
print("1. 确保Isometquick插件已正确安装")
|
||||
print("2. 禁用可能冲突的其他插件")
|
||||
print("3. 检查磁盘空间是否足够")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,13 @@
|
|||
schema_version = "1.0.0"
|
||||
id = "isometric_room_gen"
|
||||
version = "1.0.6"
|
||||
name = "Isometric Room Gen"
|
||||
tagline = "Generate Isometric Rooms"
|
||||
maintainer = "SkdSam & Mr Steve"
|
||||
type = "add-on"
|
||||
website = "https://superhivemarket.com/creators/skdsam"
|
||||
tags = ["3D View"]
|
||||
blender_version_min = "3.6.0"
|
||||
license = [
|
||||
"SPDX:GPL-2.0-or-later",
|
||||
]
|
Loading…
Reference in New Issue