diff --git a/alpha_api.py b/alpha_api.py
new file mode 100644
index 00000000..e69de29b
diff --git a/api_models.py b/api_models.py
index dd37989f..421dda0c 100644
--- a/api_models.py
+++ b/api_models.py
@@ -1,7 +1,7 @@
"""
Pydantic models for Hunyuan3D API server.
"""
-from typing import Optional, Literal
+from typing import Optional, Literal, Dict
from pydantic import BaseModel, Field
@@ -26,6 +26,62 @@ class GenerationRequest(BaseModel):
ge=0,
le=2**32-1
)
+ octree_resolution: int = Field( # Can be changed
+ 256,
+ description="Resolution of the octree for mesh generation",
+ ge=64,
+ le=512
+ )
+ num_inference_steps: int = Field(
+ 5,
+ description="Number of inference steps for generation",
+ ge=1,
+ le=20
+ )
+ guidance_scale: float = Field( # Can be changed
+ 5.0,
+ description="Guidance scale for generation",
+ ge=0.1,
+ le=20.0
+ )
+ num_chunks: int = Field(
+ 8000,
+ description="Number of chunks for processing",
+ ge=1000,
+ le=20000
+ )
+ face_count: int = Field( # Can be changed
+ 40000,
+ description="Maximum number of faces for texture generation",
+ ge=1000,
+ le=100000
+ )
+
+
+class MultiViewGenerationRequest(BaseModel):
+ """Request model for multi-view 3D generation API"""
+ images: Dict[str, str] = Field(
+ ...,
+ description="Dictionary of view images with keys: 'front', 'back', 'left', 'right'. At least one view must be provided.",
+ example={
+ "front": "iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAIAAAAmkwkpAAAAEElEQVR4nGP8z4AATAxEcQAz0QEHOoQ+uAAAAABJRU5ErkJggg==",
+ "back": "iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAIAAAAmkwkpAAAAEElEQVR4nGP8z4AATAxEcQAz0QEHOoQ+uAAAAABJRU5ErkJggg=="
+ }
+ )
+ remove_background: bool = Field(
+ True,
+ description="Whether to automatically remove background from input images"
+ )
+ texture: bool = Field(
+ False,
+ description="Whether to generate textures for the 3D model"
+ )
+ seed: int = Field(
+ 1234,
+ description="Random seed for reproducible generation",
+ ge=0,
+ le=2**32-1
+ )
octree_resolution: int = Field(
256,
description="Resolution of the octree for mesh generation",
@@ -70,6 +126,10 @@ class StatusResponse(BaseModel):
None,
description="Base64 encoded generated model file (only when status is 'completed')"
)
+ initial_model_base64: Optional[str] = Field(
+ None,
+ description="Base64 encoded initial model file (only when status is 'completed')"
+ )
message: Optional[str] = Field(
None,
description="Error message (only when status is 'error')"
diff --git a/api_server.py b/api_server.py
index ad1f219f..cad24b85 100644
--- a/api_server.py
+++ b/api_server.py
@@ -50,7 +50,6 @@
worker = None
model_semaphore = None
-
app = FastAPI(
title=API_TITLE,
description=API_DESCRIPTION,
@@ -171,13 +170,14 @@ async def status(uid: str):
#print(f"Checking files: {textured_file_path} ({os.path.exists(textured_file_path)}), {initial_file_path} ({os.path.exists(initial_file_path)})")
# If textured file exists, generation is complete
- if os.path.exists(textured_file_path):
+ if os.path.exists(textured_file_path) and os.path.exists(initial_file_path):
try:
base64_str = base64.b64encode(open(textured_file_path, 'rb').read()).decode()
- response = {'status': 'completed', 'model_base64': base64_str}
+ base64_str_initial = base64.b64encode(open(initial_file_path, 'rb').read()).decode()
+ response = {'status': 'completed', 'model_base64': base64_str, 'initial_model_base64': base64_str_initial}
return JSONResponse(response, status_code=200)
except Exception as e:
- logger.error(f"Error reading file {textured_file_path}: {e}")
+ logger.error(f"{e}")
response = {'status': 'error', 'message': 'Failed to read generated file'}
return JSONResponse(response, status_code=500)
@@ -212,7 +212,6 @@ async def status(uid: str):
SAVE_DIR = args.cache_path
os.makedirs(SAVE_DIR, exist_ok=True)
-
model_semaphore = asyncio.Semaphore(args.limit_model_concurrency)
worker = ModelWorker(
diff --git a/docker/Dockerfile b/docker/Dockerfile
index c08e1ade..23e55c80 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -38,6 +38,7 @@ ENV PATH="/workspace/miniconda3/bin:${PATH}"
# initialize conda
RUN conda init bash
+
# Accept Anaconda TOS for required channels
RUN conda tos accept --channel https://repo.anaconda.com/pkgs/main && \
conda tos accept --channel https://repo.anaconda.com/pkgs/r
@@ -55,26 +56,40 @@ RUN conda install cuda -c nvidia/label/cuda-12.4.1 -y
# Update libstdcxx-ng to fix compatibility issues (auto-confirm)
RUN conda install -c conda-forge libstdcxx-ng -y
+RUN apt install -y python3.10-venv python3.10-distutils python3.10-dev build-essential cmake
+RUN python3.10 -m venv venv310
+RUN venv310/bin/pip install --upgrade pip setuptools wheel
+
+RUN pip install --upgrade pip setuptools wheel
+
RUN pip install torch==2.5.1 torchvision==0.20.1 torchaudio==2.5.1 --index-url https://download.pytorch.org/whl/cu124
+# RUN pip install torch==2.5.1 torchvision==0.20.1 torchaudio==2.5.1 --index-url https://download.pytorch.org/whl/cu118
+
+RUN pip install bpy==4.0 --extra-index-url https://download.blender.org/pypi/
+
# Clone Hunyuan3D-2.1 repository
-RUN git clone https://github.com/Tencent-Hunyuan/Hunyuan3D-2.1.git
+RUN git clone https://github.com/AryanGrutto/Hunyuan3D-2.1.git
# Install Python dependencies from modified requirements.txt
RUN pip install -r Hunyuan3D-2.1/requirements.txt
# Install custom_rasterizer
RUN cd /workspace/Hunyuan3D-2.1/hy3dpaint/custom_rasterizer && \
- # Set compilation environment variables
export TORCH_CUDA_ARCH_LIST="6.0;6.1;7.0;7.5;8.0;8.6;8.9;9.0" && \
export CUDA_NVCC_FLAGS="-allow-unsupported-compiler" && \
- # Install with editable mode
- pip install -e .
+ pip install --no-build-isolation .
# Install DifferentiableRenderer
-RUN cd /workspace/Hunyuan3D-2.1/hy3dpaint/DifferentiableRenderer && \
+RUN cd /workspace/Hunyuan3D-2.1/hy3dpaint && \
+ mkdir -p hy3dpaint/DifferentiableRenderer && \
+ python3 DifferentiableRenderer/setup.py build_ext --inplace && \
+ cd DifferentiableRenderer && \
bash compile_mesh_painter.sh
+RUN wget https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.0/RealESRGAN_x4plus.pth -P hy3dpaint/ckpt
+
+
# Create ckpt folder in hy3dpaint and download RealESRGAN model
RUN cd /workspace/Hunyuan3D-2.1/hy3dpaint && \
mkdir -p ckpt && \
@@ -100,14 +115,15 @@ RUN apt-get install -y libxi6 libgconf-2-4 libxkbcommon-x11-0 libsm6 libxext6 li
RUN echo "conda activate hunyuan3d21" >> ~/.bashrc
SHELL ["/bin/bash", "--login", "-c"]
-#exposing 7860 port
-EXPOSE 7860
+#exposing 8081 port for API server
+EXPOSE 8081
# Cleanup
RUN rm -f /workspace/*.zip && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
+
# Set default command
WORKDIR /workspace/Hunyuan3D-2.1
RUN mkdir gradio_cache
diff --git a/gradio_app.py b/gradio_app.py
index 46b219d4..4f809b3b 100644
--- a/gradio_app.py
+++ b/gradio_app.py
@@ -452,13 +452,13 @@ def shape_generation(
def build_app():
- title = 'Hunyuan3D-2: High Resolution Textured 3D Assets Generation'
+ title = 'Introducing Alpha 2.0 Image to 3D Generation'
if MV_MODE:
- title = 'Hunyuan3D-2mv: Image to 3D Generation with 1-4 Views'
+ title = 'Alpha3D-2-mv: Image to 3D Generation with 1-4 Views'
if 'mini' in args.subfolder:
- title = 'Hunyuan3D-2mini: Strong 0.6B Image to Shape Generator'
+ title = 'Alpha3D-2mini: Strong 0.6B Image to Shape Generator'
- title = 'Hunyuan-3D-2.1'
+ title = 'Alpha3D-2'
if TURBO_MODE:
title = title.replace(':', '-Turbo: Fast ')
@@ -469,7 +469,7 @@ def build_app():
{title}
- Tencent Hunyuan3D Team
+ Alpha Intelligence Team
"""
custom_css = """
@@ -486,7 +486,7 @@ def build_app():
"""
- with gr.Blocks(theme=gr.themes.Base(), title='Hunyuan-3D-2.1', analytics_enabled=False, css=custom_css) as demo:
+ with gr.Blocks(theme=gr.themes.Base(), title='Alpha3d 2.0', analytics_enabled=False, css=custom_css) as demo:
gr.HTML(title_html)
with gr.Row():
@@ -624,8 +624,9 @@ def build_app():
],
outputs=[file_out, html_gen_mesh, stats, seed]
).then(
- lambda: (gr.update(visible=False, value=False), gr.update(interactive=True), gr.update(interactive=True),
- gr.update(interactive=False)),
+ lambda file_path: (gr.update(visible=False, value=False), gr.update(interactive=True), gr.update(interactive=True),
+ gr.update(value=file_path, interactive=True)),
+ inputs=[file_out],
outputs=[export_texture, reduce_face, confirm_export, file_export],
).then(
lambda: gr.update(selected='gen_mesh_panel'),
@@ -651,8 +652,9 @@ def build_app():
],
outputs=[file_out, file_out2, html_gen_mesh, stats, seed]
).then(
- lambda: (gr.update(visible=True, value=True), gr.update(interactive=False), gr.update(interactive=True),
- gr.update(interactive=False)),
+ lambda file_path: (gr.update(visible=True, value=True), gr.update(interactive=False), gr.update(interactive=True),
+ gr.update(value=file_path, interactive=True)),
+ inputs=[file_out2],
outputs=[export_texture, reduce_face, confirm_export, file_export],
).then(
lambda: gr.update(selected='gen_mesh_panel'),
@@ -754,6 +756,7 @@ def on_export_click(file_out, file_out2, file_type,
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
MV_MODE = 'mv' in args.model_path
+ # MV_MODE = True
TURBO_MODE = 'turbo' in args.subfolder
HTML_HEIGHT = 690 if MV_MODE else 650
@@ -761,7 +764,7 @@ def on_export_click(file_out, file_out2, file_type,
HTML_OUTPUT_PLACEHOLDER = f"""
-
Welcome to Hunyuan3D!
+
Welcone to Alpha 2.0 powered by Alpha Intelligence
No mesh here.
@@ -862,4 +865,4 @@ def on_export_click(file_out, file_out2, file_type,
torch.cuda.empty_cache()
demo = build_app()
app = gr.mount_gradio_app(app, demo, path="/")
- uvicorn.run(app, host=args.host, port=args.port)
+ uvicorn.run(app, host=args.host, port=args.port)
\ No newline at end of file
diff --git a/hy3dpaint/DifferentiableRenderer/setup.py b/hy3dpaint/DifferentiableRenderer/setup.py
new file mode 100644
index 00000000..f27581e2
--- /dev/null
+++ b/hy3dpaint/DifferentiableRenderer/setup.py
@@ -0,0 +1,22 @@
+import os
+from setuptools import setup
+from pybind11.setup_helpers import Pybind11Extension, build_ext
+import pybind11
+import numpy
+
+this_dir = os.path.dirname(__file__)
+source_file = os.path.join(this_dir, "mesh_inpaint_processor.cpp")
+
+ext = Pybind11Extension(
+ "hy3dpaint.DifferentiableRenderer.mesh_inpaint_processor",
+ [source_file],
+ include_dirs=[pybind11.get_include(), numpy.get_include()],
+ language="c++",
+ extra_compile_args=["-O3", "-std=c++17"],
+)
+
+setup(
+ name="mesh_inpaint_processor_build",
+ ext_modules=[ext],
+ cmdclass={"build_ext": build_ext},
+)
\ No newline at end of file
diff --git a/model_worker.py b/model_worker.py
index 854f1d01..11b07758 100644
--- a/model_worker.py
+++ b/model_worker.py
@@ -174,45 +174,167 @@ def generate(self, uid, params):
logger.error(f"Shape generation failed: {e}")
raise ValueError(f"Failed to generate 3D mesh: {str(e)}")
- # Export initial mesh without texture
+ # Check if texture generation is requested
+ generate_texture = params.get('texture', False)
+ # Export initial mesh without texture
initial_save_path = os.path.join(self.save_dir, f'{str(uid)}_initial.glb')
mesh.export(initial_save_path)
+ #Shape generation is done
+
+ if generate_texture:
+ # Generate textured mesh as obj ( as in demo )
+ try:
+ output_mesh_path_obj = os.path.join(self.save_dir, f'{str(uid)}_texturing.obj')
+ textured_path_obj = self.paint_pipeline(
+ mesh_path=initial_save_path,
+ image_path=image,
+ output_mesh_path=output_mesh_path_obj,
+ save_glb=False
+ )
+ logger.info("---Texture generation takes %s seconds ---" % (time.time() - start_time))
+ logger.info(f"output_mesh_path: {output_mesh_path_obj} textured_path: {textured_path_obj}")
+
+ # Convert textured OBJ to GLB using obj2gltf with PBR support
+ print("convert textured OBJ to GLB")
+ glb_path_textured = os.path.join(self.save_dir, f'{str(uid)}_texturing.glb')
+ quick_convert_with_obj2gltf(textured_path_obj, glb_path_textured)
+ # now rename glb_path to uid_textured.glb
+ print("done.")
+ final_save_path = os.path.join(self.save_dir, f'{str(uid)}_textured.glb')
+ os.rename(glb_path_textured, final_save_path)
+ print(f"final_save_path: {final_save_path}")
+
+
+ except Exception as e:
+ logger.error(f"Texture generation failed: {e}")
+ # Fall back to untextured mesh if texture generation fails
+ final_save_path = initial_save_path
+ logger.warning(f"Using untextured mesh as fallback: {final_save_path}")
+ else:
+ # Skip texture generation, use initial mesh as final output
+ logger.info("Skipping texture generation as requested")
+ final_save_path = initial_save_path
+
+ if self.low_vram_mode:
+ torch.cuda.empty_cache()
+
+ logger.info("---Total generation takes %s seconds ---" % (time.time() - start_time))
+ # Alpha completion action calls the webhook to notify the client about the completion
+
+ return final_save_path, uid
+
+ @torch.inference_mode()
+ def generate_multiview(self, uid, params):
+ """
+ Generate a 3D model from multiple view images.
+
+ Args:
+ uid: Unique identifier for this generation task
+ params (dict): Generation parameters including images dict and options
+
+ Returns:
+ tuple: (file_path, uid) - Path to generated file and task ID
+ """
+ start_time = time.time()
+ logger.info(f"Generating 3D model from multiple views for uid: {uid}")
- # Generate textured mesh as obj ( as in demo )
+ # Handle input images
+ if 'images' in params:
+ images_dict = params["images"]
+ if not images_dict or len(images_dict) == 0:
+ raise ValueError("No input images provided")
+
+ # Convert base64 images to PIL Images
+ images = {}
+ for view_name, image_b64 in images_dict.items():
+ if view_name in ['front', 'back', 'left', 'right']:
+ images[view_name] = load_image_from_base64(image_b64)
+ else:
+ logger.warning(f"Unknown view name: {view_name}, skipping")
+ else:
+ raise ValueError("No input images provided")
+
+ # Convert to RGBA and remove background if needed
+ for view_name, image in images.items():
+ image = image.convert("RGBA")
+ if image.mode == "RGB":
+ images[view_name] = self.rembg(image)
+
+ # Generate mesh from multiple views using the same approach as gradio app
try:
- output_mesh_path_obj = os.path.join(self.save_dir, f'{str(uid)}_texturing.obj')
- textured_path_obj = self.paint_pipeline(
- mesh_path=initial_save_path,
- image_path=image,
- output_mesh_path=output_mesh_path_obj,
- save_glb=False
+ # Set up generator with seed
+ generator = torch.Generator()
+ generator = generator.manual_seed(int(params.get('seed', 1234)))
+
+ # Call pipeline with multiple views (same as gradio app)
+ outputs = self.pipeline(
+ image=images,
+ num_inference_steps=params.get('num_inference_steps', 5),
+ guidance_scale=params.get('guidance_scale', 5.0),
+ generator=generator,
+ octree_resolution=params.get('octree_resolution', 256),
+ num_chunks=params.get('num_chunks', 8000),
+ output_type='mesh'
)
- logger.info("---Texture generation takes %s seconds ---" % (time.time() - start_time))
- logger.info(f"output_mesh_path: {output_mesh_path_obj} textured_path: {textured_path_obj}")
- # Use the textured GLB as the final output
- #final_save_path = os.path.join(self.save_dir, f'{str(uid)}_textured.{file_type}')
- #os.rename(output_mesh_path, final_save_path)
-
- # Convert textured OBJ to GLB using obj2gltf with PBR support
- print("convert textured OBJ to GLB")
- glb_path_textured = os.path.join(self.save_dir, f'{str(uid)}_texturing.glb')
- quick_convert_with_obj2gltf(textured_path_obj, glb_path_textured)
- # now rename glb_path to uid_textured.glb
- print("done.")
- final_save_path = os.path.join(self.save_dir, f'{str(uid)}_textured.glb')
- os.rename(glb_path_textured, final_save_path)
- print(f"final_save_path: {final_save_path}")
-
+ # Extract mesh from outputs (same as gradio app)
+ from hy3dshape.pipelines import export_to_trimesh
+ mesh = export_to_trimesh(outputs)[0]
+
+ logger.info("---Multiview shape generation takes %s seconds ---" % (time.time() - start_time))
except Exception as e:
- logger.error(f"Texture generation failed: {e}")
- # Fall back to untextured mesh if texture generation fails
+ logger.error(f"Multiview shape generation failed: {e}")
+ raise ValueError(f"Failed to generate 3D mesh from multiple views: {str(e)}")
+
+ # Check if texture generation is requested
+ generate_texture = params.get('texture', False)
+
+ # Export initial mesh without texture
+ initial_save_path = os.path.join(self.save_dir, f'{str(uid)}_initial.glb')
+ mesh.export(initial_save_path)
+
+ if generate_texture:
+ # Generate textured mesh as obj ( as in demo )
+ try:
+ output_mesh_path_obj = os.path.join(self.save_dir, f'{str(uid)}_texturing.obj')
+ # Pass all available views to texture pipeline for better quality
+ # The texture pipeline can handle multiple images and will use them for multiview texture generation
+ textured_path_obj = self.paint_pipeline(
+ mesh_path=initial_save_path,
+ image_path=list(images.values()), # Pass all views as a list
+ output_mesh_path=output_mesh_path_obj,
+ save_glb=False
+ )
+ logger.info("---Multiview texture generation takes %s seconds ---" % (time.time() - start_time))
+ logger.info(f"output_mesh_path: {output_mesh_path_obj} textured_path: {textured_path_obj}")
+
+ # Convert textured OBJ to GLB using obj2gltf with PBR support
+ print("convert textured OBJ to GLB")
+ glb_path_textured = os.path.join(self.save_dir, f'{str(uid)}_texturing.glb')
+ quick_convert_with_obj2gltf(textured_path_obj, glb_path_textured)
+ # now rename glb_path to uid_textured.glb
+ print("done.")
+ final_save_path = os.path.join(self.save_dir, f'{str(uid)}_textured.glb')
+ os.rename(glb_path_textured, final_save_path)
+ print(f"final_save_path: {final_save_path}")
+
+
+ except Exception as e:
+ logger.error(f"Multiview texture generation failed: {e}")
+ # Fall back to untextured mesh if texture generation fails
+ final_save_path = initial_save_path
+ logger.warning(f"Using untextured mesh as fallback: {final_save_path}")
+ else:
+ # Skip texture generation, use initial mesh as final output
+ logger.info("Skipping texture generation as requested")
final_save_path = initial_save_path
- logger.warning(f"Using untextured mesh as fallback: {final_save_path}")
if self.low_vram_mode:
torch.cuda.empty_cache()
- logger.info("---Total generation takes %s seconds ---" % (time.time() - start_time))
+ logger.info("---Total multiview generation takes %s seconds ---" % (time.time() - start_time))
+
+ # Alpha completion action calls the webhook to notify the client about the completion
+
return final_save_path, uid
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index d9f1e4dd..787ab093 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -53,7 +53,7 @@ psutil==6.0.0
cupy-cuda12x==13.4.1
# Blender
-bpy==4.0
+bpy
# ONNX Runtime
onnxruntime==1.16.3