【C4D实用脚本】清除废点及删除了面的选择tag和材质tag及材质--AI编程

注意!为了优化多子材质物体,会清除没有选择集的材质,出错的请自行补上。

【标签脚本】清除选择和材质tag.py

源码:

python 复制代码
import c4d
import traceback

class CleanupDialog(c4d.gui.GeDialog):
    """C4D标签&材质清理工具(兼容全版本)- 新增孤立点清理"""
    ID_BTN_RUN = 1000
    ID_TEXT_LOG = 1001
    ID_CHECK_POINT_CLEAN = 1002  # 新增孤立点清理勾选框

    def __init__(self):
        c4d.gui.GeDialog.__init__(self)
        self.log_content = ""
        self.is_running = False  # 防止重复执行

    def CreateLayout(self):
        """兼容版可视化界面(修复AddCheckbox参数错误)"""
        self.SetTitle("C4D标签&材质+模型优化工具")
        # 核心修复:AddButton参数格式(布局标识 + 宽度 + 高度 + 按钮文本)
        self.AddButton(self.ID_BTN_RUN, c4d.BFH_CENTER | c4d.BFV_TOP, 200, 30, "执行清理(先选对象)")
        
        # 修复AddCheckbox参数:仅传递5个参数(id, flags, width, height, name)
        self.AddCheckbox(self.ID_CHECK_POINT_CLEAN, c4d.BFH_LEFT | c4d.BFV_TOP, 300, 15, 
                         "清理无依赖面的孤立点(模型优化)")
        self.SetBool(self.ID_CHECK_POINT_CLEAN, True)  # 默认勾选
        
        # 日志标题
        self.AddStaticText(0, c4d.BFH_LEFT | c4d.BFV_TOP, 0, 15, "运行日志")
        
        # 日志区域
        self.AddMultiLineEditText(self.ID_TEXT_LOG, c4d.BFH_SCALE | c4d.BFV_SCALE, 580, 320)
        
        try:
            # 兼容不同版本的只读标识
            self.SetEditTextFlags(self.ID_TEXT_LOG, c4d.EDITTEXTFLAGS_READONLY)
        except:
            try:
                self.SetEditTextFlags(self.ID_TEXT_LOG, 2)  # 旧版本只读标识
            except:
                pass
        return True

    def AddLog(self, text):
        """添加并刷新日志(实时滚动到底部)"""
        self.log_content += text + "\n"
        try:
            self.SetString(self.ID_TEXT_LOG, self.log_content)
            # 兼容滚动逻辑(避免版本差异报错)
            try:
                self.SendMsgToChild(self.ID_TEXT_LOG, 100, c4d.BaseContainer(), len(self.log_content))
            except:
                pass
        except:
            print(text)  # 降级输出到控制台

    def ClearLog(self):
        """清空日志"""
        self.log_content = ""
        try:
            self.SetString(self.ID_TEXT_LOG, "")
        except:
            pass

    def is_material_used(self, doc, material):
        """
        精准判断材质是否被使用(等效C4D原生逻辑)
        :param doc: 文档对象
        :param material: 要判断的材质
        :return: True=被使用,False=未被使用
        """
        def traverse_objects(obj):
            if not obj:
                return False
            # 检查当前对象的材质标签
            for tag in obj.GetTags():
                if tag.CheckType(c4d.Ttexture):
                    if tag.GetMaterial() == material:
                        return True
            # 递归检查子对象
            for child in obj.GetChildren():
                if traverse_objects(child):
                    return True
            return False

        # 检查文档所有根对象
        for root_obj in doc.GetObjects():
            if traverse_objects(root_obj):
                return True
        return False

    def delete_unused_points(self, doc, obj):
        """
        修复版:基于C4D官方API删除孤立点
        核心:重构点数组+多边形索引,通过ResizeObject生效
        """
        deleted_point_count = 0
        obj_name = obj.GetName() if hasattr(obj, 'GetName') else "未知对象"


        # 仅处理多边形对象
        if not obj.CheckType(c4d.Opolygon):
            self.AddLog(f"  ⚠️ {obj_name} 不是多边形对象,跳过孤立点清理")
            return deleted_point_count


        try:
            # 1. 获取原始数据
            original_points = obj.GetAllPoints()  # 原始点坐标数组
            original_polys = obj.GetAllPolygons() # 原始多边形数组
            point_count = len(original_points)
            poly_count = len(original_polys)


            if point_count == 0:
                self.AddLog(f"  ℹ️ {obj_name} 无顶点数据,跳过孤立点清理")
                return deleted_point_count


            # 2. 标记被多边形使用的点
            used_points = set()
            for poly in original_polys:
                used_points.add(poly.a)
                used_points.add(poly.b)
                used_points.add(poly.c)
                if poly.c != poly.d:
                    used_points.add(poly.d)


            unused_count = point_count - len(used_points)
            if unused_count == 0:
                self.AddLog(f"  ℹ️ {obj_name} 无孤立点需要清理(总顶点数:{point_count})")
                return deleted_point_count


            # 3. 构建新数据:仅保留使用的点 + 重构多边形索引
            self.AddLog(f"  📊 {obj_name} - 总顶点数:{point_count} | 孤立点数:{unused_count}")
            
            # 映射:原始点索引 → 新点索引
            old_to_new_index = {}
            new_points = []
            new_index = 0
            for old_idx in range(point_count):
                if old_idx in used_points:
                    old_to_new_index[old_idx] = new_index
                    new_points.append(original_points[old_idx])
                    new_index += 1


            # 重构多边形(更新顶点索引)
            new_polys = []
            for poly in original_polys:
                # 替换每个顶点的旧索引为新索引
                a = old_to_new_index[poly.a]
                b = old_to_new_index[poly.b]
                c = old_to_new_index[poly.c]
                d = old_to_new_index[poly.d] if poly.c != poly.d else c
                new_polys.append(c4d.CPolygon(a, b, c, d))


            # 4. 应用修改(核心:ResizeObject + 重新赋值)
            doc.AddUndo(c4d.UNDOTYPE_CHANGE, obj)
            obj.ResizeObject(len(new_points), len(new_polys))  # 重置点/多边形数量
            obj.SetAllPoints(new_points)                      # 设置新点坐标
            for i in range(len(new_polys)):
                obj.SetPolygon(i, new_polys[i])               # 设置新多边形


            # 5. 刷新对象(关键:必须调用Message更新)
            obj.Message(c4d.MSG_UPDATE)
            obj.Message(c4d.MSG_CHANGE)
            c4d.EventAdd()


            deleted_point_count = unused_count
            self.AddLog(f"  ✂️ {obj_name} - 成功删除 {deleted_point_count} 个孤立点")


        except Exception as e:
            self.AddLog(f"  ❌ {obj_name} 孤立点清理失败:{str(e)}")
            self.AddLog(f"  📝 错误详情:{traceback.format_exc()[:200]}")
        
        return deleted_point_count

    def CleanupProcess(self):
        """核心清理逻辑(加锁防止重复执行)"""
        if self.is_running:
            self.AddLog("⚠️  清理操作正在执行中,请等待!")
            return
        
        self.is_running = True
        self.ClearLog()
        self.AddLog("===== 开始执行清理操作 =====")

        doc = c4d.documents.GetActiveDocument()
        doc.StartUndo()  # 开启撤销群组

        # 统计变量
        deleted_sel_tag_count = 0
        deleted_mat_tag_count = 0
        deleted_material_count = 0
        deleted_point_count = 0  # 新增孤立点统计

        # 获取是否启用孤立点清理
        clean_points = self.GetBool(self.ID_CHECK_POINT_CLEAN) if hasattr(self, 'GetBool') else True

        try:
            # 兼容多版本获取选中对象
            try:
                objs = doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_CHILDREN)
            except:
                objs = doc.GetActiveObjects(1) if hasattr(doc, 'GetActiveObjects') else []

            if not objs:
                self.AddLog("❌ 错误:请先在C4D主窗口选中需要处理的对象!")
                self.is_running = False
                return

            # 遍历处理选中对象
            for obj in objs:
                obj_name = obj.GetName() if hasattr(obj, 'GetName') else "未知对象"
                self.AddLog(f"\n📌 正在处理对象:{obj_name}")
                
                # 1. 清理空多边形选择标签(精准判断空选择集)
                poly_sel_tags = [tag for tag in obj.GetTags() if tag.CheckType(c4d.Tpolygonselection)]
                valid_poly_sel_names = set()

                for sel_tag in poly_sel_tags:
                    # 安全获取选择面数
                    sel_count = 0
                    try:
                        sel_base = sel_tag.GetBaseSelect()
                        sel_count = sel_base.GetCount() if sel_base else 0
                    except:
                        sel_count = 0
                    
                    tag_name = sel_tag.GetName() if hasattr(sel_tag, 'GetName') else "未知标签"
                    self.AddLog(f"  📄 选择标签[{tag_name}] → 选择面数:{sel_count}")

                    if sel_count == 0:
                        self.AddLog(f"  ✂️ 删除空选择标签:{tag_name}")
                        doc.AddUndo(c4d.UNDOTYPE_DELETE, sel_tag)
                        sel_tag.Remove()
                        deleted_sel_tag_count += 1
                    else:
                        valid_poly_sel_names.add(tag_name)

                # 2. 清理无效材质标签(精准判断引用有效性)
                material_tags = [tag for tag in obj.GetTags() if tag.CheckType(c4d.Ttexture)]
                for mat_tag in material_tags:
                    # 安全获取选择集名称
                    mat_sel_name = ""
                    try:
                        mat_sel_name = mat_tag[c4d.TEXTURETAG_RESTRICTION] or ""
                    except:
                        mat_sel_name = ""

                    # 安全获取材质信息
                    mat = mat_tag.GetMaterial() if hasattr(mat_tag, 'GetMaterial') else None
                    mat_name = mat.GetName() if (mat and hasattr(mat, 'GetName')) else "无材质"

                    # 判断删除条件
                    need_delete = False
                    delete_reason = ""
                    if not mat_sel_name:
                        need_delete = True
                        delete_reason = "未设置任何选择集"
                    elif mat_sel_name not in valid_poly_sel_names:
                        need_delete = True
                        delete_reason = f"引用无效选择集:{mat_sel_name}"

                    if need_delete:
                        self.AddLog(f"  ✂️ 删除材质标签[{mat_name}]:{delete_reason}")
                        doc.AddUndo(c4d.UNDOTYPE_DELETE, mat_tag)
                        mat_tag.Remove()
                        deleted_mat_tag_count += 1

                # 3. 新增:清理孤立点(可选功能)
                if clean_points:
                    deleted_point_count += self.delete_unused_points(doc, obj)

            # 4. 清理未使用材质
            self.AddLog("\n===== 开始清理未使用材质 =====")
            all_materials = doc.GetMaterials() if hasattr(doc, 'GetMaterials') else []
            
            for mat in all_materials:
                if not self.is_material_used(doc, mat):
                    mat_name = mat.GetName() if hasattr(mat, 'GetName') else "未知材质"
                    self.AddLog(f"  ✂️ 删除未使用材质:{mat_name}")
                    doc.AddUndo(c4d.UNDOTYPE_DELETE, mat)
                    mat.Remove()
                    deleted_material_count += 1

            # 清理结果汇总
            self.AddLog("\n===== 清理完成!汇总信息 =====")
            self.AddLog(f"✅ 共删除 {deleted_sel_tag_count} 个空多边形选择标签")
            self.AddLog(f"✅ 共删除 {deleted_mat_tag_count} 个无效材质标签")
            self.AddLog(f"✅ 共删除 {deleted_material_count} 个未使用材质")
            if clean_points:
                self.AddLog(f"✅ 共删除 {deleted_point_count} 个无依赖面的孤立点")
            if deleted_sel_tag_count + deleted_mat_tag_count + deleted_material_count + deleted_point_count == 0:
                self.AddLog("ℹ️  未发现需要清理的内容")

            # 提交撤销并刷新界面
            doc.EndUndo()
            c4d.EventAdd()

        except Exception as e:
            self.AddLog(f"\n❌ 执行出错:{str(e)}")
            self.AddLog(f"❌ 错误详情:{traceback.format_exc()}")
            doc.EndUndo()  # 确保撤销群组关闭
        finally:
            self.is_running = False  # 释放执行锁

    def Command(self, id, msg):
        """按钮点击事件"""
        if id == self.ID_BTN_RUN:
            self.CleanupProcess()
        return True

# 全局窗口实例(防止重复创建)
g_dlg = None

def main():
    """启动工具(兼容全版本)"""
    global g_dlg
    try:
        # 关闭已有窗口
        if g_dlg and g_dlg.IsOpen():
            g_dlg.Close()
        
        # 创建非阻塞窗口(移除SetAlwaysOnTop兼容低版本)
        g_dlg = CleanupDialog()
        g_dlg.Open(
            c4d.DLG_TYPE_ASYNC,  # 非阻塞模式(核心)
            xpos=-1, ypos=-1, 
            defaultw=400, defaulth=450  # 调整窗口大小适配新选项
        )

    except Exception as e:
        # 窗口启动失败时降级执行核心逻辑
        print(f"⚠️  窗口启动失败:{str(e)}")
        print("===== 直接执行清理逻辑 =====")
        doc = c4d.documents.GetActiveDocument()
        try:
            objs = doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_CHILDREN)
        except:
            objs = doc.GetActiveObjects(1) if hasattr(doc, 'GetActiveObjects') else []
        
        if not objs:
            print("❌ 请先选中需要处理的对象!")
        else:
            dlg = CleanupDialog()
            dlg.CleanupProcess()

if __name__ == "__main__":
    main()

【界面脚本】

相关推荐
傅里叶1 天前
Flutter移动端获取相机内参
前端·flutter
哒哒哒5285201 天前
React useMemo 大白话用法文档(含注意项)
前端
xkxnq1 天前
第一阶段:Vue 基础入门(第 10 天)
前端·javascript·vue.js
智商偏低1 天前
abp PermissionDefinitionManager源码解析
开发语言·前端·javascript
RaidenLiu1 天前
Offstage / Visibility:不可见真的就不消耗性能吗
前端·flutter·性能优化
lgliuying1 天前
wangEditor5 富文本编辑器中使用 kityformula 公式编辑器的具体实践
前端·javascript·html
RisunJan1 天前
Linux命令-iotop命令(实时磁盘 I/O 监控工具)
linux·运维·服务器
闲人编程1 天前
商品管理与库存系统
服务器·网络·数据库·python·api·数据模型·codecapsule
PBitW1 天前
Electron 脚本调用大坑!害惨我了
前端·electron