import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
def draw_cube(ax, center, size, angle_deg, color, alpha=1.0, label=None):
"""
绘制一个旋转的立方体 (用于表示设备)
center: (x, y) 中心点
size: (length, width, height)
angle_deg: 旋转角度
"""
l, w, h = size
cx, cy = center
theta = np.radians(angle_deg)
# 计算底面四个角的坐标 (未旋转)
# dx, dy 相对中心的偏移
dx = np.array([-l/2, l/2, l/2, -l/2])
dy = np.array([-w/2, -w/2, w/2, w/2])
# 旋转矩阵应用
rot_x = dx * np.cos(theta) - dy * np.sin(theta)
rot_y = dx * np.sin(theta) + dy * np.cos(theta)
# 加上中心坐标
x = cx + rot_x
y = cy + rot_y
z = np.array([0, 0, 0, 0])
z_top = np.array([h, h, h, h])
# 构建顶点
verts = []
# 底面
verts.append(list(zip(x, y, z)))
# 顶面
verts.append(list(zip(x, y, z_top)))
# 四个侧面
for i in range(4):
next_i = (i + 1) % 4
verts.append([
(x[i], y[i], 0),
(x[next_i], y[next_i], 0),
(x[next_i], y[next_i], h),
(x[i], y[i], h)
])
ax.add_collection3d(Poly3DCollection(verts, facecolors=color, linewidths=1, edgecolors='k', alpha=alpha, label=label))
def draw_walls(ax, room_w, room_d, door_w, wall_h):
"""
绘制房间墙壁
"""
door_start = (room_w - door_w) / 2
door_end = (room_w + door_w) / 2
# 墙壁坐标定义 (x, y)
# Wall 1: Top (0, 1660) to (2090, 1660)
# Wall 2: Right (2090, 1660) to (2090, 0)
# Wall 3: Left (0, 1660) to (0, 0)
# Wall 4: Bottom Left (0, 0) to (door_start, 0)
# Wall 5: Bottom Right (door_end, 0) to (2090, 0)
walls = [
[(0, room_d), (room_w, room_d)], # Top
[(room_w, room_d), (room_w, 0)], # Right
[(0, room_d), (0, 0)], # Left
[(0, 0), (door_start, 0)], # Bottom Left
[(door_end, 0), (room_w, 0)] # Bottom Right
]
for w_start, w_end in walls:
xs = [w_start[0], w_end[0], w_end[0], w_start[0]]
ys = [w_start[1], w_end[1], w_end[1], w_start[1]]
zs = [0, 0, wall_h, wall_h]
verts = [list(zip(xs, ys, zs))]
ax.add_collection3d(Poly3DCollection(verts, facecolors='lightgray', linewidths=1, edgecolors='gray', alpha=0.3))
# 参数设置
room_w = 2090
room_d = 1660
door_w = 1400
wall_h = 500 # 示意高度
dev_l = 1710
dev_w = 1140
dev_h = 300 # 示意高度
fig = plt.figure(figsize=(15, 5))
# --- Subplot 1: Horizontal ---
ax1 = fig.add_subplot(131, projection='3d')
ax1.set_title("State A: Horizontal (Inside)\nFits Easily", fontsize=12, pad=20)
draw_walls(ax1, room_w, room_d, door_w, wall_h)
# Place horizontal: Centered
draw_cube(ax1, (room_w/2, room_d/2), (dev_l, dev_w, dev_h), 0, '#4CAF50', alpha=0.8) # Green
ax1.set_xlim(-200, room_w+200)
ax1.set_ylim(-500, room_d+200)
ax1.set_zlim(0, 1000)
ax1.view_init(elev=50, azim=-90) # Top down view
ax1.set_xlabel('Width (2090)')
ax1.set_ylabel('Depth (1660)')
ax1.set_zticks([])
# --- Subplot 2: Diagonal (The Squeeze) ---
ax2 = fig.add_subplot(132, projection='3d')
ax2.set_title("State B: Rotation (45 Deg)\nMust use Door Opening!", fontsize=12, color='red', pad=20)
draw_walls(ax2, room_w, room_d, door_w, wall_h)
# Place diagonal: 45 degrees.
# Position it so top corner touches top wall roughly.
# y_center + projected_half_length = 1660
# projected_half_length at 45 deg = (1710/2 * sin45 + 1140/2 * cos45) approx.
# To make it visual, we just center it but shifted down to use the door.
draw_cube(ax2, (room_w/2, 600), (dev_l, dev_w, dev_h), 45, '#FF9800', alpha=0.8) # Orange
ax2.text(room_w/2, 1700, 0, "Top Wall Limit", color='red', fontsize=8)
ax2.text(room_w/2, -200, 0, "Sticks Out", color='red', fontsize=8)
ax2.set_xlim(-200, room_w+200)
ax2.set_ylim(-500, room_d+200)
ax2.set_zlim(0, 1000)
ax2.view_init(elev=50, azim=-90)
ax2.set_zticks([])
# --- Subplot 3: Vertical ---
ax3 = fig.add_subplot(133, projection='3d')
ax3.set_title("State C: Vertical\nSticks Out ~50mm", fontsize=12, pad=20)
draw_walls(ax3, room_w, room_d, door_w, wall_h)
# Place vertical: 90 degrees.
# Must stick out bottom. Top at 1660.
# y_center = 1660 - 1710/2 = 805
draw_cube(ax3, (room_w/2, 805), (dev_l, dev_w, dev_h), 90, '#2196F3', alpha=0.8) # Blue
ax3.text(room_w/2 + 700, 0, 0, "Door Width 1400", color='blue', fontsize=8)
ax3.set_xlim(-200, room_w+200)
ax3.set_ylim(-500, room_d+200)
ax3.set_zlim(0, 1000)
ax3.view_init(elev=50, azim=-90)
ax3.set_zticks([])
plt.tight_layout()
plt.show()
aW1wb3J0IG1hdHBsb3RsaWIucHlwbG90IGFzIHBsdAppbXBvcnQgbnVtcHkgYXMgbnAKZnJvbSBtcGxfdG9vbGtpdHMubXBsb3QzZC5hcnQzZCBpbXBvcnQgUG9seTNEQ29sbGVjdGlvbgoKZGVmIGRyYXdfY3ViZShheCwgY2VudGVyLCBzaXplLCBhbmdsZV9kZWcsIGNvbG9yLCBhbHBoYT0xLjAsIGxhYmVsPU5vbmUpOgogICAgIiIiCiAgICDnu5jliLbkuIDkuKrml4vovaznmoTnq4vmlrnkvZMgKOeUqOS6juihqOekuuiuvuWkhykKICAgIGNlbnRlcjogKHgsIHkpIOS4reW/g+eCuQogICAgc2l6ZTogKGxlbmd0aCwgd2lkdGgsIGhlaWdodCkKICAgIGFuZ2xlX2RlZzog5peL6L2s6KeS5bqmCiAgICAiIiIKICAgIGwsIHcsIGggPSBzaXplCiAgICBjeCwgY3kgPSBjZW50ZXIKICAgIHRoZXRhID0gbnAucmFkaWFucyhhbmdsZV9kZWcpCiAgICAKICAgICMg6K6h566X5bqV6Z2i5Zub5Liq6KeS55qE5Z2Q5qCHICjmnKrml4vovawpCiAgICAjIGR4LCBkeSDnm7jlr7nkuK3lv4PnmoTlgY/np7sKICAgIGR4ID0gbnAuYXJyYXkoWy1sLzIsIGwvMiwgbC8yLCAtbC8yXSkKICAgIGR5ID0gbnAuYXJyYXkoWy13LzIsIC13LzIsIHcvMiwgdy8yXSkKICAgIAogICAgIyDml4vovaznn6npmLXlupTnlKgKICAgIHJvdF94ID0gZHggKiBucC5jb3ModGhldGEpIC0gZHkgKiBucC5zaW4odGhldGEpCiAgICByb3RfeSA9IGR4ICogbnAuc2luKHRoZXRhKSArIGR5ICogbnAuY29zKHRoZXRhKQogICAgCiAgICAjIOWKoOS4iuS4reW/g+WdkOaghwogICAgeCA9IGN4ICsgcm90X3gKICAgIHkgPSBjeSArIHJvdF95CiAgICB6ID0gbnAuYXJyYXkoWzAsIDAsIDAsIDBdKQogICAgel90b3AgPSBucC5hcnJheShbaCwgaCwgaCwgaF0pCiAgICAKICAgICMg5p6E5bu66aG254K5CiAgICB2ZXJ0cyA9IFtdCiAgICAKICAgICMg5bqV6Z2iCiAgICB2ZXJ0cy5hcHBlbmQobGlzdCh6aXAoeCwgeSwgeikpKQogICAgIyDpobbpnaIKICAgIHZlcnRzLmFwcGVuZChsaXN0KHppcCh4LCB5LCB6X3RvcCkpKQogICAgCiAgICAjIOWbm+S4quS+p+mdogogICAgZm9yIGkgaW4gcmFuZ2UoNCk6CiAgICAgICAgbmV4dF9pID0gKGkgKyAxKSAlIDQKICAgICAgICB2ZXJ0cy5hcHBlbmQoWwogICAgICAgICAgICAoeFtpXSwgeVtpXSwgMCksCiAgICAgICAgICAgICh4W25leHRfaV0sIHlbbmV4dF9pXSwgMCksCiAgICAgICAgICAgICh4W25leHRfaV0sIHlbbmV4dF9pXSwgaCksCiAgICAgICAgICAgICh4W2ldLCB5W2ldLCBoKQogICAgICAgIF0pCiAgICAgICAgCiAgICBheC5hZGRfY29sbGVjdGlvbjNkKFBvbHkzRENvbGxlY3Rpb24odmVydHMsIGZhY2Vjb2xvcnM9Y29sb3IsIGxpbmV3aWR0aHM9MSwgZWRnZWNvbG9ycz0naycsIGFscGhhPWFscGhhLCBsYWJlbD1sYWJlbCkpCgpkZWYgZHJhd193YWxscyhheCwgcm9vbV93LCByb29tX2QsIGRvb3Jfdywgd2FsbF9oKToKICAgICIiIgogICAg57uY5Yi25oi/6Ze05aKZ5aOBCiAgICAiIiIKICAgIGRvb3Jfc3RhcnQgPSAocm9vbV93IC0gZG9vcl93KSAvIDIKICAgIGRvb3JfZW5kID0gKHJvb21fdyArIGRvb3JfdykgLyAyCiAgICAKICAgICMg5aKZ5aOB5Z2Q5qCH5a6a5LmJICh4LCB5KQogICAgIyBXYWxsIDE6IFRvcCAoMCwgMTY2MCkgdG8gKDIwOTAsIDE2NjApCiAgICAjIFdhbGwgMjogUmlnaHQgKDIwOTAsIDE2NjApIHRvICgyMDkwLCAwKQogICAgIyBXYWxsIDM6IExlZnQgKDAsIDE2NjApIHRvICgwLCAwKQogICAgIyBXYWxsIDQ6IEJvdHRvbSBMZWZ0ICgwLCAwKSB0byAoZG9vcl9zdGFydCwgMCkKICAgICMgV2FsbCA1OiBCb3R0b20gUmlnaHQgKGRvb3JfZW5kLCAwKSB0byAoMjA5MCwgMCkKICAgIAogICAgd2FsbHMgPSBbCiAgICAgICAgWygwLCByb29tX2QpLCAocm9vbV93LCByb29tX2QpXSwgIyBUb3AKICAgICAgICBbKHJvb21fdywgcm9vbV9kKSwgKHJvb21fdywgMCldLCAjIFJpZ2h0CiAgICAgICAgWygwLCByb29tX2QpLCAoMCwgMCldLCAgICAgICAgICAgIyBMZWZ0CiAgICAgICAgWygwLCAwKSwgKGRvb3Jfc3RhcnQsIDApXSwgICAgICAgIyBCb3R0b20gTGVmdAogICAgICAgIFsoZG9vcl9lbmQsIDApLCAocm9vbV93LCAwKV0gICAgICMgQm90dG9tIFJpZ2h0CiAgICBdCiAgICAKICAgIGZvciB3X3N0YXJ0LCB3X2VuZCBpbiB3YWxsczoKICAgICAgICB4cyA9IFt3X3N0YXJ0WzBdLCB3X2VuZFswXSwgd19lbmRbMF0sIHdfc3RhcnRbMF1dCiAgICAgICAgeXMgPSBbd19zdGFydFsxXSwgd19lbmRbMV0sIHdfZW5kWzFdLCB3X3N0YXJ0WzFdXQogICAgICAgIHpzID0gWzAsIDAsIHdhbGxfaCwgd2FsbF9oXQogICAgICAgIHZlcnRzID0gW2xpc3QoemlwKHhzLCB5cywgenMpKV0KICAgICAgICBheC5hZGRfY29sbGVjdGlvbjNkKFBvbHkzRENvbGxlY3Rpb24odmVydHMsIGZhY2Vjb2xvcnM9J2xpZ2h0Z3JheScsIGxpbmV3aWR0aHM9MSwgZWRnZWNvbG9ycz0nZ3JheScsIGFscGhhPTAuMykpCgojIOWPguaVsOiuvue9rgpyb29tX3cgPSAyMDkwCnJvb21fZCA9IDE2NjAKZG9vcl93ID0gMTQwMAp3YWxsX2ggPSA1MDAgICMg56S65oSP6auY5bqmCmRldl9sID0gMTcxMApkZXZfdyA9IDExNDAKZGV2X2ggPSAzMDAgICAjIOekuuaEj+mrmOW6pgoKZmlnID0gcGx0LmZpZ3VyZShmaWdzaXplPSgxNSwgNSkpCgojIC0tLSBTdWJwbG90IDE6IEhvcml6b250YWwgLS0tCmF4MSA9IGZpZy5hZGRfc3VicGxvdCgxMzEsIHByb2plY3Rpb249JzNkJykKYXgxLnNldF90aXRsZSgiU3RhdGUgQTogSG9yaXpvbnRhbCAoSW5zaWRlKVxuRml0cyBFYXNpbHkiLCBmb250c2l6ZT0xMiwgcGFkPTIwKQpkcmF3X3dhbGxzKGF4MSwgcm9vbV93LCByb29tX2QsIGRvb3Jfdywgd2FsbF9oKQojIFBsYWNlIGhvcml6b250YWw6IENlbnRlcmVkCmRyYXdfY3ViZShheDEsIChyb29tX3cvMiwgcm9vbV9kLzIpLCAoZGV2X2wsIGRldl93LCBkZXZfaCksIDAsICcjNENBRjUwJywgYWxwaGE9MC44KSAjIEdyZWVuCmF4MS5zZXRfeGxpbSgtMjAwLCByb29tX3crMjAwKQpheDEuc2V0X3lsaW0oLTUwMCwgcm9vbV9kKzIwMCkKYXgxLnNldF96bGltKDAsIDEwMDApCmF4MS52aWV3X2luaXQoZWxldj01MCwgYXppbT0tOTApICMgVG9wIGRvd24gdmlldwpheDEuc2V0X3hsYWJlbCgnV2lkdGggKDIwOTApJykKYXgxLnNldF95bGFiZWwoJ0RlcHRoICgxNjYwKScpCmF4MS5zZXRfenRpY2tzKFtdKQoKIyAtLS0gU3VicGxvdCAyOiBEaWFnb25hbCAoVGhlIFNxdWVlemUpIC0tLQpheDIgPSBmaWcuYWRkX3N1YnBsb3QoMTMyLCBwcm9qZWN0aW9uPSczZCcpCmF4Mi5zZXRfdGl0bGUoIlN0YXRlIEI6IFJvdGF0aW9uICg0NSBEZWcpXG5NdXN0IHVzZSBEb29yIE9wZW5pbmchIiwgZm9udHNpemU9MTIsIGNvbG9yPSdyZWQnLCBwYWQ9MjApCmRyYXdfd2FsbHMoYXgyLCByb29tX3csIHJvb21fZCwgZG9vcl93LCB3YWxsX2gpCiMgUGxhY2UgZGlhZ29uYWw6IDQ1IGRlZ3JlZXMuCiMgUG9zaXRpb24gaXQgc28gdG9wIGNvcm5lciB0b3VjaGVzIHRvcCB3YWxsIHJvdWdobHkuCiMgeV9jZW50ZXIgKyBwcm9qZWN0ZWRfaGFsZl9sZW5ndGggPSAxNjYwCiMgcHJvamVjdGVkX2hhbGZfbGVuZ3RoIGF0IDQ1IGRlZyA9ICgxNzEwLzIgKiBzaW40NSArIDExNDAvMiAqIGNvczQ1KSBhcHByb3guCiMgVG8gbWFrZSBpdCB2aXN1YWwsIHdlIGp1c3QgY2VudGVyIGl0IGJ1dCBzaGlmdGVkIGRvd24gdG8gdXNlIHRoZSBkb29yLgpkcmF3X2N1YmUoYXgyLCAocm9vbV93LzIsIDYwMCksIChkZXZfbCwgZGV2X3csIGRldl9oKSwgNDUsICcjRkY5ODAwJywgYWxwaGE9MC44KSAjIE9yYW5nZQpheDIudGV4dChyb29tX3cvMiwgMTcwMCwgMCwgIlRvcCBXYWxsIExpbWl0IiwgY29sb3I9J3JlZCcsIGZvbnRzaXplPTgpCmF4Mi50ZXh0KHJvb21fdy8yLCAtMjAwLCAwLCAiU3RpY2tzIE91dCIsIGNvbG9yPSdyZWQnLCBmb250c2l6ZT04KQpheDIuc2V0X3hsaW0oLTIwMCwgcm9vbV93KzIwMCkKYXgyLnNldF95bGltKC01MDAsIHJvb21fZCsyMDApCmF4Mi5zZXRfemxpbSgwLCAxMDAwKQpheDIudmlld19pbml0KGVsZXY9NTAsIGF6aW09LTkwKQpheDIuc2V0X3p0aWNrcyhbXSkKCiMgLS0tIFN1YnBsb3QgMzogVmVydGljYWwgLS0tCmF4MyA9IGZpZy5hZGRfc3VicGxvdCgxMzMsIHByb2plY3Rpb249JzNkJykKYXgzLnNldF90aXRsZSgiU3RhdGUgQzogVmVydGljYWxcblN0aWNrcyBPdXQgfjUwbW0iLCBmb250c2l6ZT0xMiwgcGFkPTIwKQpkcmF3X3dhbGxzKGF4Mywgcm9vbV93LCByb29tX2QsIGRvb3Jfdywgd2FsbF9oKQojIFBsYWNlIHZlcnRpY2FsOiA5MCBkZWdyZWVzLgojIE11c3Qgc3RpY2sgb3V0IGJvdHRvbS4gVG9wIGF0IDE2NjAuCiMgeV9jZW50ZXIgPSAxNjYwIC0gMTcxMC8yID0gODA1CmRyYXdfY3ViZShheDMsIChyb29tX3cvMiwgODA1KSwgKGRldl9sLCBkZXZfdywgZGV2X2gpLCA5MCwgJyMyMTk2RjMnLCBhbHBoYT0wLjgpICMgQmx1ZQpheDMudGV4dChyb29tX3cvMiArIDcwMCwgMCwgMCwgIkRvb3IgV2lkdGggMTQwMCIsIGNvbG9yPSdibHVlJywgZm9udHNpemU9OCkKYXgzLnNldF94bGltKC0yMDAsIHJvb21fdysyMDApCmF4My5zZXRfeWxpbSgtNTAwLCByb29tX2QrMjAwKQpheDMuc2V0X3psaW0oMCwgMTAwMCkKYXgzLnZpZXdfaW5pdChlbGV2PTUwLCBhemltPS05MCkKYXgzLnNldF96dGlja3MoW10pCgpwbHQudGlnaHRfbGF5b3V0KCkKcGx0LnNob3coKQo=