用途:灯光跟随 or 对角色某个顶点跟踪
1、创建
locator又名定位器,有多种创建方式,可以在创建下拉菜单中创建,也可直接在绑定面板创建
方法1
方法2
2、maya使用情况(一)
有一个情景,角色在跑步和跳跃,需要被聚光灯持续照射。(对应角色在不停的运动,灯光需要对角色跟踪的情况。)
第一步,首先是创建locator,随后将locator按住X吸附到物体身上,选择子物体(定位器) → 再选父物体(角色) → 按 P,绑定父子级关系。这一步是让locator跟着角色运动
第二步,创建 SpotLight,选择物体下的locator,再选择需要被约束SpotLight做一个目标约束

层级关系如下:让Locator随着角色运动而运动了,灯光始终对准角色不改变。
物体移动
↓
带动Locator移动
↓
Spot Light跟随旋转运动
↓
灯光跟随物体


甚至可以在目标约束的同时,套娃一个父子约束,让灯光不单单固定在原点对角色追踪,像是把一个灯光绑定在摇臂上,始终跟拍随角色运动

locator的作用:由于在项目中经常使用引用物体的abc,因此物体是不允许改变的运动,locator就起代替的作用,例如希望对物体进行一个追光/扫光,最后离开角色,直接对locator进行动画的K帧,就能较为轻易控制追光出现的位置。又或者比如灯光追踪目标晃动感,直接给locator些表达式rotateY = noise(time*0.5)*0.5;就可以使用上了。(具体maya表达式怎么写,直接问AI即可)
3、maya导出locator在nuke中使用
一个情景是,我需要把一个定位器放在角色身上某个位置,例如角色带结算的耳环,角色牙齿上,角色手环,这种角度刁钻的位置,选择物体顶点,创建一个铆钉,会在角色身上生成一个始终跟随的定位器(这个定位器生成跟随与角色顶点UV有关,顶点UV错误情况下定位器也会有bug)
角色顶点定位器跟随的效果
创建铆钉会发现虽然定位器可以始终跟随在角色身上,但是pinOutput定位器的位移数值始终是0,即使导出abc也没有正确的数值。
解决方式是自己创建一个locator,使用父子约束或者是点约束,取消偏移让新的locator获取到pinOutput的位移数值

选择locator烘焙关键帧,再正常导出abc即可得到一个带位移的locator

最后再nuke中按R选择路径下的locator.abc导入,或者创建Axis读取

还有一种办法是用Reconcile3D节点,将三维的数据转为2d平面的跟踪数据,这样好处是对一些比较刁钻的区域免去了手Kroto麻烦


注意需要在copy时,需减去原来的数值
我将创建locator功能导出abc用ai快速写一个maya脚本
python
# -*- coding: utf-8 -*-
"""
Maya 2022
Rivet -> Locator -> Bake -> Alembic Tool
工作流:
1. 选择模型上的 Vertex / Edge / Face
2. Create Rivet Locator
3. Maya 创建 uvPin + pinOutput
4. 创建新的 Locator
5. pinOutput -> Parent Constraint -> Locator
6. Bake Locator
7. 删除约束和临时 Rivet
8. 可选择导出 Locator 为 Alembic
Author: GPT
"""
import maya.cmds as cmds
import maya.mel as mel
import os
WINDOW = "rivet_locator_tool"
# ---------------------------------------------------------
# 工具函数
# ---------------------------------------------------------
def get_selected_components():
"""获取当前选择的模型组件"""
sel = cmds.ls(sl=True, fl=True)
if not sel:
return []
components = []
for item in sel:
if "." in item:
components.append(item)
return components
def get_pin_outputs():
"""
Rivet 执行后 Maya 会选择:
uvPin + pinOutput
找出其中的 pinOutput。
"""
sel = cmds.ls(sl=True, type="transform")
outputs = []
for obj in sel:
if cmds.objExists(obj):
# Maya Rivet 创建出来的输出 locator
if "pinOutput" in obj:
outputs.append(obj)
return outputs
def get_uvpin_nodes():
"""获取当前场景中所有 uvPin"""
return cmds.ls(type="uvPin") or []
def get_new_uvpin(before):
"""找出 Rivet 新创建的 uvPin"""
after = get_uvpin_nodes()
for node in after:
if node not in before:
return node
return None
# ---------------------------------------------------------
# 创建 Rivet
# ---------------------------------------------------------
def create_rivet_locator(*args):
"""
选择 Vertex / Edge / Face 后:
Component
↓
cmds.Rivet()
↓
uvPin
↓
pinOutput
↓
Parent Constraint
↓
Locator
"""
components = get_selected_components()
if not components:
cmds.warning(
"请先选择模型上的 Vertex / Edge / Face。"
)
return
# 检查是否来自同一个模型
meshes = []
for comp in components:
mesh = comp.split(".")[0]
if mesh not in meshes:
meshes.append(mesh)
if len(meshes) != 1:
cmds.warning(
"一次操作请选择同一个模型上的组件。"
)
return
# 保存当前选择
original_selection = list(components)
# 记录创建前的 uvPin
before_uvpin = get_uvpin_nodes()
try:
# -------------------------------------------------
# Maya 原生 Rivet
# -------------------------------------------------
cmds.select(original_selection, r=True)
cmds.Rivet()
except Exception as e:
cmds.warning(
"Rivet 创建失败:{}".format(e)
)
return
# -----------------------------------------------------
# 找新创建的 uvPin
# -----------------------------------------------------
uvpin = get_new_uvpin(before_uvpin)
# -----------------------------------------------------
# 找 pinOutput
# -----------------------------------------------------
rivet_selection = cmds.ls(sl=True, type="transform") or []
pin_outputs = []
for obj in rivet_selection:
if "pinOutput" in obj:
pin_outputs.append(obj)
if not pin_outputs:
cmds.warning(
"没有找到 Maya 创建的 pinOutput。"
)
return
# -----------------------------------------------------
# 如果选择多个组件,会创建多个 pinOutput
# -----------------------------------------------------
created_locators = []
for pin_output in pin_outputs:
# 创建新的真正 Locator
locator = cmds.spaceLocator(
name="bakedLocator#"
)[0]
# -------------------------------------------------
# 先让 Locator 对齐 pinOutput
# -------------------------------------------------
cmds.delete(
cmds.parentConstraint(
pin_output,
locator,
mo=False
)
)
# -------------------------------------------------
# 再建立 Parent Constraint
# -------------------------------------------------
constraint = cmds.parentConstraint(
pin_output,
locator,
mo=False
)[0]
created_locators.append(
(locator, constraint, pin_output)
)
# -----------------------------------------------------
# 重新选择 Locator
# -----------------------------------------------------
cmds.select(
[x[0] for x in created_locators],
r=True
)
# -----------------------------------------------------
# UI 显示
# -----------------------------------------------------
cmds.inViewMessage(
amg="<hl>Rivet Locator Created</hl>",
pos="midCenter",
fade=True
)
print("====================================")
print("Rivet Locator 创建完成")
print("uvPin :", uvpin)
print("Locator :", [x[0] for x in created_locators])
print("====================================")
# ---------------------------------------------------------
# Bake Locator
# ---------------------------------------------------------
def bake_selected_locators(*args):
"""
Bake 当前选择的 Locator。
Bake 后:
pinOutput
↓
Constraint
↓
Locator
变成:
Locator
translate / rotate
每帧都有 key
"""
locators = cmds.ls(sl=True, type="transform")
if not locators:
cmds.warning(
"请先选择需要 Bake 的 Locator。"
)
return
start = cmds.playbackOptions(
q=True,
min=True
)
end = cmds.playbackOptions(
q=True,
max=True
)
# -----------------------------------------------------
# Bake
# -----------------------------------------------------
cmds.bakeResults(
locators,
t=(start, end),
simulation=True,
sampleBy=1,
disableImplicitControl=True,
preserveOutsideKeys=True,
sparseAnimCurveBake=False,
removeBakedAttributeFromLayer=False,
bakeOnOverrideLayer=False,
minimizeRotation=True
)
# -----------------------------------------------------
# 删除 Locator 上的约束
# -----------------------------------------------------
for locator in locators:
constraints = cmds.listRelatives(
locator,
type="constraint"
) or []
if constraints:
cmds.delete(constraints)
cmds.select(locators, r=True)
cmds.inViewMessage(
amg="<hl>Locator Bake Complete</hl>",
pos="midCenter",
fade=True
)
print("Bake 完成:")
print(locators)
# ---------------------------------------------------------
# Bake + 清理
# ---------------------------------------------------------
def bake_and_cleanup(*args):
"""
一键完成:
Bake
↓
删除 Parent Constraint
↓
删除 pinOutput
↓
删除 uvPin
"""
locators = cmds.ls(sl=True, type="transform")
if not locators:
cmds.warning(
"请先选择需要 Bake 的 Locator。"
)
return
start = cmds.playbackOptions(
q=True,
min=True
)
end = cmds.playbackOptions(
q=True,
max=True
)
# -----------------------------------------------------
# Bake
# -----------------------------------------------------
cmds.bakeResults(
locators,
t=(start, end),
simulation=True,
sampleBy=1,
disableImplicitControl=True,
preserveOutsideKeys=True,
sparseAnimCurveBake=False,
minimizeRotation=True
)
# -----------------------------------------------------
# 删除约束
# -----------------------------------------------------
for locator in locators:
constraints = cmds.listRelatives(
locator,
type="constraint"
) or []
if constraints:
cmds.delete(constraints)
# -----------------------------------------------------
# 删除对应 pinOutput
# -----------------------------------------------------
for locator in locators:
# 根据 Locator 名字无法可靠找到 pinOutput,
# 所以这里通过场景中的连接关系查找。
connections = cmds.listConnections(
locator,
s=True,
d=False
) or []
for node in connections:
if cmds.nodeType(node) == "uvPin":
outputs = cmds.listConnections(
node + ".outputMatrix",
s=False,
d=True
) or []
for output in outputs:
if cmds.objExists(output):
try:
cmds.delete(output)
except:
pass
try:
cmds.delete(node)
except:
pass
cmds.select(locators, r=True)
cmds.inViewMessage(
amg="<hl>Bake + Cleanup Complete</hl>",
pos="midCenter",
fade=True
)
# ---------------------------------------------------------
# Alembic Export
# ---------------------------------------------------------
def export_selected_abc(*args):
"""
导出当前选择的 Locator 为 Alembic。
"""
locators = cmds.ls(sl=True, type="transform")
if not locators:
cmds.warning(
"请先选择需要导出的 Locator。"
)
return
# -----------------------------------------------------
# 获取路径
# -----------------------------------------------------
result = cmds.fileDialog2(
fileMode=0,
caption="Export Alembic",
fileFilter="Alembic (*.abc)"
)
if not result:
return
filepath = result[0]
if not filepath.lower().endswith(".abc"):
filepath += ".abc"
# -----------------------------------------------------
# 时间范围
# -----------------------------------------------------
start = cmds.playbackOptions(
q=True,
min=True
)
end = cmds.playbackOptions(
q=True,
max=True
)
# -----------------------------------------------------
# 加载 AbcExport
# -----------------------------------------------------
if not cmds.pluginInfo(
"AbcExport",
q=True,
loaded=True
):
try:
cmds.loadPlugin("AbcExport")
except:
cmds.warning(
"无法加载 AbcExport 插件。"
)
return
# -----------------------------------------------------
# Alembic Job
# -----------------------------------------------------
root_flags = ""
for locator in locators:
root_flags += ' -root "{}"'.format(locator)
job = (
"-frameRange {} {} "
"-uvWrite "
"-worldSpace "
"-writeVisibility "
"-dataFormat ogawa "
"{} "
'-file "{}"'
).format(
start,
end,
root_flags,
filepath
)
try:
cmds.AbcExport(
j=job
)
except Exception as e:
cmds.warning(
"Alembic 导出失败:{}".format(e)
)
return
cmds.inViewMessage(
amg="<hl>ABC Export Complete</hl>",
pos="midCenter",
fade=True
)
print("Alembic exported:")
print(filepath)
# ---------------------------------------------------------
# 一键流程
# ---------------------------------------------------------
def create_bake_export(*args):
"""
一键:
Component
↓
Rivet
↓
Locator
↓
Bake
↓
Cleanup
↓
Export ABC
"""
create_rivet_locator()
cmds.refresh()
locators = cmds.ls(
sl=True,
type="transform"
)
if not locators:
return
bake_and_cleanup()
cmds.refresh()
export_selected_abc()
# ---------------------------------------------------------
# UI
# ---------------------------------------------------------
def show_ui():
if cmds.window(
WINDOW,
exists=True
):
cmds.deleteUI(WINDOW)
window = cmds.window(
WINDOW,
title="Rivet Locator Tool",
sizeable=True,
widthHeight=(420, 420)
)
cmds.columnLayout(
adjustableColumn=True,
rowSpacing=8
)
cmds.separator(
height=10,
style="none"
)
cmds.text(
label="Rivet Locator Tool",
font="boldLabelFont",
height=30
)
cmds.text(
label="Maya 2022 / UV Pin Rivet Workflow",
height=20
)
cmds.separator(
height=10,
style="in"
)
# -----------------------------------------------------
# Step 1
# -----------------------------------------------------
cmds.text(
label="STEP 1 | 选择模型组件",
align="left",
font="boldLabelFont"
)
cmds.text(
label="Vertex / Edge / Face 均可",
align="left"
)
cmds.separator(
height=8,
style="none"
)
cmds.button(
label="① 创建 Rivet Locator",
height=45,
backgroundColor=(0.25, 0.45, 0.65),
command=create_rivet_locator
)
# -----------------------------------------------------
# Step 2
# -----------------------------------------------------
cmds.text(
label="STEP 2 | Bake",
align="left",
font="boldLabelFont"
)
cmds.button(
label="② Bake Locator",
height=40,
command=bake_selected_locators
)
cmds.button(
label="③ Bake + Cleanup Rivet",
height=40,
command=bake_and_cleanup
)
# -----------------------------------------------------
# Step 3
# -----------------------------------------------------
cmds.text(
label="STEP 3 | Alembic",
align="left",
font="boldLabelFont"
)
cmds.button(
label="④ Export Selected Locator → ABC",
height=45,
backgroundColor=(0.35, 0.55, 0.35),
command=export_selected_abc
)
cmds.separator(
height=10,
style="in"
)
# -----------------------------------------------------
# One Click
# -----------------------------------------------------
cmds.button(
label="一键:Rivet → Bake → Cleanup → ABC",
height=50,
backgroundColor=(0.55, 0.40, 0.20),
command=create_bake_export
)
cmds.separator(
height=10,
style="none"
)
cmds.showWindow(window)
# ---------------------------------------------------------
# Run
# ---------------------------------------------------------
show_ui()