要在 Maya 中编写兼容 **Python 2.7** 和 **Python 3.x** 的脚本,我们需要处理以下几个主要差异点:
---
## ✅ 兼容 Python 2.7 和 Python 3 的关键点
| 问题 | Python 2.7 | Python 3.x | 解决方案 |
|------|-------------|------------|----------|
| `print` 语法 | `print "hello"` | `print("hello")` | 使用 `from __future__ import print_function` |
| 字符串编码 | 默认 `str` 是字节串 | 默认 `str` 是 Unicode | 使用 `u""` 表示 Unicode,`str()` 转换 |
| 除法运算 | `5 / 2 = 2` | `5 / 2 = 2.5` | 使用 `from __future__ import division` |
| `xrange` vs `range` | `xrange` 是迭代器 | `range` 是迭代器 | 使用 `xrange`(在 2.7)或 `range`(3.x) |
| `__future__` 导入 | 支持 | 支持 | 用于兼容性 |
| `lambda` 函数绑定 | 有 late-binding 问题 | 同上 | 使用默认参数绑定当前值 |
---
## ✅ 示例代码(兼容 Python 2.7 和 Python 3.x)
以下是一个完整的、兼容 Python 2.7 和 Python 3.x 的 Maya GUI 脚本示例:
```python
# -*- coding: utf-8 -*-
from __future__ import print_function, division
import maya.cmds as cmds
import os
# 存储贴图节点信息
texture_node_map = []
def get_texture_nodes_with_color_space_and_name():
textures = cmds.ls(type='file')
result = []
for tex in textures:
try:
cs = cmds.getAttr(tex + '.colorSpace')
except:
cs = u'未设置'
try:
path = cmds.getAttr(tex + '.fileTextureName')
if path:
tex_name = os.path.basename(path)
else:
tex_name = u'未指定'
except:
tex_name = u'获取失败'
result.append((tex, tex_name, cs))
return result
def update_texture_list(ctrl_name):
global texture_node_map
texture_node_map = []
if cmds.iconTextScrollList(ctrl_name, exists=True):
cmds.deleteUI(ctrl_name)
textures_with_info = get_texture_nodes_with_color_space_and_name()
list_ctrl = cmds.iconTextScrollList(ctrl_name, width=680, height=300, allowMultiSelection=True, parent='textureListLayout')
for tex, name, cs in textures_with_info:
line = u"{0:<15} {1:^20} {2:>10}".format(tex, name, cs)
cmds.iconTextScrollList(list_ctrl, edit=True, append=line)
texture_node_map.append(tex)
def on_set_color_space(ctrl_name, color_space):
selected_indices = cmds.iconTextScrollList(ctrl_name, query=True, selectIndexedItem=True)
if not selected_indices:
cmds.confirmDialog(title=u'提示', message=u'请先从列表中选择贴图。', button=[u'确定'])
return
for idx in selected_indices:
tex_name = texture_node_map[idx - 1]
if tex_name:
try:
cmds.setAttr(tex_name + '.colorSpace', color_space, type='string')
print(u"已将 {0} 设置为 {1} 色彩空间。".format(tex_name, color_space))
except Exception as e:
print(u"无法设置 {0} 的色彩空间:{1}".format(tex_name, str(e)))
cmds.confirmDialog(title=u'完成', message=u'已修改 {0} 个贴图的色彩空间为 {1}。'.format(len(selected_indices), color_space), button=[u'确定'])
update_texture_list(ctrl_name)
def set_texture_path_to_file_name_only():
textures = cmds.ls(type='file')
modified_count = 0
for tex in textures:
try:
path = cmds.getAttr(tex + '.fileTextureName')
if path and os.path.exists(path):
filename = os.path.basename(path)
cmds.setAttr(tex + '.fileTextureName', filename, type='string')
print("已将 {} 的路径设置为: {}".format(tex, filename))
modified_count += 1
else:
print("跳过 {}: 路径无效或文件不存在".format(tex))
except Exception as e:
print("无法修改 {}: {}".format(tex, str(e)))
if modified_count > 0:
cmds.confirmDialog(title="完成", message="已修改 {} 个贴图路径为文件名".format(modified_count), button=["确定"])
else:
cmds.confirmDialog(title="提示", message="没有贴图路径被修改", button=["确定"])
def on_window_resize(*args):
layout_name = "colorButtonLayout"
if not cmds.layout(layout_name, exists=True):
return
window_name = "CustomTextureColorSpaceTool"
if not cmds.window(window_name, exists=True):
return
window_width = cmds.window(window_name, query=True, width=True)
padding = 60
spacing = 10
total_spacing = spacing * 5
button_width = (window_width - padding - total_spacing) / 6
button_height = button_width # 正方形按钮
for i in range(1, 7):
btn = "colorButton{}".format(i)
if cmds.button(btn, exists=True):
cmds.button(btn, edit=True, width=button_width, height=button_height)
def create_custom_color_space_gui():
window_name = "CustomTextureColorSpaceTool"
if cmds.window(window_name, exists=True):
cmds.deleteUI(window_name)
window = cmds.window(window_name, title="Color Space Tool", widthHeight=(700, 450), resizeToFitChildren=True)
main_layout = cmds.columnLayout(adjustableColumn=True, rowSpacing=10)
# 贴图列表说明文本
cmds.text(label="Texture List (Node | File Name | Color Space):", height=30, align='left')
# 使用 layout 包裹贴图列表
list_layout = cmds.columnLayout('textureListLayout', adjustableColumn=True)
update_texture_list('textureList')
cmds.setParent('..')
refresh_btn = cmds.button(label="Refresh", command=lambda x: update_texture_list('textureList'))
# 按钮布局 - 6 个色彩空间按钮横向排列
color_button_layout = cmds.rowLayout("colorButtonLayout", numberOfColumns=6,
adjustableColumn=True,
columnAttach=[(i, 'both', 10) for i in range(1, 7)],
parent=main_layout)
bg_color = [0.3, 0.6, 1.0]
# 创建按钮并命名
cmds.button("colorButton1", label="Raw", backgroundColor=bg_color, command=lambda x, cs='Raw': on_set_color_space('textureList', cs))
cmds.button("colorButton2", label="Utility - Raw", backgroundColor=bg_color, command=lambda x, cs='Utility - Raw': on_set_color_space('textureList', cs))
cmds.button("colorButton3", label="sRGB", backgroundColor=bg_color, command=lambda x, cs='sRGB': on_set_color_space('textureList', cs))
cmds.button("colorButton4", label="ACES - ACEScg", backgroundColor=bg_color, command=lambda x, cs='ACES - ACEScg': on_set_color_space('textureList', cs))
cmds.button("colorButton5", label="Utility - sRGB - Texture", backgroundColor=bg_color, command=lambda x, cs='Utility - sRGB - Texture': on_set_color_space('textureList', cs))
cmds.button("colorButton6", label="Utility - Linear - sRGB", backgroundColor=bg_color, command=lambda x, cs='Utility - Linear - sRGB': on_set_color_space('textureList', cs))
cmds.setParent('..')
# 显示窗口
cmds.showWindow(window)
# 使用 scriptJob 监听窗口大小变化
global resize_job_id
try:
resize_job_id = cmds.scriptJob(
uiDeleted=[window_name, lambda: print("Window closed")],
attributeChange=[window + ".width", on_window_resize, window + ".height", on_window_resize]
)
except Exception as e:
print("ScriptJob error:", str(e))
# 新增按钮:将贴图路径改为文件名
cmds.button(label="Use File Name Only", command=lambda x: set_texture_path_to_file_name_only())
# 执行函数创建 GUI
create_custom_color_space_gui()
```
---
## ✅ 关键兼容处理说明
| 处理点 | 说明 |
|--------|------|
| `from __future__ import print_function` | 使 `print("xxx")` 在 Python 2 中可用 |
| `from __future__ import division` | 使 `5 / 2 == 2.5`,统一除法行为 |
| `u"字符串"` | 明确使用 Unicode 字符串 |
| `lambda x, cs='xxx': ...` | 避免 late-binding 问题 |
| `global resize_job_id` | 防止重复绑定 |
| `try-except` 包裹 `scriptJob` | 提高稳定性 |
---
###