init commot

This commit is contained in:
libtxixi 2025-07-18 16:42:22 +08:00
parent 1f4552aad1
commit ef5bc0760d
20 changed files with 7988 additions and 0 deletions

View File

@ -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

View File

@ -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

View File

@ -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()

363
Isometquick-server/irg.sh Normal file
View File

@ -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 "$@"

234
Isometquick-server/main.py Normal file
View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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()

View File

@ -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())
```
就这么简单!🎉

1267
__init__.py Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

770
auto_room copy 2.py Normal file
View File

@ -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.051Alpha 1
# 转换为RGBHSV(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()

775
auto_room copy.py Normal file
View File

@ -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.051Alpha 1
# 转换为RGBHSV(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()

773
auto_room copy_window.py Normal file
View File

@ -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.051Alpha 1
# 转换为RGBHSV(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()

976
auto_room.py Normal file
View File

@ -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.051Alpha 1
# 转换为RGBHSV(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()

13
blender_manifest.toml Normal file
View File

@ -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",
]