mirror of
https://github.com/deepmodeling/Uni-Lab-OS
synced 2026-05-25 06:16:08 +00:00
feat: add layout_optimizer package for automatic layout of devices
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
144
unilabos/layout_optimizer/pencil_integration.py
Normal file
144
unilabos/layout_optimizer/pencil_integration.py
Normal file
@@ -0,0 +1,144 @@
|
||||
"""初始布局生成:Pencil MCP 接口 + 行列式回退。
|
||||
|
||||
策略:
|
||||
1. 尝试调用 Pencil AI MCP 生成初始布局
|
||||
2. 若 Pencil 不可用或失败,回退到行列式放置算法
|
||||
|
||||
行列式回退逻辑:
|
||||
- 设备按面积从大到小排序
|
||||
- 沿 X 轴逐个放置,行满(超出 lab.width)则换行
|
||||
- 设备间保留 margin 间距
|
||||
- 所有设备 θ=0(朝向不变)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from .models import Device, Lab, Placement
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 设备间最小间距(米)
|
||||
DEFAULT_MARGIN = 0.3
|
||||
|
||||
|
||||
def generate_initial_layout(
|
||||
devices: list[Device],
|
||||
lab: Lab,
|
||||
margin: float = DEFAULT_MARGIN,
|
||||
) -> list[Placement]:
|
||||
"""生成初始布局方案。
|
||||
|
||||
优先尝试 Pencil MCP,失败则回退到行列式放置。
|
||||
|
||||
Args:
|
||||
devices: 待放置的设备列表
|
||||
lab: 实验室平面图
|
||||
margin: 设备间最小间距
|
||||
|
||||
Returns:
|
||||
初始布局 Placement 列表
|
||||
"""
|
||||
# 尝试 Pencil MCP
|
||||
pencil_result = _try_pencil(devices, lab)
|
||||
if pencil_result is not None:
|
||||
logger.info("Using Pencil AI generated layout")
|
||||
return pencil_result
|
||||
|
||||
# 回退到行列式
|
||||
logger.info("Pencil unavailable, using row-based fallback layout")
|
||||
return generate_fallback(devices, lab, margin)
|
||||
|
||||
|
||||
def _try_pencil(
|
||||
devices: list[Device],
|
||||
lab: Lab,
|
||||
) -> list[Placement] | None:
|
||||
"""尝试通过 Pencil AI MCP 生成布局。
|
||||
|
||||
当前 Pencil MCP 不可用,返回 None 触发回退。
|
||||
未来集成时,此函数应:
|
||||
1. 将设备 2D 投影 + 实验室平面图序列化为 Pencil 输入格式
|
||||
2. 调用 mcp__pencil_* 工具
|
||||
3. 解析返回的布局方案为 Placement 列表
|
||||
|
||||
预留接口参数:
|
||||
- devices: 设备列表(id, bbox)
|
||||
- lab: 实验室尺寸
|
||||
"""
|
||||
# TODO: 当 Pencil MCP 可用时实现
|
||||
# 预期调用方式:
|
||||
# pencil_input = {
|
||||
# "floor_plan": {"width": lab.width, "depth": lab.depth},
|
||||
# "items": [{"id": d.id, "width": d.bbox[0], "depth": d.bbox[1]} for d in devices],
|
||||
# }
|
||||
# result = mcp__pencil_layout(pencil_input)
|
||||
# return [Placement(device_id=r["id"], x=r["x"], y=r["y"], theta=r["theta"]) for r in result]
|
||||
return None
|
||||
|
||||
|
||||
def generate_fallback(
|
||||
devices: list[Device],
|
||||
lab: Lab,
|
||||
margin: float = DEFAULT_MARGIN,
|
||||
) -> list[Placement]:
|
||||
"""行列式回退布局:按面积从大到小排序,逐行放置。
|
||||
|
||||
放置规则:
|
||||
- 设备中心坐标,从左上角开始
|
||||
- 每行从 margin + half_width 开始
|
||||
- 行满(下一个设备右边缘超出 lab.width - margin)则换行
|
||||
- 行高取该行最大设备深度
|
||||
|
||||
Args:
|
||||
devices: 待放置的设备列表
|
||||
lab: 实验室平面图
|
||||
margin: 设备间最小间距
|
||||
|
||||
Returns:
|
||||
Placement 列表。若实验室空间不足,剩余设备堆叠在右下角并记录警告。
|
||||
"""
|
||||
if not devices:
|
||||
return []
|
||||
|
||||
# 按面积从大到小排序
|
||||
sorted_devices = sorted(devices, key=lambda d: d.bbox[0] * d.bbox[1], reverse=True)
|
||||
|
||||
placements: list[Placement] = []
|
||||
cursor_x = margin
|
||||
cursor_y = margin
|
||||
row_height = 0.0
|
||||
|
||||
for dev in sorted_devices:
|
||||
w, d = dev.bbox
|
||||
half_w = w / 2
|
||||
half_d = d / 2
|
||||
|
||||
# 检查当前行是否放得下
|
||||
if cursor_x + half_w + margin > lab.width and placements:
|
||||
# 换行
|
||||
cursor_x = margin
|
||||
cursor_y += row_height + margin
|
||||
row_height = 0.0
|
||||
|
||||
# 设备中心位置
|
||||
cx = cursor_x + half_w
|
||||
cy = cursor_y + half_d
|
||||
|
||||
# 检查是否超出实验室深度
|
||||
if cy + half_d + margin > lab.depth:
|
||||
logger.warning(
|
||||
"Lab space insufficient for device '%s' (%s), "
|
||||
"placing at overflow position",
|
||||
dev.id,
|
||||
dev.bbox,
|
||||
)
|
||||
|
||||
placements.append(Placement(device_id=dev.id, x=cx, y=cy, theta=0.0))
|
||||
|
||||
# 更新游标
|
||||
cursor_x = cx + half_w + margin
|
||||
row_height = max(row_height, d)
|
||||
|
||||
return placements
|
||||
Reference in New Issue
Block a user