[c4d]-物理Tag烘焙为骨骼动画_&_塌陷Fracture和Voronoi到骨骼
·

一 、 塌陷Fracture和Voronoi
bakeAnim_Fracture和Voronoi拆开&绑定骨骼.py
import c4d
from c4d import gui, documents, utils
#from c4d.modules import character
# =================================================================================
# 核心逻辑
# =================================================================================
def _iterate_cache(op):
res = []
root = op.GetCache()
def walk(o):
while o:
if o.IsInstanceOf(c4d.Opolygon) or o.IsInstanceOf(c4d.Ospline) or o.IsInstanceOf(c4d.Oinstance) or o.IsInstanceOf(c4d.Osds):
res.append(o)
if o.GetDown():
walk(o.GetDown())
o = o.GetNext()
if root:
walk(root)
return res
def _ensure_track(obj, desc):
tr = obj.FindCTrack(desc)
if tr is None:
tr = c4d.CTrack(obj, desc)
obj.InsertTrackSorted(tr)
return tr
def _add_key(track, time, value):
curve = track.GetCurve()
key_dict = curve.AddKey(time)
if key_dict is None: return None
key = key_dict["key"]
key.SetValue(curve, value)
key.SetInterpolation(curve, c4d.CINTERPOLATION_SPLINE)
return key
def _mg_to_hpb(mg):
return utils.MatrixToHPB(mg)
def _create_skin_binding(seg_obj, joint_obj, doc):
wt = c4d.BaseTag(c4d.Tweights)
seg_obj.InsertTag(wt)
wt.AddJoint(joint_obj)
cnt = seg_obj.GetPointCount()
weights = [1.0] * cnt
try:
wt.SetWeightMap(0, weights)
except Exception:
for i in range(cnt):
wt.SetWeight(0, i, 1.0)
wt.SetGeomMg(seg_obj.GetMg())
wt.CalculateBoneStates(0)
skin = c4d.BaseObject(c4d.Oskin)
skin.SetName("Skin")
skin.InsertUnder(seg_obj)
doc.AddUndo(c4d.UNDOTYPE_NEWOBJ, skin)
doc.AddUndo(c4d.UNDOTYPE_NEWOBJ, wt)
return wt, skin
def _disable_dynamics(fracture, doc):
for t in fracture.GetTags():
try:
if t.GetParameter(c4d.RIGIDBODY_USE, doc) is not None:
t[c4d.RIGIDBODY_USE] = False
doc.AddUndo(c4d.UNDOTYPE_CHANGE, t)
except Exception:
pass
fracture.SetEditorMode(c4d.MODE_OFF)
fracture.SetRenderMode(c4d.MODE_OFF)
doc.AddUndo(c4d.UNDOTYPE_CHANGE, fracture)
# =================================================================================
# UI 界面类
# =================================================================================
class BakeDialog(gui.GeDialog):
ID_BTN_BAKE = 1001
ID_TXT_STATUS = 1002
ID_GROUP_MAIN = 2000
def CreateLayout(self):
self.SetTitle("Bake Anim Tool")
if self.GroupBegin(self.ID_GROUP_MAIN, c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, 1, 0, "", 0):
self.GroupBorderSpace(10, 10, 10, 10)
# 说明文本
self.AddStaticText(0, c4d.BFH_LEFT, 0, 0, "选中 Fracture 对象,点击下方按钮开始烘焙。", 0)
# 状态显示
self.AddStaticText(self.ID_TXT_STATUS, c4d.BFH_SCALEFIT, 0, 0, "准备就绪", 0)
# 按钮
self.AddButton(self.ID_BTN_BAKE, c4d.BFH_SCALEFIT, 0, 20, "开始烘焙 (Bake)")
self.GroupEnd()
return True
def Command(self, id, msg):
if id == self.ID_BTN_BAKE:
self.run_bake()
return True
def update_progress(self, current, total, message=""):
# 更新 C4D 状态栏进度条
if total > 0:
percent = int(100.0 * current / total)
c4d.StatusSetBar(percent)
else:
c4d.StatusSetBar(0)
c4d.StatusSetText(message)
# 更新界面文本
self.SetString(self.ID_TXT_STATUS, message)
def run_bake(self):
doc = documents.GetActiveDocument()
if not doc: return
target = doc.GetActiveObject()
if target is None:
c4d.gui.MessageDialog("请先选中一个 Fracture 对象!")
return
try:
mode = target[c4d.MGFRACTUREOBJECT_MODE]
except Exception:
c4d.gui.MessageDialog("选中的对象不是有效的 Fracture 对象,或者不支持该属性。")
return
# 1. 准备工作
c4d.StopAllThreads()
if mode not in (c4d.MGFRACTUREOBJECT_MODE_EXPLODE, c4d.MGFRACTUREOBJECT_MODE_EXCONNECT):
target[c4d.MGFRACTUREOBJECT_MODE] = c4d.MGFRACTUREOBJECT_MODE_EXPLODE
fps = doc.GetFps()
min_t = doc.GetMinTime()
max_t = doc.GetMaxTime()
min_f = int(min_t.GetFrame(fps))
max_f = int(max_t.GetFrame(fps))
total_frames = max_f - min_f + 1
doc.StartUndo()
try:
# 2. 创建层级结构
rig_root = c4d.BaseObject(c4d.Onull)
rig_root.SetName("bakeAnim_rig")
doc.InsertObject(rig_root)
doc.AddUndo(c4d.UNDOTYPE_NEWOBJ, rig_root)
joints_root = c4d.BaseObject(c4d.Onull)
joints_root.SetName("Joints")
joints_root.InsertUnder(rig_root)
segments_root = c4d.BaseObject(c4d.Onull)
segments_root.SetName("Segments")
segments_root.InsertUnder(rig_root)
doc.AddUndo(c4d.UNDOTYPE_NEWOBJ, joints_root)
doc.AddUndo(c4d.UNDOTYPE_NEWOBJ, segments_root)
# 3. 初始缓存获取与绑定
self.update_progress(0, total_frames, "正在初始化缓存与骨骼绑定...")
# 强制刷新一次缓存
doc.ExecutePasses(None, True, True, True, 0)
cache_objs = _iterate_cache(target)
if not cache_objs:
c4d.gui.MessageDialog("无法获取 Fracture 缓存,请检查物理模拟设置。")
doc.EndUndo()
return
segs = []
joints = []
for i, src in enumerate(cache_objs):
clone = src.GetClone()
clone.SetName(f"segment_{i+1}")
clone.InsertUnder(segments_root)
clone.SetMg(src.GetMg())
doc.AddUndo(c4d.UNDOTYPE_NEWOBJ, clone)
j = c4d.BaseObject(c4d.Ojoint)
j.SetName(f"joint_{i+1}")
j.InsertUnder(joints_root)
j.SetMg(src.GetMg())
doc.AddUndo(c4d.UNDOTYPE_NEWOBJ, j)
_create_skin_binding(clone, j, doc)
segs.append(clone)
joints.append(j)
# 4. 逐帧烘焙
for i, f in enumerate(range(min_f, max_f + 1)):
# 更新进度
if i % 5 == 0:
self.update_progress(i, total_frames, f"正在烘焙第 {f} 帧...")
t = c4d.BaseTime(f, fps)
doc.SetTime(t)
doc.ExecutePasses(None, True, True, True, 0)
# c4d.GeSyncMessage(c4d.EVMSG_TIMECHANGED)
clones = _iterate_cache(target)
if not clones or len(clones) != len(joints):
continue
for idx, src in enumerate(clones):
if idx >= len(joints): break
mg = src.GetMg()
pos = mg.off
hpb = _mg_to_hpb(mg)
h = hpb.x
p = hpb.y
b = hpb.z
j = joints[idx]
# 记录位置关键帧
_add_key(_ensure_track(j, c4d.DescID(c4d.DescLevel(c4d.ID_BASEOBJECT_POSITION, c4d.DTYPE_VECTOR, 0), c4d.DescLevel(c4d.VECTOR_X, c4d.DTYPE_REAL, 0))), t, pos.x)
_add_key(_ensure_track(j, c4d.DescID(c4d.DescLevel(c4d.ID_BASEOBJECT_POSITION, c4d.DTYPE_VECTOR, 0), c4d.DescLevel(c4d.VECTOR_Y, c4d.DTYPE_REAL, 0))), t, pos.y)
_add_key(_ensure_track(j, c4d.DescID(c4d.DescLevel(c4d.ID_BASEOBJECT_POSITION, c4d.DTYPE_VECTOR, 0), c4d.DescLevel(c4d.VECTOR_Z, c4d.DTYPE_REAL, 0))), t, pos.z)
# 记录旋转关键帧
_add_key(_ensure_track(j, c4d.DescID(c4d.DescLevel(c4d.ID_BASEOBJECT_ROTATION, c4d.DTYPE_VECTOR, 0), c4d.DescLevel(c4d.VECTOR_X, c4d.DTYPE_REAL, 0))), t, h)
_add_key(_ensure_track(j, c4d.DescID(c4d.DescLevel(c4d.ID_BASEOBJECT_ROTATION, c4d.DTYPE_VECTOR, 0), c4d.DescLevel(c4d.VECTOR_Y, c4d.DTYPE_REAL, 0))), t, p)
_add_key(_ensure_track(j, c4d.DescID(c4d.DescLevel(c4d.ID_BASEOBJECT_ROTATION, c4d.DTYPE_VECTOR, 0), c4d.DescLevel(c4d.VECTOR_Z, c4d.DTYPE_REAL, 0))), t, b)
# 5. 收尾
_disable_dynamics(target, doc)
self.update_progress(total_frames, total_frames, "烘焙完成!")
c4d.StatusClear()
c4d.gui.MessageDialog("烘焙完成!")
except Exception as e:
import traceback
traceback.print_exc()
c4d.gui.MessageDialog(f"烘焙过程中发生错误: {e}")
finally:
doc.EndUndo()
c4d.EventAdd()
# 全局变量,保持 Dialog 引用
dlg = None
def main():
global dlg
# 每次运行先关闭旧窗口 (如果存在)
if dlg is not None:
dlg.Close()
dlg = BakeDialog()
# 异步模式打开,方便操作
dlg.Open(c4d.DLG_TYPE_ASYNC, defaultw=300, defaulth=150)
if __name__ == "__main__":
main()
二、 bakeAnim 塌陷动画

bakeAnim_c4d烘焙物理解算到骨骼蒙皮.py
import c4d
def main() -> None:
selected_objects = doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_0)
for obj in selected_objects:
target_parent = obj.GetUp()
res = c4d.utils.SendModelingCommand(
command=c4d.MCOMMAND_MAKEEDITABLE,
list=[obj],
mode=c4d.MODELINGCOMMANDMODE_ALL,
doc=doc,
flags=c4d.MODELINGCOMMANDFLAGS_CREATEUNDO
)
if res and len(res) > 0:
new_obj = res[0]
if target_parent:
new_obj.InsertUnder(target_parent)
完整代码
from pydoc import doc
import c4d
from c4d import gui, documents, utils
import traceback
# =================================================================================
# 核心工具函数
# =================================================================================
def _ensure_track(obj, desc):
"""确保对象存在指定的动画轨道,不存在则创建"""
tr = obj.FindCTrack(desc)
if tr is None:
tr = c4d.CTrack(obj, desc)
obj.InsertTrackSorted(tr)
return tr
def _add_key(track, time, value):
"""给轨道添加关键帧(样条插值)"""
curve = track.GetCurve()
key_dict = curve.AddKey(time)
if key_dict is None: return None
key = key_dict["key"]
key.SetValue(curve, value)
key.SetInterpolation(curve, c4d.CINTERPOLATION_SPLINE)
return key
def _mg_to_hpb(mg):
"""矩阵转HPB旋转(纯世界坐标)"""
return utils.MatrixToHPB(mg)
def _create_skin_binding(seg_obj, joint_obj, doc):
# 关键:在绑定前,将参数化物体转换为 Polygon 对象 (Make Editable)
# 这样可以确保蒙皮标签正确应用到点上
target_parent = seg_obj.GetUp()
# 1. 尝试转换为可编辑对象
res = c4d.utils.SendModelingCommand(
command=c4d.MCOMMAND_MAKEEDITABLE,
list=[seg_obj],
mode=c4d.MODELINGCOMMANDMODE_ALL,
doc=doc,
flags=c4d.MODELINGCOMMANDFLAGS_CREATEUNDO
)
if res and len(res) > 0:
new_obj = res[0]
# 记录旧物体的删除Undo
doc.AddUndo(c4d.UNDOTYPE_DELETE, seg_obj)
# 插入新物体
#if target_parent:
# new_obj.InsertUnder(target_parent)
# 保持位置和变换
#new_obj.SetMg(seg_obj.GetMg())
# 移除旧物体
#seg_obj.Remove()
# 记录新物体的Undo
doc.AddUndo(c4d.UNDOTYPE_NEWOBJ, new_obj)
# 更新引用
seg_obj = new_obj
# 2. 检查是否为 PointObject (Polygon/Spline),如果不是(例如是 Null 组),尝试合并
if not isinstance(seg_obj, c4d.PointObject):
# 尝试连接对象+删除 (JOIN)
res_join = c4d.utils.SendModelingCommand(
command=c4d.MCOMMAND_JOIN,
list=[seg_obj],
mode=c4d.MODELINGCOMMANDMODE_ALL,
doc=doc,
flags=c4d.MODELINGCOMMANDFLAGS_CREATEUNDO
)
if res_join and len(res_join) > 0:
joined_obj = res_join[0]
# 记录旧物体(此时是MakeEditable后的Null)的删除
doc.AddUndo(c4d.UNDOTYPE_DELETE, seg_obj)
# 插入合并后的物体
if seg_obj.GetUp():
joined_obj.InsertUnder(seg_obj.GetUp())
else:
doc.InsertObject(joined_obj)
joined_obj.SetMg(seg_obj.GetMg())
seg_obj.Remove()
doc.AddUndo(c4d.UNDOTYPE_NEWOBJ, joined_obj)
seg_obj = joined_obj
# 3. 再次检查,确保是 PointObject
if not isinstance(seg_obj, c4d.PointObject):
print(f"Warning: Object {seg_obj.GetName()} is not a PointObject (Type: {seg_obj.GetType()}), skipping skin binding.")
return None, None
"""给物体添加权重标签并绑定到骨骼"""
# 创建权重标签
wt = c4d.BaseTag(c4d.Tweights)
seg_obj.InsertTag(wt)
wt.AddJoint(joint_obj)
cnt = seg_obj.GetPointCount()
weights = [1.0] * cnt
# 兼容不同C4D版本的权重设置
try:
wt.SetWeightMap(0, weights)
except Exception:
for i in range(cnt):
wt.SetWeight(0, i, 1.0)
wt.SetGeomMg(seg_obj.GetMg())
wt.CalculateBoneStates(0)
# 创建蒙皮对象
skin = c4d.BaseObject(c4d.Oskin)
skin.SetName(f"{seg_obj.GetName()}_Skin")
skin.InsertUnder(seg_obj)
# 记录撤销
doc.AddUndo(c4d.UNDOTYPE_NEWOBJ, skin)
doc.AddUndo(c4d.UNDOTYPE_NEWOBJ, wt)
return seg_obj, wt, skin
def _clear_phy_cache_and_disable_dynamics (obj, doc):
try:
for t in obj.GetTags():
if t.GetType() == 180000102:
t.Remove() #删除标签
#t[c4d.RIGID_BODY_ENABLED] = False # 禁用标签
doc.AddUndo(c4d.UNDOTYPE_CHANGE, t)
except Exception:
pass
def _get_valid_children(null_obj):
"""获取Null下可处理的子物体(多边形/样条/实例/SDS,排除Null)"""
valid_objs = []
def walk(obj):
while obj:
# 只处理可编辑的几何体,排除Null
if (obj.IsInstanceOf(c4d.Opolygon) or obj.IsInstanceOf(c4d.Ospline) or
obj.IsInstanceOf(c4d.Oinstance) or obj.IsInstanceOf(c4d.Osds)):
valid_objs.append(obj)
# 递归遍历子层级
if obj.GetDown():
walk(obj.GetDown())
obj = obj.GetNext()
if null_obj and null_obj.IsInstanceOf(c4d.Onull):
walk(null_obj.GetDown())
return valid_objs
def bakeAnim(doc, joint, target_obj, min_f, max_f, fps, obj_idx, total_frames, total_objs, obj_name, progress_callback=None):
"""
逐帧烘焙Joint的纯世界坐标动画(核心:始终获取物体世界矩阵,无层级偏移)
"""
for frame_idx, frame in enumerate(range(min_f, max_f + 1)):
if frame_idx % 5 == 0 and progress_callback: # 每5帧更新一次进度,避免卡顿
progress_callback(
obj_idx * total_frames + frame_idx,
total_objs * total_frames,
f"烘焙 {obj_name} 第{frame}帧..."
)
# 设置当前时间并刷新文档(确保获取最新物体状态)
current_time = c4d.BaseTime(frame, fps)
doc.SetTime(current_time)
doc.ExecutePasses(None, True, True, True, 0)
# 关键:获取物体的**世界矩阵**(obj.GetMg()),不受任何父层级变换影响
obj_world_mg = target_obj.GetMg()
world_pos = obj_world_mg.off # 纯世界位置(x/y/z)
world_hpb = _mg_to_hpb(obj_world_mg) # 纯世界旋转(H/P/B)
# 烘焙位置关键帧(存储纯世界坐标,无层级偏移)
_add_key(_ensure_track(joint, c4d.DescID(
c4d.DescLevel(c4d.ID_BASEOBJECT_POSITION, c4d.DTYPE_VECTOR, 0),
c4d.DescLevel(c4d.VECTOR_X, c4d.DTYPE_REAL, 0)
)), current_time, world_pos.x)
_add_key(_ensure_track(joint, c4d.DescID(
c4d.DescLevel(c4d.ID_BASEOBJECT_POSITION, c4d.DTYPE_VECTOR, 0),
c4d.DescLevel(c4d.VECTOR_Y, c4d.DTYPE_REAL, 0)
)), current_time, world_pos.y)
_add_key(_ensure_track(joint, c4d.DescID(
c4d.DescLevel(c4d.ID_BASEOBJECT_POSITION, c4d.DTYPE_VECTOR, 0),
c4d.DescLevel(c4d.VECTOR_Z, c4d.DTYPE_REAL, 0)
)), current_time, world_pos.z)
# 烘焙旋转关键帧(存储纯世界坐标,无层级偏移)
_add_key(_ensure_track(joint, c4d.DescID(
c4d.DescLevel(c4d.ID_BASEOBJECT_ROTATION, c4d.DTYPE_VECTOR, 0),
c4d.DescLevel(c4d.VECTOR_X, c4d.DTYPE_REAL, 0)
)), current_time, world_hpb.x)
_add_key(_ensure_track(joint, c4d.DescID(
c4d.DescLevel(c4d.ID_BASEOBJECT_ROTATION, c4d.DTYPE_VECTOR, 0),
c4d.DescLevel(c4d.VECTOR_Y, c4d.DTYPE_REAL, 0)
)), current_time, world_hpb.y)
_add_key(_ensure_track(joint, c4d.DescID(
c4d.DescLevel(c4d.ID_BASEOBJECT_ROTATION, c4d.DTYPE_VECTOR, 0),
c4d.DescLevel(c4d.VECTOR_Z, c4d.DTYPE_REAL, 0)
)), current_time, world_hpb.z)
# =================================================================================
# UI 界面类
# =================================================================================
class BakeJointDialog(gui.GeDialog):
ID_BTN_BAKE = 1001
ID_TXT_STATUS = 1002
ID_GROUP_MAIN = 2000
ID_CHECK_POINT_CLEAN = 2001
def CreateLayout(self):
self.SetTitle("Bake Joint Anim (纯世界坐标版)")
if self.GroupBegin(self.ID_GROUP_MAIN, c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, 1, 0, "", 0):
self.GroupBorderSpace(10, 10, 10, 10)
# 说明文本(强调纯世界坐标)
self.AddStaticText(0, c4d.BFH_LEFT, 0, 0, "选中 Null 物体,烘焙其下子物体的纯世界坐标Joint动画(Null_Joints独立层级)", 0)
# 状态显示和复选框(同一行显示)
if self.GroupBegin(0, c4d.BFH_SCALEFIT | c4d.BFV_TOP, 2, 1, "", 0):
self.AddStaticText(self.ID_TXT_STATUS, c4d.BFH_SCALEFIT, 0, 0, "准备就绪", 0)
self.AddCheckbox(self.ID_CHECK_POINT_CLEAN, c4d.BFH_RIGHT | c4d.BFV_TOP, 180, 15, "删除子物体刚体标签")
self.GroupEnd()
# 按钮
self.AddButton(self.ID_BTN_BAKE, c4d.BFH_SCALEFIT, 0, 20, "开始烘焙 (Bake)")
self.GroupEnd()
return True
def Command(self, id, msg):
if id == self.ID_BTN_BAKE:
self.run_bake()
return True
def update_progress(self, current, total, message=""):
"""更新进度显示(C4D状态栏+UI文本)"""
if total > 0:
percent = int(100.0 * current / total)
c4d.StatusSetBar(percent)
else:
c4d.StatusSetBar(0)
c4d.StatusSetText(message)
self.SetString(self.ID_TXT_STATUS, message)
def run_bake(self):
doc = documents.GetActiveDocument()
if not doc:
gui.MessageDialog("无激活文档!")
return
# 1. 获取选中的Null物体
target_objs = doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_0)
"""if not selected_null or not selected_null.IsInstanceOf(c4d.Onull):
gui.MessageDialog("请先选中一个 Null 物体!")
return
# 2. 获取Null下的有效子物体
target_objs = _get_valid_children(selected_null)
if not target_objs:
gui.MessageDialog(f"选中的Null【{selected_null.GetName()}】下无有效子物体(多边形/样条/实例/SDS)!")
return """
# 3. 基础参数初始化
fps = doc.GetFps()
min_t = doc.GetMinTime()
max_t = doc.GetMaxTime()
min_f = int(min_t.GetFrame(fps))
max_f = int(max_t.GetFrame(fps))
total_frames = max_f - min_f + 1
total_objs = len(target_objs)
clean_points = self.GetBool(self.ID_CHECK_POINT_CLEAN) if hasattr(self, 'GetBool') else True
# 4. 准备工作(停止线程+开始撤销记录)
c4d.StopAllThreads()
doc.StartUndo()
try:
# 5. 创建独立的Joints根节点(关键修改1:脱离目标Null,放在场景根层级,自身零变换)
joints_root = c4d.BaseObject(c4d.Onull)
joints_root.SetName("root")
# 关键:插入到场景根层级,不嵌套在任何Null下(避免层级变换干扰)
doc.InsertObject(joints_root, None, None)
# 关键修改2:设置Joints根节点为默认单位矩阵,确保无位移/旋转/缩放值
joints_root.SetMg(c4d.Matrix()) # 单位矩阵:位移(0,0,0)、旋转(0,0,0)、缩放(1,1,1)
doc.AddUndo(c4d.UNDOTYPE_NEWOBJ, joints_root)
# 6. 遍历每个子物体处理(烘焙纯世界坐标动画)
for obj_idx, target_obj in enumerate(target_objs):
obj_name = target_obj.GetName()
self.update_progress(obj_idx, total_objs, f"处理物体: {obj_name} ({obj_idx+1}/{total_objs})")
# 6.1 清除物理缓存+禁用动力学(根据复选框状态)
if clean_points:
_clear_phy_cache_and_disable_dynamics(target_obj, doc)
# 6.2 创建Joint(关键:先插入到target_obj下面,保持零变换)
joint = c4d.BaseObject(c4d.Ojoint)
joint.SetName(f"Joint_{obj_name}")
# 关键:先插入到target_obj下面,保持零变换
joint.InsertUnder(target_obj)
# 设置Joint的初始矩阵为单位矩阵(零变换)
joint.SetMg(c4d.Matrix())
doc.AddUndo(c4d.UNDOTYPE_NEWOBJ, joint)
# 6.3 逐帧烘焙Joint的纯世界坐标动画
bakeAnim(doc, joint, target_obj, min_f, max_f, fps, obj_idx, total_frames, total_objs, obj_name, self.update_progress)
# 6.4 烘焙完成后,将Joint移动到独立的Joints根节点
joint.InsertUnder(joints_root)
# 重新设置Joint的世界坐标为烘焙后的数据
joint.SetMg(target_obj.GetMg())
# 6.5 给物体绑定蒙皮到Joint(100%权重,基于世界坐标匹配)
final_obj, _, _ = _create_skin_binding(target_obj, joint, doc)
# 收集最终物体,用于后续清理
if final_obj:
# 更新列表中的引用(虽然target_objs循环中不会用到,但为了保持一致性)
target_objs[obj_idx] = final_obj
# 7. 完成提示
# 恢复并修复原 327 行逻辑:无条件删除选择物体(处理后的最终物体)的动力学标签
for obj in target_objs:
if obj and obj.IsAlive():
_clear_phy_cache_and_disable_dynamics(obj, doc)
# 如果勾选了删除子物体刚体标签(虽然目前逻辑主要是处理单个物体,保留此接口以符合作者意图)
if clean_points:
# 这里可以添加针对子物体的额外清理逻辑,或者如果target_objs本身就是"子物体",则上面的循环已经处理了
pass
self.update_progress(total_objs * total_frames, total_objs * total_frames, "全部处理完成!")
c4d.StatusClear()
gui.MessageDialog(f"✅ 烘焙完成!\n共处理 {total_objs} 个物体\n{joints_root.GetName()} 为独立层级(零变换),Joint存储纯世界坐标动画")
except Exception as e:
# 异常处理
traceback.print_exc()
gui.MessageDialog(f"❌ 处理出错: {str(e)}")
finally:
# 结束撤销并刷新界面
doc.EndUndo()
c4d.EventAdd()
c4d.StatusClear()
# 全局Dialog引用
dlg = None
def main():
global dlg
# 关闭旧窗口
if dlg is not None:
dlg.Close()
# 打开新窗口
dlg = BakeJointDialog()
dlg.Open(c4d.DLG_TYPE_ASYNC, defaultw=450, defaulth=160)
if __name__ == "__main__":
main()
更多推荐


所有评论(0)