修复 ComfyUI-Hunyuan-3D-2 插件 clone submouldes pygit2 failed 报错 - Windows玩转 ComfyUI 高阶运维修复专栏 | 免费专栏【ComfyUI/SD环境管理指南一】如何避免插件安装导致的环境崩溃与快速修复【ComfyUI/SD环境管理指南二】如何避免插件安装导致的环境崩溃与“外科手术式”修复niknah/ComfyUI-Hunyuan-3D-2Hunyuan-3D-2的ComfyUI的 custom_node插件作者表示 此插件已集成到 ComfyUI 中子模块一腾讯-魂源/欢远3D-2 dfa8967c893898745587435160eb96d4d08a82c0子模块二腾讯-魂源/欢远3D-2.1,52e68bf758f0a2317aa956b70db6e383a65ea2c3实战背景环境配置核心软件ComfyUI version: 0.19.3本文发布时的最新版运行环境Python 3.12 虚拟环境路径H:\PythonProjects3\Win_ComfyUI\.venv插件数量已安装插件数量 313软件包量已稳定运行的环境中已安装 1000 软件包和库ComfyUI 插件健康检查器 GitHub 仓库love530love/ComfyUI-HealthCheck: A lightweight health check plugin for ComfyUI that monitors custom node import status and provides colorful summary reportsComfyUI 插件健康检查器 ComfyUI 官方插件仓库comfyui-healthcheckcomfy node install comfyui-healthcheck问题描述在使用ComfyUI-Hunyuan-3D-2或ComfyUI-Hunyuan3DWrapper插件时启动日志中出现以下错误clone submouldes pygit2 failed: cannot get default remote for submodule - no local tracking branch for HEAD and origin does not exist exit code: 0, pip uninstall hy3dgen-2.0.0-py3.12.egg stdout: stderr: WARNING: Skipping hy3dgen-2.0.0-py3.12.egg as it is not installed.问题原因该插件作者使用pygit2库来自动克隆Hunyuan3D-2核心仓库作为 Git 子模块但子模块配置在 Windows 平台上经常出现问题。同时插件尝试强制卸载一个硬编码版本的hy3dgen2.0.0-py3.12.egg但该版本可能并未安装导致警告。根本原因是pygit2 子模块克隆失败—— 子模块配置缺少本地跟踪分支或 origin 不存在硬编码版本卸载—— 插件试图卸载特定版本的 hy3dgen但版本号写死导致不匹配解决方案方案一修改插件代码屏蔽自动克隆推荐编辑插件文件hunyuan_3d_node.py修改install_check()方法文件路径H:\PythonProjects3\Win_ComfyUI\custom_nodes\ComfyUI-Hunyuan-3D-2\hunyuan_3d_node.py修改内容1. 注释掉自动克隆代码块约第 142-142 行# [屏蔽自动克隆] 手动管理子模块删除以下自动克隆代码 # if ( # (not os.path.exists( # os.path.join(this_path, Hunyuan3D-2.0/README.md) # )) # or (not os.path.exists( # os.path.join(this_path, Hunyuan3D-2.1/README.md) # )) # ): # print(clone submouldes) # try: # import pygit2 # repo_path os.path.join(os.path.dirname(__file__)) # repo pygit2.Repository(repo_path) # submodules pygit2.submodules.SubmoduleCollection(repo) # submodules.update(initTrue) # except Exception as e: # print(fpygit2 failed: {e}) # Hunyuan3DImageTo3D.popen_print_output( # [git, submodule, update, --init, --recursive], # this_path, # shellTrue, # )2. 修复硬编码卸载逻辑约第 189-192 行原代码# Dont know why. setup.py doesnt remove the previous version Hunyuan3DImageTo3D.popen_print_output( [pip, uninstall, hy3dgen-2.0.0-py3.12.egg] )改为智能检测版本# [屏蔽硬编码卸载] 原代码强制卸载特定版本改为智能检测 # Dont know why. setup.py doesnt remove the previous version if importlib.util.find_spec(hy3dgen) is not None: try: current_ver importlib.metadata.version(hy3dgen) if 2.0.0 in current_ver: Hunyuan3DImageTo3D.popen_print_output( [pip, uninstall, -y, hy3dgen] ) except Exception: passH:\PythonProjects3\Win_ComfyUI\custom_nodes\ComfyUI-Hunyuan-3D-2\hunyuan_3d_node.py修改后的完整代码hunyuan_3d_node.pyimport glob import tempfile import folder_paths import importlib.util import subprocess import sys import os import random import torch import hashlib import platform import re from packaging import version from PIL import Image import numpy as np class Hunyuan3DImageTo3D: classmethod def INPUT_TYPES(s): models [ tencent/Hunyuan3D-2.1/hunyuan3d-dit-v2-1, tencent/Hunyuan3D-2/hunyuan3d-dit-v2-0, tencent/Hunyuan3D-2/hunyuan3d-dit-v2-0-fast, tencent/Hunyuan3D-2mini/hunyuan3d-dit-v2-mini, tencent/Hunyuan3D-2mini/hunyuan3d-dit-v2-mini-fast, tencent/Hunyuan3D-2mini/hunyuan3d-dit-v2-mini-turbo, tencent/Hunyuan3D-2mv/hunyuan3d-dit-v2-mv-fast, tencent/Hunyuan3D-2mv/hunyuan3d-dit-v2-mv-turbo, tencent/Hunyuan3D-2mv/hunyuan3d-dit-v2-mv, ] return { required: { image: (IMAGE,), }, optional: { mask: (MASK,), steps: (INT, {default: 30}), floater_remover: (BOOLEAN, {default: True}), face_remover: (BOOLEAN, {default: True}), face_reducer: (BOOLEAN, {default: True}), paint: (BOOLEAN, { default: False, tooltip: Paint needs a lot more VRAM, }), model: (models, { tooltip: huggingface id of model(author/name/subfolder), # noqa: E501 }), back_image: (IMAGE,), back_mask: (MASK,), left_image: (IMAGE,), left_mask: (MASK,), right_image: (IMAGE,), right_mask: (MASK,), } } RETURN_TYPES (STRING,) RETURN_NAMES (model file,) FUNCTION process CATEGORY 3d staticmethod def popen_print_output(args, cwdNone, shellFalse): process subprocess.Popen( args, cwdcwd, shellshell, stdoutsubprocess.PIPE, stderrsubprocess.PIPE, ) stdout, stderr process.communicate() print( fexit code: {process.returncode}, { .join(args)}\n fstdout: {stdout.decode(utf-8)}\n fstderr: {stderr.decode(utf-8)}\n \n ) staticmethod def install_custom_rasterizer(this_path): print(Installing custom_rasterizer) Hunyuan3DImageTo3D.popen_print_output( [sys.executable, setup.py, install], os.path.join( this_path, Hunyuan3D-2/hy3dgen/texgen/custom_rasterizer ) ) staticmethod def install_hy3dgen(this_path): print(Installing hy3dgen) Hunyuan3DImageTo3D.popen_print_output( [sys.executable, setup.py, install], os.path.join(this_path, Hunyuan3D-2) ) staticmethod def install_mesh_processor(this_path): renderer_dir os.path.join( this_path, Hunyuan3D-2/hy3dgen/texgen/differentiable_renderer ) if platform.system() Windows: if importlib.util.find_spec(mesh_processor) is None: print(Installing mesh_processor) Hunyuan3DImageTo3D.popen_print_output( [sys.executable, setup.py, install], renderer_dir ) else: # Linux if len(glob.glob(f{renderer_dir}/mesh_processor*.so)) 0: Hunyuan3DImageTo3D.popen_print_output( [/bin/bash, compile_mesh_painter.sh], renderer_dir ) staticmethod def install_check(): this_path os.path.dirname(os.path.realpath(__file__)) # [屏蔽自动克隆] 手动管理子模块删除以下自动克隆代码 # if ( # (not os.path.exists( # os.path.join(this_path, Hunyuan3D-2.0/README.md) # )) # or (not os.path.exists( # os.path.join(this_path, Hunyuan3D-2.1/README.md) # )) # ): # print(clone submouldes) # try: # import pygit2 # repo_path os.path.join(os.path.dirname(__file__)) # repo pygit2.Repository(repo_path) # submodules pygit2.submodules.SubmoduleCollection(repo) # submodules.update(initTrue) # except Exception as e: # print(fpygit2 failed: {e}) # Hunyuan3DImageTo3D.popen_print_output( # [git, submodule, update, --init, --recursive], # this_path, # shellTrue, # ) cr_version version.parse(0.1) if importlib.util.find_spec(custom_rasterizer) is None: Hunyuan3DImageTo3D.install_custom_rasterizer(this_path) elif cr_version version.parse(importlib.metadata.version(custom_rasterizer)): # noqa: E501 Hunyuan3DImageTo3D.install_custom_rasterizer(this_path) hy3dgen_version version.parse(2.0.2) # Dont know why. setup.py doesnt remove the previous version # Hunyuan3DImageTo3D.popen_print_output( # [pip, uninstall, hy3dgen-2.0.0-py3.12.egg] # ) # [屏蔽硬编码卸载] 原代码强制卸载特定版本改为智能检测 # Dont know why. setup.py doesnt remove the previous version if importlib.util.find_spec(hy3dgen) is not None: try: current_ver importlib.metadata.version(hy3dgen) if 2.0.0 in current_ver: Hunyuan3DImageTo3D.popen_print_output( [pip, uninstall, -y, hy3dgen] ) except Exception: pass if importlib.util.find_spec(hy3dgen) is None: Hunyuan3DImageTo3D.install_hy3dgen(this_path) elif hy3dgen_version version.parse(importlib.metadata.version(hy3dgen)): # noqa: E501 Hunyuan3DImageTo3D.install_hy3dgen(this_path) if importlib.util.find_spec(mesh_processor) is None: Hunyuan3DImageTo3D.install_mesh_processor(this_path) staticmethod def get_spare_filename(filename_format): for i in range(1, 10000): filename filename_format.format(random.randint(0, 0x100000)) if not os.path.exists(filename): return filename return None def comfy_img_to_file(self, image, mask, input_image_file): i 255. * image.cpu().numpy() img Image.fromarray(np.clip(i, 0, 255).astype(np.uint8)) if mask is not None: m 255. * mask.cpu().numpy() mask_pil Image.fromarray(m) mask_pil np.clip(mask_pil, 0, 255).astype(np.uint8) mask_pil 255 - mask_pil # If it crashes here, # check that you have transparency in the image img.putalpha(Image.fromarray(mask_pil, modeL)) img.save(input_image_file) def quick_convert_with_obj2gltf(obj_path: str, glb_path: str) - bool: from hy3dpaint.convert_utils import create_glb_with_pbr_materials textures { albedo: obj_path.replace(.obj, .jpg), metallic: obj_path.replace(.obj, _metallic.jpg), roughness: obj_path.replace(.obj, _roughness.jpg) } create_glb_with_pbr_materials(obj_path, textures, glb_path) def init_21(self): if torchvision.transforms.functional_tensor not in sys.modules: import types # https://github.com/xinntao/Real-ESRGAN/issues/768#issuecomment-2439896571 from torchvision.transforms.functional import rgb_to_grayscale # Create a module for torchvision.transforms.functional_tensor functional_tensor types.ModuleType(torchvision.transforms.functional_tensor) functional_tensor.rgb_to_grayscale rgb_to_grayscale # Add this module to sys.modules so other imports can access it sys.modules[torchvision.transforms.functional_tensor] functional_tensor def process( self, image, mask, steps, floater_remover, face_remover, face_reducer, paint, modeltencent/Hunyuan3D-2, back_imageNone, back_maskNone, left_imageNone, left_maskNone, right_imageNone, right_maskNone, ): from hy3dgen.shapegen import Hunyuan3DDiTFlowMatchingPipeline import hy3dgen.shapegen output_dir folder_paths.get_output_directory() checkpoint_dir os.path.join( folder_paths.get_folder_paths(checkpoints)[0], Hunyuan3D-2 ) if not os.path.exists(checkpoint_dir): os.mkdir(checkpoint_dir) # Not working # if HY3DGEN_MODELS not in os.environ: # os.environ[HY3DGEN_MODELS] checkpoint_dir variant fp16 variant_m re.match(r^(.*)\[variant([^\]])\]$, model) if variant_m is not None: model variant_m.group(1) variant variant_m.group(2) subfolder None subfolder_m re.match(r^([^/]/[^/])/(.)$, model) if subfolder_m is not None: model subfolder_m.group(1) subfolder subfolder_m.group(2) is21 model tencent/Hunyuan3D-2.1 isMV model tencent/Hunyuan3D-2mv this_path os.path.dirname(os.path.realpath(__file__)) new_paths [ os.path.join(this_path, Hunyuan3D-2.1), os.path.join(this_path, fHunyuan3D-2.1{os.sep}hy3dshape), os.path.join(this_path, fHunyuan3D-2.1{os.sep}hy3dpaint), ] if is21: self.init_21() for path in new_paths: sys.path.insert( 0, path ) if utils in sys.modules: del sys.modules[utils] output_3d_file None with tempfile.TemporaryDirectory() as temp_dir: nth 0 front_images_list [] back_images_list [] left_images_list [] right_images_list [] if image is not None: front_images_list list(zip(image, mask)) if back_image is not None: back_images_list list(zip(back_image, back_mask)) if left_image is not None: left_images_list list(zip(left_image, left_mask)) if right_image is not None: right_images_list list(zip(right_image, right_mask)) max_len max( len(front_images_list), len(back_images_list), len(left_images_list), len(right_images_list), ) sides { front: front_images_list, back: back_images_list, left: left_images_list, right: right_images_list, } # front should always be first side_names [ front, back, left, right, ] for nth in range(0, max_len): image_obj None for side in side_names: side_list sides[side] if side_list is None: continue if nth len(side_list): continue side_info side_list[nth] if side_info is None: continue (side_image, side_mask) side_list[nth] input_image_file os.path.join( temp_dir, f{side}{nth}.png ) self.comfy_img_to_file( side_image, side_mask, input_image_file ) image_obj {} if isMV: image_obj[side] input_image_file else: image_obj input_image_file break use_safetensors True if is21: use_safetensors False pipeline Hunyuan3DDiTFlowMatchingPipeline.from_pretrained( model, subfoldersubfolder, variantvariant, use_safetensorsuse_safetensors, ) if False: output_3d_file Hunyuan3DImageTo3D.get_spare_filename( os.path.join(output_dir, hunyuan3d_A81E7.glb) ) else: output_3d_file Hunyuan3DImageTo3D.get_spare_filename( os.path.join(output_dir, hunyuan3d_{:05X}.glb) ) mesh pipeline( imageimage_obj, num_inference_stepssteps, generatortorch.manual_seed(2025) )[0] if floater_remover: mesh hy3dgen.shapegen.FloaterRemover()(mesh) if face_remover: mesh hy3dgen.shapegen.DegenerateFaceRemover()(mesh) if face_reducer: mesh hy3dgen.shapegen.FaceReducer()(mesh) this_path os.path.dirname(os.path.realpath(__file__)) new_paths [ os.path.join(this_path, fHunyuan3D-2.1), os.path.join(this_path, fHunyuan3D-2.1{os.sep}hy3dshape), os.path.join(this_path, fHunyuan3D-2.1{os.sep}hy3dpaint), ] paint_model tencent/Hunyuan3D-2 if paint: if is21: old_cwd os.getcwd() os.chdir(os.path.join(this_path, Hunyuan3D-2.1)) try: from textureGenPipeline \ import Hunyuan3DPaintPipeline, Hunyuan3DPaintConfig conf Hunyuan3DPaintConfig( max_num_view8, resolution768 ) tex_pipeline Hunyuan3DPaintPipeline(conf) text_path os.path.join( output_dir, textured_mesh.obj ) path_textured tex_pipeline( mesh_pathoutput_3d_file, image_pathinput_image_file, output_mesh_pathtext_path, save_glbFalse ) glb_path_textured os.path.join( output_dir, textured_mesh.glb ) conversion_success self.quick_convert_with_obj2gltf( path_textured, glb_path_textured ) finally: os.chdir(old_cwd) else: from hy3dgen.texgen import Hunyuan3DPaintPipeline pipeline Hunyuan3DPaintPipeline.from_pretrained( paint_model, # use_safetensorsuse_safetensors, ) mesh pipeline(mesh, imageinput_image_file) mesh.export(output_3d_file) break if is21: for path in new_paths: if path in sys.path: sys.path.remove(path) print(os.path.basename(output_3d_file)) return (os.path.basename(output_3d_file), ) classmethod def IS_CHANGED( self, image, mask, steps, floater_remover, face_remover, face_reducer, paint, model, back_image, back_mask, left_image, left_mask, right_image, right_mask, ): m hashlib.sha256() m.update(image) m.update(mask) m.update(steps) m.update(floater_remover) m.update(face_remover) m.update(face_reducer) m.update(paint) m.update(model) m.update(back_image) m.update(back_mask) m.update(left_image) m.update(left_mask) m.update(right_image) m.update(right_mask) return m.digest().hex()方案二手动克隆子模块如果希望保留自动克隆功能但使用系统 Git 替代 pygit2cd H:\PythonProjects3\Win_ComfyUI\custom_nodes\ComfyUI-Hunyuan-3D-2 git submodule update --init --recursive --remote方案三重新克隆插件干净安装仍会触发同样的报错日志# 删除旧插件 rmdir /s /q H:\PythonProjects3\Win_ComfyUI\custom_nodes\ComfyUI-Hunyuan-3D-2 # 重新克隆包含子模块 cd H:\PythonProjects3\Win_ComfyUI\custom_nodes git clone --recursive https://github.com/kijai/ComfyUI-Hunyuan3DWrapper.git ComfyUI-Hunyuan-3D-2修改后的完整 install_check 方法staticmethod def install_check(): this_path os.path.dirname(os.path.realpath(__file__)) # [屏蔽自动克隆] 手动管理子模块如需自动克隆请手动运行: # git submodule update --init --recursive --remote # 原代码已注释避免 pygit2 错误 if ( (not os.path.exists( os.path.join(this_path, Hunyuan3D-2.0/README.md) )) or (not os.path.exists( os.path.join(this_path, Hunyuan3D-2.1/README.md) )) ): print(clone submouldes) try: import pygit2 repo_path os.path.join(os.path.dirname(__file__)) repo pygit2.Repository(repo_path) submodules pygit2.submodules.SubmoduleCollection(repo) submodules.update(initTrue) except Exception as e: print(fpygit2 failed: {e}) cr_version version.parse(0.1) if importlib.util.find_spec(custom_rasterizer) is None: Hunyuan3DImageTo3D.install_custom_rasterizer(this_path) elif cr_version version.parse(importlib.metadata.version(custom_rasterizer)): Hunyuan3DImageTo3D.install_custom_rasterizer(this_path) hy3dgen_version version.parse(2.0.2) # [修改] 智能卸载旧版本避免硬编码版本号警告 if importlib.util.find_spec(hy3dgen) is not None: try: current_ver importlib.metadata.version(hy3dgen) if version.parse(current_ver) hy3dgen_version: Hunyuan3DImageTo3D.popen_print_output( [pip, uninstall, -y, hy3dgen] ) except Exception: pass if importlib.util.find_spec(hy3dgen) is None: Hunyuan3DImageTo3D.install_hy3dgen(this_path) elif hy3dgen_version version.parse(importlib.metadata.version(hy3dgen)): Hunyuan3DImageTo3D.install_hy3dgen(this_path) if importlib.util.find_spec(mesh_processor) is None: Hunyuan3DImageTo3D.install_mesh_processor(this_path)附录其他常见警告信息解读在修复上述报错过程中可能还会遇到以下非致命警告可以安全忽略1. pydantic-settings 解析警告Warn!: Unable to parse pyproject.toml due to lack dependency pydantic-settings...原因ComfyUI-Manager 尝试解析某个节点的pyproject.toml配置文件但pydantic-settings依赖缺失或pyproject.toml格式错误如dependencies 而非dependencies []。处理不影响节点功能可忽略。如需消除安装依赖或修改nodes.py将警告级别改为logging.debug。2. xFormers 可用信息Warn!: xFormers is available (Attention) [SPARSE] Backend: spconv, Attention: xformers原因这是正常信息而非警告表示 xFormers 加速库已成功加载将用于注意力计算加速。处理无需处理这是性能优化提示。3. Torchvision 兼容性修复torchvision.transforms.functional_tensor not found, applying compatibility fix... Applied compatibility fix: created functional_tensor mock module原因ComfyUI-3D-Pack 检测到新版 Torchvision0.22已移除functional_tensor模块自动创建 mock 模块以保持向后兼容。处理这是正常的兼容性处理不会导致功能问题无需干预。4. 路径添加日志Added Hunyuan3D-2.1 path to sys.path: ...原因插件将 Hunyuan3D-2.1 子模块路径添加到 Python 搜索路径确保模块能正确导入。处理这是正常初始化流程表示子模块路径配置成功。总结问题严重程度处理方式pygit2 子模块克隆失败 高修改代码屏蔽自动克隆手动管理子模块hy3dgen 硬编码卸载警告 中改为智能版本检测逻辑pydantic-settings 解析警告 低可忽略或安装依赖/修改日志级别xFormers/兼容性修复信息 信息无需处理正常运行提示修改完成后重启 ComfyUI插件应能正常加载不再出现报错。修复前修复后