一 、 塌陷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()

更多推荐