BladeSzaSza commited on
Commit
55083e9
Β·
1 Parent(s): 05ce3e0

fix(3d): Implement correct Hunyuan3D installation and usage

Browse files

- Replaces the previous flawed dynamic loading with a robust setup process that follows the official documentation.
- After downloading the model, the code now programmatically runs pip install for the custom rasterizer and compiles the mesh painter.
- Updates the pipeline to use the correct `Hunyuan3DDiTFlowMatchingPipeline` and `Hunyuan3DPaintPipeline` classes.
- This should resolve the `FileNotFoundError` and enable the direct 3D generation model.

Files changed (1) hide show
  1. models/model_3d_generator.py +85 -79
models/model_3d_generator.py CHANGED
@@ -71,69 +71,75 @@ class Hunyuan3DGenerator:
71
  return False
72
 
73
  def load_model(self):
74
- """Load Hunyuan3D model directly"""
75
  if self.model is None:
76
- logger.info("πŸš€ Starting Hunyuan3D model loading...")
77
 
78
  try:
79
- # Check if we can use the model directly
80
- try:
81
- # Try to import the Hunyuan3D modules
82
- logger.info("πŸ“¦ Attempting to import Hunyuan3D modules...")
83
-
84
- # Download model weights if not already present
85
- logger.info("πŸ“₯ Downloading Hunyuan3D model weights...")
86
- self.model_path = snapshot_download(
87
- repo_id=self.model_id,
88
- repo_type="space",
89
- cache_dir="./models/hunyuan3d_cache"
90
- )
91
- logger.info(f"βœ… Model downloaded to: {self.model_path}")
92
-
93
- # Try to set up the model pipeline
94
- logger.info("πŸ”§ Setting up Hunyuan3D pipeline...")
95
-
96
- # Import necessary modules using importlib for robustness
97
- import sys
98
- import os
99
- import importlib.util
100
-
101
- try:
102
- # Add the model path to sys.path to allow internal imports within Hunyuan3D code
103
- if self.model_path not in sys.path:
104
- sys.path.insert(0, self.model_path)
 
 
 
 
 
 
 
 
 
105
 
106
- # Dynamically load the modules from the downloaded paths
107
- shape_infer_path = os.path.join(self.model_path, 'hy3dshape', 'infer.py')
108
- paint_infer_path = os.path.join(self.model_path, 'hy3dpaint', 'infer.py')
 
 
 
 
 
 
 
109
 
110
- spec_shape = importlib.util.spec_from_file_location("hy3dshape.infer", shape_infer_path)
111
- shape_module = importlib.util.module_from_spec(spec_shape)
112
- spec_shape.loader.exec_module(shape_module)
113
- self.predict_shape = shape_module.predict_shape
114
-
115
- spec_paint = importlib.util.spec_from_file_location("hy3dpaint.infer", paint_infer_path)
116
- paint_module = importlib.util.module_from_spec(spec_paint)
117
- spec_paint.loader.exec_module(paint_module)
118
- self.predict_texture = paint_module.predict_texture
119
-
120
- self.model = "direct_model"
121
- logger.info("βœ… Hunyuan3D modules loaded successfully via importlib")
122
 
123
- except (ImportError, FileNotFoundError) as e:
124
- logger.warning(f"⚠️ Could not import Hunyuan3D modules dynamically: {e}")
125
- logger.info("πŸ”„ Using simplified implementation...")
126
- self.model = "simplified"
127
-
128
- except Exception as e:
129
- logger.error(f"❌ Failed to set up Hunyuan3D: {e}")
130
- logger.info("πŸ”„ Using fallback mode...")
131
- self.model = "fallback_mode"
132
-
133
  except Exception as e:
134
- logger.error(f"❌ Failed to initialize Hunyuan3D: {e}")
135
- logger.info("πŸ”„ Falling back to simple 3D generation...")
136
- self.model = "fallback_mode"
137
 
138
  def image_to_3d(self,
139
  image: Union[str, Image.Image, np.ndarray],
@@ -192,7 +198,7 @@ class Hunyuan3DGenerator:
192
  return self._generate_fallback_3d(image)
193
 
194
  def _generate_with_direct_model(self, image: Image.Image, remove_background: bool, texture_resolution: int) -> str:
195
- """Generate 3D model using direct Hunyuan3D model"""
196
 
197
  try:
198
  # Remove background if requested
@@ -200,38 +206,38 @@ class Hunyuan3DGenerator:
200
  logger.info("🎭 Removing background...")
201
  image = self._remove_background(image)
202
 
203
- # Save image temporarily
204
  temp_image_path = self._save_temp_image(image)
205
 
206
- # Generate shape
207
- logger.info("πŸ”² Generating 3D shape...")
208
- shape_output = self.predict_shape(
209
- image_path=temp_image_path,
 
 
210
  guidance_scale=self.guidance_scale,
211
- steps=self.num_inference_steps,
212
- seed=random.randint(1, 10000),
213
- octree_resolution=self.resolution
214
- )
215
-
216
- # Generate texture
217
- logger.info("🎨 Generating texture...")
218
- textured_output = self.predict_texture(
219
- shape_path=shape_output,
220
  image_path=temp_image_path,
221
  guidance_scale=self.guidance_scale,
222
- steps=self.num_inference_steps,
223
- seed=random.randint(1, 10000),
224
- texture_resolution=texture_resolution
225
  )
226
-
227
- # Save final output
228
- output_path = self._save_output_mesh(textured_output)
229
- logger.info(f"βœ… 3D model generated successfully: {output_path}")
 
230
 
231
  return output_path
232
 
233
  except Exception as e:
234
- logger.error(f"❌ Direct model generation failed: {e}")
235
  raise
236
 
237
  def _generate_simplified_3d(self, image: Image.Image) -> str:
 
71
  return False
72
 
73
  def load_model(self):
74
+ """Load Hunyuan3D model and run necessary setup"""
75
  if self.model is None:
76
+ logger.info("πŸš€ Starting Hunyuan3D model loading and setup...")
77
 
78
  try:
79
+ # Download model repository if not already present
80
+ logger.info(f"πŸ“₯ Downloading Hunyuan3D repository from {self.model_id}...")
81
+ self.model_path = snapshot_download(
82
+ repo_id=self.model_id,
83
+ repo_type="space",
84
+ cache_dir="./models/hunyuan3d_cache"
85
+ )
86
+ logger.info(f"βœ… Model repository downloaded to: {self.model_path}")
87
+
88
+ # --- Installation and Compilation ---
89
+ logger.info("πŸ”§ Running Hunyuan3D setup scripts...")
90
+ import subprocess
91
+ import sys
92
+ import os
93
+
94
+ # 1. Install requirements from the model's specific requirements file
95
+ requirements_path = os.path.join(self.model_path, 'requirements.txt')
96
+ if os.path.exists(requirements_path):
97
+ logger.info(f"πŸ“¦ Installing requirements from {requirements_path}...")
98
+ subprocess.run([sys.executable, '-m', 'pip', 'install', '-r', requirements_path], check=True, capture_output=True, text=True)
99
+
100
+ # 2. Install custom rasterizer
101
+ rasterizer_path = os.path.join(self.model_path, 'hy3dpaint', 'packages', 'custom_rasterizer')
102
+ if os.path.exists(rasterizer_path):
103
+ logger.info(f"πŸ“¦ Installing custom_rasterizer from {rasterizer_path}...")
104
+ subprocess.run([sys.executable, '-m', 'pip', 'install', '-e', '.'], cwd=rasterizer_path, check=True, capture_output=True, text=True)
105
+
106
+ # 3. Compile mesh painter
107
+ renderer_path = os.path.join(self.model_path, 'hy3dpaint', 'DifferentiableRenderer')
108
+ compile_script_path = os.path.join(renderer_path, 'compile_mesh_painter.sh')
109
+ if os.path.exists(compile_script_path):
110
+ logger.info(f"πŸ–ŒοΈ Compiling mesh painter in {renderer_path}...")
111
+ subprocess.run(['bash', 'compile_mesh_painter.sh'], cwd=renderer_path, check=True, capture_output=True, text=True)
112
+
113
+ logger.info("βœ… Hunyuan3D setup completed successfully.")
114
 
115
+ # --- Pipeline Initialization ---
116
+ logger.info("✈️ Initializing Hunyuan3D pipelines...")
117
+
118
+ # Add subdirectories to Python path
119
+ sys.path.insert(0, os.path.join(self.model_path, 'hy3dshape'))
120
+ sys.path.insert(0, os.path.join(self.model_path, 'hy3dpaint'))
121
+
122
+ # Import the correct pipelines
123
+ from hy3dshape.pipelines import Hunyuan3DDiTFlowMatchingPipeline
124
+ from textureGenPipeline import Hunyuan3DPaintPipeline, Hunyuan3DPaintConfig
125
 
126
+ # Instantiate pipelines
127
+ logger.info("Instantiating shape pipeline...")
128
+ self.shape_pipeline = Hunyuan3DDiTFlowMatchingPipeline.from_pretrained(
129
+ self.model_path, torch_dtype=torch.bfloat16
130
+ ).to(self.device)
131
+
132
+ logger.info("Instantiating paint pipeline...")
133
+ paint_config = Hunyuan3DPaintConfig(max_num_view=8, resolution=1024, pbr_optimization=True)
134
+ self.paint_pipeline = Hunyuan3DPaintPipeline(paint_config)
135
+
136
+ self.model = "direct_model"
137
+ logger.info("βœ… Hunyuan3D pipelines loaded successfully.")
138
 
 
 
 
 
 
 
 
 
 
 
139
  except Exception as e:
140
+ logger.error(f"❌ Failed to set up Hunyuan3D pipeline: {e}", exc_info=True)
141
+ logger.warning("πŸ”„ Falling back to simplified 3D generation...")
142
+ self.model = "simplified"
143
 
144
  def image_to_3d(self,
145
  image: Union[str, Image.Image, np.ndarray],
 
198
  return self._generate_fallback_3d(image)
199
 
200
  def _generate_with_direct_model(self, image: Image.Image, remove_background: bool, texture_resolution: int) -> str:
201
+ """Generate 3D model using the official Hunyuan3D pipelines"""
202
 
203
  try:
204
  # Remove background if requested
 
206
  logger.info("🎭 Removing background...")
207
  image = self._remove_background(image)
208
 
209
+ # Save image to a temporary file, as pipelines expect a path
210
  temp_image_path = self._save_temp_image(image)
211
 
212
+ # 1. Generate the untextured mesh
213
+ logger.info("πŸ”² Generating 3D shape with Hunyuan3DDiTFlowMatchingPipeline...")
214
+ # The pipeline returns a list of meshes, we take the first one
215
+ mesh_untextured_path = self.shape_pipeline(
216
+ image=temp_image_path,
217
+ num_inference_steps=self.num_inference_steps,
218
  guidance_scale=self.guidance_scale,
219
+ seed=random.randint(1, 10000)
220
+ )[0]
221
+ logger.info(f"βœ… Untextured mesh saved to: {mesh_untextured_path}")
222
+
223
+ # 2. Generate the texture for the mesh
224
+ logger.info("🎨 Generating texture with Hunyuan3DPaintPipeline...")
225
+ mesh_textured_path = self.paint_pipeline(
226
+ mesh_path=mesh_untextured_path,
 
227
  image_path=temp_image_path,
228
  guidance_scale=self.guidance_scale,
229
+ seed=random.randint(1, 10000)
 
 
230
  )
231
+ logger.info(f"βœ… Textured mesh saved to: {mesh_textured_path}")
232
+
233
+ # 3. Save the final output to a consistent location
234
+ output_path = self._save_output_mesh(mesh_textured_path)
235
+ logger.info(f"βœ… 3D model generation successful. Final model at: {output_path}")
236
 
237
  return output_path
238
 
239
  except Exception as e:
240
+ logger.error(f"❌ Direct model generation failed: {e}", exc_info=True)
241
  raise
242
 
243
  def _generate_simplified_3d(self, image: Image.Image) -> str: