Merge pull request #491 from DenisKochetov/mv-adapter-fix

fix scheduler for mv-adapter
This commit is contained in:
Mr. For Example
2025-06-07 12:59:39 +01:00
committed by GitHub
12 changed files with 819 additions and 734 deletions
@@ -30,7 +30,7 @@ class RenderOutput:
class NVDiffRastContextWrapper:
def __init__(self, device: str, context_type: str = "gl"):
if context_type == "gl":
self.ctx = dr.RasterizeGLContext(device=device)
self.ctx = dr.RasterizeCudaContext(device=device)
elif context_type == "cuda":
self.ctx = dr.RasterizeCudaContext(device=device)
else:
+198 -209
View File
@@ -10,7 +10,7 @@ from typing import Optional, Union, List
from torchvision import transforms
from transformers import AutoModelForImageSegmentation
from diffusers import AutoencoderKL, DDPMScheduler, LCMScheduler, UNet2DConditionModel
from diffusers import AutoencoderKL, DDPMScheduler, UNet2DConditionModel
from huggingface_hub import snapshot_download
from .mvadapter.pipelines.pipeline_mvadapter_i2mv_sdxl import MVAdapterI2MVSDXLPipeline
@@ -37,10 +37,9 @@ except ImportError:
def prepare_pipeline(
base_model: str,
vae_model: Optional[str] = None,
unet_model: Optional[str] = None,
lora_model: Optional[str] = None,
adapter_path: str = "huanngzh/mv-adapter",
scheduler: str = "default",
scheduler: str = "ddpm",
num_views: int = 6,
device: str = "cuda",
dtype: torch.dtype = torch.float16,
@@ -53,40 +52,30 @@ def prepare_pipeline(
Args:
base_model: Base Stable Diffusion XL model
vae_model: Custom VAE model (optional)
unet_model: Custom UNet model (optional)
lora_model: LoRA model (optional)
adapter_path: Path to adapter weights
scheduler: Scheduler type ("default", "ddpm", "lcm")
scheduler: Scheduler type ("ddpm")
num_views: Number of views to generate
device: Device for computation
dtype: Data type
use_mmgp: Use mmgp for memory optimization
adapter_local_path: Local path for adapter download
"""
# Load vae and unet if provided
# Load vae if provided
pipe_kwargs = {}
if vae_model is not None:
pipe_kwargs["vae"] = AutoencoderKL.from_pretrained(vae_model)
if unet_model is not None:
pipe_kwargs["unet"] = UNet2DConditionModel.from_pretrained(unet_model)
# Prepare pipeline
pipe = MVAdapterI2MVSDXLPipeline.from_pretrained(base_model, **pipe_kwargs)
# Load scheduler if provided
if scheduler != "default":
scheduler_class = None
if scheduler == "ddpm":
scheduler_class = DDPMScheduler
elif scheduler == "lcm":
scheduler_class = LCMScheduler
pipe.scheduler = ShiftSNRScheduler.from_scheduler(
pipe.scheduler,
shift_mode="interpolated",
shift_scale=8.0,
scheduler_class=scheduler_class,
)
# Load DDPM scheduler
pipe.scheduler = ShiftSNRScheduler.from_scheduler(
pipe.scheduler,
shift_mode="interpolated",
shift_scale=8.0,
scheduler_class=DDPMScheduler,
)
pipe.init_custom_adapter(
num_views=num_views, self_attn_processor=DecoupledMVRowColSelfAttnProcessor2_0
@@ -130,32 +119,6 @@ def prepare_pipeline(
return pipe
# def create_bg_remover(device: str = "cuda"):
# """Create background removal function"""
# birefnet = AutoModelForImageSegmentation.from_pretrained(
# "ZhengPeng7/BiRefNet", trust_remote_code=True
# )
# birefnet.to(device)
# transform_image = transforms.Compose([
# transforms.Resize((1024, 1024)),
# transforms.ToTensor(),
# transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
# ])
# def remove_bg(image):
# image_size = image.size
# input_images = transform_image(image).unsqueeze(0).to(device)
# with torch.no_grad():
# preds = birefnet(input_images)[-1].sigmoid().cpu()
# pred = preds[0].squeeze()
# pred_pil = transforms.ToPILImage()(pred)
# mask = pred_pil.resize(image_size)
# image.putalpha(mask)
# return image
# return remove_bg
def preprocess_image(image: Image.Image, height: int, width: int) -> Image.Image:
"""Preprocess image for MV-Adapter"""
image = np.array(image)
@@ -251,9 +214,6 @@ def run_pipeline(
# Prepare image
reference_image = Image.open(image) if isinstance(image, str) else image
# if remove_bg_fn is not None:
# reference_image = remove_bg_fn(reference_image)
# reference_image = preprocess_image(reference_image, height, width)
if reference_image.mode == "RGBA":
reference_image = preprocess_image(reference_image, height, width)
@@ -301,158 +261,6 @@ def pils_to_torch_tensor(images: List[Image.Image]) -> torch.Tensor:
return torch.stack(tensors, dim=0)
def prepare_t2mv_pipeline(
base_model: str,
vae_model: Optional[str] = None,
unet_model: Optional[str] = None,
lora_model: Optional[str] = None,
adapter_path: str = "huanngzh/mv-adapter",
scheduler: str = "default",
num_views: int = 6,
device: str = "cuda",
dtype: torch.dtype = torch.float16,
use_mmgp: bool = False,
adapter_local_path: Optional[str] = None,
):
"""
Prepare MV-Adapter T2MV pipeline
Args:
base_model: Base Stable Diffusion XL model
vae_model: Custom VAE model (optional)
unet_model: Custom UNet model (optional)
lora_model: LoRA model (optional)
adapter_path: Path to adapter weights
scheduler: Scheduler type ("default", "ddpm", "lcm")
num_views: Number of views to generate
device: Device for computation
dtype: Data type
use_mmgp: Use mmgp for memory optimization
adapter_local_path: Local path for adapter download
"""
# Load vae and unet if provided
pipe_kwargs = {}
if vae_model is not None:
pipe_kwargs["vae"] = AutoencoderKL.from_pretrained(vae_model)
if unet_model is not None:
pipe_kwargs["unet"] = UNet2DConditionModel.from_pretrained(unet_model)
# Prepare pipeline
pipe = MVAdapterT2MVSDXLPipeline.from_pretrained(base_model, **pipe_kwargs)
# Load scheduler if provided
if scheduler != "default":
scheduler_class = None
if scheduler == "ddpm":
scheduler_class = DDPMScheduler
elif scheduler == "lcm":
scheduler_class = LCMScheduler
pipe.scheduler = ShiftSNRScheduler.from_scheduler(
pipe.scheduler,
shift_mode="interpolated",
shift_scale=8.0,
scheduler_class=scheduler_class,
)
pipe.init_custom_adapter(num_views=num_views)
# Load adapter weights
weight_name = "mvadapter_t2mv_sdxl.safetensors"
pipe.load_custom_adapter(adapter_path, weight_name=weight_name, local_cache_dir=adapter_local_path)
pipe.to(device=device, dtype=dtype)
pipe.cond_encoder.to(device=device, dtype=dtype)
# load lora if provided
if lora_model is not None:
model_, name_ = lora_model.rsplit("/", 1)
pipe.load_lora_weights(model_, weight_name=name_)
# Apply mmgp optimization if available and requested
if use_mmgp and MMGP_AVAILABLE:
import gc
torch.cuda.empty_cache()
gc.collect()
all_models = {
"vae": pipe.vae,
"text_encoder": pipe.text_encoder,
"text_encoder_2": pipe.text_encoder_2,
"unet": pipe.unet,
"cond_encoder": pipe.cond_encoder,
}
if hasattr(pipe, 'image_encoder') and pipe.image_encoder is not None:
all_models["image_encoder"] = pipe.image_encoder
offload.profile(all_models, profile_type.HighRAM_LowVRAM)
print("mmgp profiling applied successfully.")
# Enable VAE slicing for memory efficiency
pipe.enable_vae_slicing()
return pipe
def run_t2mv_pipeline(
pipe,
num_views: int,
text: str,
height: int,
width: int,
num_inference_steps: int,
guidance_scale: float,
seed: int,
negative_prompt: str = "watermark, ugly, deformed, noisy, blurry, low contrast",
lora_scale: float = 1.0,
device: str = "cuda",
azimuth_deg: Optional[List[int]] = None,
):
"""
Execute text-to-multiview generation
"""
# Prepare cameras
if azimuth_deg is None:
azimuth_deg = [0, 45, 90, 180, 270, 315][:num_views]
cameras = get_orthogonal_camera(
elevation_deg=[0] * num_views,
distance=[1.8] * num_views,
left=-0.55,
right=0.55,
bottom=-0.55,
top=0.55,
azimuth_deg=[x - 90 for x in azimuth_deg],
device=device,
)
plucker_embeds = get_plucker_embeds_from_cameras_ortho(
cameras.c2w, [1.1] * num_views, width
)
control_images = ((plucker_embeds + 1.0) / 2.0).clamp(0, 1)
pipe_kwargs = {}
if seed != -1 and isinstance(seed, int):
pipe_kwargs["generator"] = torch.Generator(device=device).manual_seed(seed)
images = pipe(
text,
height=height,
width=width,
num_inference_steps=num_inference_steps,
guidance_scale=guidance_scale,
num_images_per_prompt=num_views,
control_image=control_images,
control_conditioning_scale=1.0,
negative_prompt=negative_prompt,
cross_attention_kwargs={"scale": lora_scale},
**pipe_kwargs,
).images
return images
def prepare_texture_pipeline(
upscaler_ckpt_path: Optional[str] = None,
inpaint_ckpt_path: Optional[str] = None,
@@ -563,7 +371,6 @@ def run_texture_pipeline(
if camera_elevation_deg is None:
camera_elevation_deg = [0, 0, 0, 0, 89.99, -89.99]
# Adjust azimuth angles (subtract 90 as in original code)
azimuth_deg_corrected = [x - 90 for x in camera_azimuth_deg]
grid_image = make_image_grid(multiview_images, rows=1)
@@ -641,7 +448,6 @@ def download_texture_checkpoints(texture_ckpt_dir, upscaler_ckpt_path, inpaint_c
except (subprocess.CalledProcessError, FileNotFoundError):
pass
# Try curl if wget failed
if not success:
try:
subprocess.run([
@@ -652,7 +458,6 @@ def download_texture_checkpoints(texture_ckpt_dir, upscaler_ckpt_path, inpaint_c
except (subprocess.CalledProcessError, FileNotFoundError):
pass
# Try Python urllib as fallback
if not success:
try:
import urllib.request
@@ -662,11 +467,195 @@ def download_texture_checkpoints(texture_ckpt_dir, upscaler_ckpt_path, inpaint_c
except Exception as e:
print(f"Failed to download {ckpt['name']} using urllib: {e}")
# If all methods failed
if not success:
print(f"Failed to download {ckpt['name']}. Please download manually:")
print(f"wget {ckpt['url']} -O {ckpt['path']}")
print(f"or")
print(f"curl -L {ckpt['url']} -o {ckpt['path']}")
else:
print(f"{ckpt['name']} already exists at {ckpt['path']}")
print(f"{ckpt['name']} already exists at {ckpt['path']}")
def prepare_tg2mv_pipeline(
base_model: str,
vae_model: Optional[str] = None,
lora_model: Optional[str] = None,
adapter_path: str = "huanngzh/mv-adapter",
scheduler: str = "ddpm",
num_views: int = 6,
device: str = "cuda",
dtype: torch.dtype = torch.float16,
use_mmgp: bool = False,
adapter_local_path: Optional[str] = None,
):
"""
Prepare MV-Adapter TG2MV (Text-Guided to Multi-View) pipeline
Args:
base_model: Base Stable Diffusion XL model
vae_model: Custom VAE model (optional)
lora_model: LoRA model (optional)
adapter_path: Path to adapter weights
scheduler: Scheduler type ("ddpm")
num_views: Number of views to generate
device: Device for computation
dtype: Data type
use_mmgp: Use mmgp for memory optimization
adapter_local_path: Local path for adapter download
"""
import gc
torch.cuda.empty_cache()
gc.collect()
pipe_kwargs = {}
if vae_model is not None:
pipe_kwargs["vae"] = AutoencoderKL.from_pretrained(vae_model, torch_dtype=dtype)
pipe = MVAdapterT2MVSDXLPipeline.from_pretrained(base_model, torch_dtype=dtype, **pipe_kwargs)
pipe.scheduler = ShiftSNRScheduler.from_scheduler(
pipe.scheduler,
shift_mode="interpolated",
shift_scale=8.0,
scheduler_class=DDPMScheduler,
)
pipe.init_custom_adapter(
num_views=num_views, self_attn_processor=DecoupledMVRowColSelfAttnProcessor2_0
)
weight_name = "mvadapter_tg2mv_sdxl.safetensors"
pipe.load_custom_adapter(adapter_path, weight_name=weight_name, local_cache_dir=adapter_local_path)
pipe.to(device=device, dtype=dtype)
pipe.cond_encoder.to(device=device, dtype=dtype)
if lora_model is not None:
model_, name_ = lora_model.rsplit("/", 1)
pipe.load_lora_weights(model_, weight_name=name_)
pipe.enable_vae_slicing()
pipe.enable_vae_tiling()
if hasattr(pipe, 'enable_model_cpu_offload'):
if not use_mmgp:
pipe.enable_model_cpu_offload()
print("[INFO] Включен CPU offload для экономии VRAM")
if use_mmgp and MMGP_AVAILABLE:
torch.cuda.empty_cache()
gc.collect()
all_models = {
"vae": pipe.vae,
"text_encoder": pipe.text_encoder,
"text_encoder_2": pipe.text_encoder_2,
"unet": pipe.unet,
"cond_encoder": pipe.cond_encoder,
}
if hasattr(pipe, 'image_encoder') and pipe.image_encoder is not None:
all_models["image_encoder"] = pipe.image_encoder
try:
offload.profile(all_models, profile_type.HighRAM_LowVRAM)
print("mmgp profiling applied successfully.")
except Exception as e:
print(f"[WARNING] mmgp failed, continuing without it: {e}")
torch.cuda.empty_cache()
gc.collect()
return pipe
def run_tg2mv_pipeline(
pipe,
mesh_path: str,
num_views: int,
text: str,
height: int,
width: int,
num_inference_steps: int,
guidance_scale: float,
seed: int,
negative_prompt: str = "watermark, ugly, deformed, noisy, blurry, low contrast",
lora_scale: float = 1.0,
device: str = "cuda",
):
"""
Execute text-guided multiview generation (TG2MV)
Args:
pipe: TG2MV pipeline
mesh_path: Path to 3D mesh file for guidance
num_views: Number of views to generate
text: Text prompt
height: Image height
width: Image width
num_inference_steps: Number of denoising steps
guidance_scale: Guidance scale
seed: Random seed
negative_prompt: Negative prompt
lora_scale: LoRA scale
device: Device for computation
"""
cameras = get_orthogonal_camera(
elevation_deg=[0, 0, 0, 0, 89.99, -89.99],
distance=[1.8] * num_views,
left=-0.55,
right=0.55,
bottom=-0.55,
top=0.55,
azimuth_deg=[x - 90 for x in [0, 90, 180, 270, 180, 180]],
device=device,
)
ctx = NVDiffRastContextWrapper(device=device)
mesh = load_mesh(mesh_path, rescale=True, device=device)
render_out = render(
ctx,
mesh,
cameras,
height=height,
width=width,
render_attr=False,
normal_background=0.0,
)
pos_images = tensor_to_image((render_out.pos + 0.5).clamp(0, 1), batched=True)
normal_images = tensor_to_image(
(render_out.normal / 2 + 0.5).clamp(0, 1), batched=True
)
control_images = (
torch.cat(
[
(render_out.pos + 0.5).clamp(0, 1),
(render_out.normal / 2 + 0.5).clamp(0, 1),
],
dim=-1,
)
.permute(0, 3, 1, 2)
.to(device)
)
pipe_kwargs = {}
if seed != -1 and isinstance(seed, int):
pipe_kwargs["generator"] = torch.Generator(device=device).manual_seed(seed)
images = pipe(
text,
height=height,
width=width,
num_inference_steps=num_inference_steps,
guidance_scale=guidance_scale,
num_images_per_prompt=num_views,
control_image=control_images,
control_conditioning_scale=1.0,
negative_prompt=negative_prompt,
cross_attention_kwargs={"scale": lora_scale},
**pipe_kwargs,
).images
return images, pos_images, normal_images
+4 -4
View File
@@ -24,18 +24,18 @@ if parent_dir not in sys.path:
trellis_path = os.path.join(current_dir, "trellis")
if os.path.exists(trellis_path) and trellis_path not in sys.path:
sys.path.insert(0, trellis_path)
logger.info(f"Added trellis path to sys.path: {trellis_path}")
# logger.info(f"Added trellis path to sys.path: {trellis_path}")
# Add stablx package path
stablex_path = os.path.join(current_dir, "stablex")
if os.path.exists(trellis_path) and trellis_path not in sys.path:
sys.path.insert(0, trellis_path)
logger.info(f"Added stablex path to sys.path: {trellis_path}")
# logger.info(f"Added stablex path to sys.path: {trellis_path}")
# Verify trellis package is importable
try:
import trellis
logger.info("Trellis package imported successfully")
# logger.info("Trellis package imported successfully")
except ImportError as e:
logger.error(f"Failed to import trellis package: {e}")
logger.error(f"Current sys.path: {sys.path}")
@@ -44,7 +44,7 @@ except ImportError as e:
# Verify stablex package is importable
try:
import stablex
logger.info("stablex package imported successfully")
# logger.info("stablex package imported successfully")
except ImportError as e:
logger.error(f"Failed to import stablex package: {e}")
logger.error(f"Current sys.path: {sys.path}")
@@ -112,12 +112,13 @@ class FlowEulerSampler(Sampler):
t_pairs = list((t_seq[i], t_seq[i + 1]) for i in range(steps))
ret = edict({"samples": None, "pred_x_t": [], "pred_x_0": []})
comfy_pbar = comfy.utils.ProgressBar(steps)
for t, t_prev in tqdm(t_pairs, desc="Sampling", disable=not verbose):
for i, (t, t_prev) in enumerate(tqdm(t_pairs, desc="Sampling", disable=not verbose)):
out = self.sample_once(model, sample, t, t_prev, cond, **kwargs)
sample = out.pred_x_prev
ret.pred_x_t.append(out.pred_x_prev)
ret.pred_x_0.append(out.pred_x_0)
comfy_pbar.update_absolute(i + 1)
comfy_pbar.update_absolute(i + 1)
ret.samples = sample
return ret
+6 -6
View File
@@ -49,15 +49,15 @@ Your ComfyUI Root Directory\python_embeded\python.exe install.py
- **MV-Adapter**: [huanngzh/MV-Adapter](https://github.com/huanngzh/MV-Adapter)
- Two generation methods:
- IG2MV: Single image + 3D mesh to multi-view images
- T2MV: Text prompt to multi-view images
- TG2MV: Text prompt + 3D mesh to multi-view images
- Texturing: Grid image + 3D mesh to textured mesh
- Model weights: https://huggingface.co/huanngzh/mv-adapter
- Workflows: [IG2MV](./_Example_Workflows/MV-Adapter/MV-Adapter-ig2mv.json), [T2MV](./_Example_Workflows/MV-Adapter/MV-Adapter-t2mv.json), [Texturing](./_Example_Workflows/MV-Adapter/MV-Adapter-Texturing.json)
- Workflows: [IG2MV](./_Example_Workflows/MV-Adapter/MV-Adapter-ig2mv.json), [T2MV](./_Example_Workflows/MV-Adapter/MV-Adapter-tg2mv.json), [Texturing](./_Example_Workflows/MV-Adapter/MV-Adapter-Texturing.json)
<video controls autoplay loop src="https://github.com/user-attachments/assets/c03fe162-c2fd-4499-aa8d-43e5863ceb3e"></video>
<video controls autoplay loop src="https://github.com/user-attachments/assets/0d67dae6-2167-4bf7-83f6-d7c6c3b8e2e8"></video>
<video controls autoplay loop src="https://github.com/user-attachments/assets/943d8d31-30e0-4b9f-968d-a68b5899d83d"></video>
<video controls autoplay loop src="https://github.com/user-attachments/assets/47b77c9e-a121-45c4-a6d0-f307bda1579c"></video>
<video controls autoplay loop src="https://github.com/user-attachments/assets/35c6cee5-d408-4559-88aa-753741eacb95"></video>
<video controls autoplay loop src="https://github.com/user-attachments/assets/dcf33c27-0ff7-4379-91b5-a0efe229d63c"></video>
- **Stable3DGen**: [Stable-X/Stable3DGen](https://github.com/Stable-X/Stable3DGen)
- Two models pipeline:
- Stable3DGen: Single image to 3D Mesh
@@ -106,43 +106,6 @@
true
]
},
{
"id": 15,
"type": "LoadImage",
"pos": [
4655.7041015625,
132.22010803222656
],
"size": [
325.982177734375,
338.67474365234375
],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
39
]
},
{
"name": "MASK",
"type": "MASK",
"links": null
}
],
"properties": {
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"squirel_girl_grid.png",
"image"
]
},
{
"id": 26,
"type": "[Comfy3D] MVAdapter Texture Projection",
@@ -216,6 +179,43 @@
false
]
},
{
"id": 15,
"type": "LoadImage",
"pos": [
4655.7041015625,
132.22010803222656
],
"size": [
325.982177734375,
338.67474365234375
],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
39
]
},
{
"name": "MASK",
"type": "MASK",
"links": null
}
],
"properties": {
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"matermulti.png",
"image"
]
},
{
"id": 4,
"type": "PrimitiveString",
@@ -245,7 +245,7 @@
"Node name for S&R": "PrimitiveString"
},
"widgets_values": [
"output/StableGenTest/mesh.glb"
"output/StableGenTest/mater.glb"
]
}
],
@@ -295,15 +295,15 @@
"config": {},
"extra": {
"ds": {
"scale": 4.321942375150668,
"scale": 0.8227024747918819,
"offset": [
-4633.552682414396,
-263.1857272782662
-4472.103767929271,
239.69671269689613
]
},
"frontendVersion": "1.20.4",
"node_versions": {
"ComfyUI-3D-Pack": "626a5ee60d6fbe3d76d69cd89261d445dbca2bc4",
"ComfyUI-3D-Pack": "1f6bd293b66069de8bb2182bea8308a9441c961e",
"comfy-core": "0.3.35"
},
"VHS_latentpreview": false,
@@ -1,36 +1,9 @@
{
"id": "867889cd-eeb4-48b0-853c-21e125a93896",
"revision": 0,
"last_node_id": 28,
"last_link_id": 25,
"last_node_id": 34,
"last_link_id": 41,
"nodes": [
{
"id": 8,
"type": "PreviewImage",
"pos": [
3033.075927734375,
-927.3829956054688
],
"size": [
370.7532958984375,
288.39501953125
],
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 6
}
],
"outputs": [],
"properties": {
"Node name for S&R": "PreviewImage"
},
"widgets_values": []
},
{
"id": 26,
"type": "[Comfy3D] Make Image Grid",
@@ -49,7 +22,7 @@
{
"name": "images",
"type": "IMAGE",
"link": 22
"link": 39
}
],
"outputs": [
@@ -69,16 +42,180 @@
true
]
},
{
"id": 8,
"type": "PreviewImage",
"pos": [
3033.075927734375,
-927.3829956054688
],
"size": [
370.7532958984375,
288.39501953125
],
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 38
}
],
"outputs": [],
"properties": {
"Node name for S&R": "PreviewImage"
},
"widgets_values": []
},
{
"id": 32,
"type": "[Comfy3D] Load MVAdapter IG2MV Pipeline",
"pos": [
2138.81591796875,
-1144.373291015625
],
"size": [
353.38751220703125,
238.59991455078125
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "mvadapter_pipe",
"type": "DIFFUSERS_PIPE",
"links": [
36
]
}
],
"properties": {
"Node name for S&R": "[Comfy3D] Load MVAdapter IG2MV Pipeline"
},
"widgets_values": [
"stabilityai/stable-diffusion-xl-base-1.0",
"madebyollin/sdxl-vae-fp16-fix",
"huanngzh/mv-adapter",
"ddpm",
6,
true,
true,
""
]
},
{
"id": 3,
"type": "LoadImage",
"pos": [
2151.8310546875,
-841.9072265625
],
"size": [
346.8450622558594,
326
],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
37
]
},
{
"name": "MASK",
"type": "MASK",
"links": null
}
],
"properties": {
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"squirrel_girl_1.png",
"image"
]
},
{
"id": 34,
"type": "[Comfy3D] MVAdapter IG2MV",
"pos": [
2552.5146484375,
-933.671875
],
"size": [
400,
402
],
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "mvadapter_pipe",
"type": "DIFFUSERS_PIPE",
"link": 36
},
{
"name": "reference_image",
"type": "IMAGE",
"link": 37
},
{
"name": "mesh_path",
"type": "STRING",
"widget": {
"name": "mesh_path"
},
"link": 41
}
],
"outputs": [
{
"name": "multiview_images",
"type": "IMAGE",
"links": [
38,
39
]
}
],
"properties": {
"Node name for S&R": "[Comfy3D] MVAdapter IG2MV"
},
"widgets_values": [
"",
"high quality",
"watermark, ugly, deformed, noisy, blurry, low contrast",
50,
3,
1,
768,
768,
459568704818520,
"randomize",
false,
1
]
},
{
"id": 27,
"type": "PreviewImage",
"pos": [
3032.88818359375,
-449.5286865234375
3035.859130859375,
-443.73687744140625
],
"size": [
420.0350036621094,
123.75502014160156
246
],
"flags": {},
"order": 6,
@@ -96,43 +233,6 @@
},
"widgets_values": []
},
{
"id": 3,
"type": "LoadImage",
"pos": [
2153.30078125,
-839.7021484375
],
"size": [
346.8450622558594,
326
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
19
]
},
{
"name": "MASK",
"type": "MASK",
"links": null
}
],
"properties": {
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"squirrel_girl_1.png",
"image"
]
},
{
"id": 9,
"type": "PrimitiveString",
@@ -145,7 +245,7 @@
67.5549545288086
],
"flags": {},
"order": 1,
"order": 2,
"mode": 0,
"inputs": [],
"outputs": [
@@ -153,152 +253,20 @@
"name": "STRING",
"type": "STRING",
"links": [
7
41
]
}
],
"title": "Mesh Path",
"properties": {
"Node name for S&R": "PrimitiveString"
},
"widgets_values": [
"output/StableGenTest/mesh.glb"
]
},
{
"id": 7,
"type": "[Comfy3D] MVAdapter Image To MultiView",
"pos": [
2557.745361328125,
-938.2529296875
],
"size": [
406.614990234375,
407.14501953125
],
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "mvadapter_pipe",
"type": "DIFFUSERS_PIPE",
"link": 25
},
{
"name": "reference_image",
"type": "IMAGE",
"link": 19
},
{
"name": "mesh_path",
"type": "STRING",
"widget": {
"name": "mesh_path"
},
"link": 7
}
],
"outputs": [
{
"name": "multiview_images",
"type": "IMAGE",
"links": [
6,
22
]
}
],
"properties": {
"Node name for S&R": "[Comfy3D] MVAdapter Image To MultiView"
},
"widgets_values": [
"",
"high quality",
"watermark, ugly, deformed, noisy, blurry, low contrast",
50,
3,
1,
768,
768,
710491314281528,
"randomize",
false,
1
]
},
{
"id": 28,
"type": "[Comfy3D] Load MVAdapter IG2MV Pipeline",
"pos": [
2154.555908203125,
-1155.862060546875
],
"size": [
357.2724609375,
257.3499755859375
],
"flags": {},
"order": 2,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "mvadapter_pipe",
"type": "DIFFUSERS_PIPE",
"links": [
25
]
}
],
"properties": {
"Node name for S&R": "[Comfy3D] Load MVAdapter IG2MV Pipeline"
},
"widgets_values": [
"stabilityai/stable-diffusion-xl-base-1.0",
"madebyollin/sdxl-vae-fp16-fix",
"huanngzh/mv-adapter",
"default",
6,
true,
true,
"",
""
]
}
],
"links": [
[
6,
7,
0,
8,
0,
"IMAGE"
],
[
7,
9,
0,
7,
2,
"STRING"
],
[
19,
3,
0,
7,
1,
"IMAGE"
],
[
22,
7,
0,
26,
0,
"IMAGE"
],
[
23,
26,
@@ -308,28 +276,60 @@
"IMAGE"
],
[
25,
28,
36,
32,
0,
7,
34,
0,
"DIFFUSERS_PIPE"
],
[
37,
3,
0,
34,
1,
"IMAGE"
],
[
38,
34,
0,
8,
0,
"IMAGE"
],
[
39,
34,
0,
26,
0,
"IMAGE"
],
[
41,
9,
0,
34,
2,
"STRING"
]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 0.9070294784580498,
"scale": 0.9523809523809523,
"offset": [
-1941.4116551684099,
1211.3556830868408
-1889.6151044555788,
1117.4222501296233
]
},
"frontendVersion": "1.20.4",
"node_versions": {
"comfy-core": "0.3.35",
"ComfyUI-3D-Pack": "626a5ee60d6fbe3d76d69cd89261d445dbca2bc4"
"ComfyUI-3D-Pack": "1f6bd293b66069de8bb2182bea8308a9441c961e",
"comfy-core": "0.3.35"
},
"VHS_latentpreview": false,
"VHS_latentpreviewrate": 0,
@@ -1,160 +0,0 @@
{
"id": "fd4d59aa-116b-4ec5-920e-f918c36151fe",
"revision": 0,
"last_node_id": 3,
"last_link_id": 2,
"nodes": [
{
"id": 2,
"type": "PreviewImage",
"pos": [
2673.054931640625,
-795.7294311523438
],
"size": [
291.89996337890625,
246
],
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 1
}
],
"outputs": [],
"properties": {
"Node name for S&R": "PreviewImage"
},
"widgets_values": []
},
{
"id": 1,
"type": "[Comfy3D] MVAdapter Text To MultiView",
"pos": [
2183.7578125,
-848.2293701171875
],
"size": [
400,
358
],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [
{
"name": "mvadapter_t2mv_pipe",
"type": "DIFFUSERS_PIPE",
"link": 2
}
],
"outputs": [
{
"name": "multiview_images",
"type": "IMAGE",
"links": [
1
]
}
],
"properties": {
"Node name for S&R": "[Comfy3D] MVAdapter Text To MultiView"
},
"widgets_values": [
"Mater, a rusty and beat-up tow truck from the 2006 Disney/Pixar animated film 'Cars', with a rusty brown exterior, big blue eyes.",
"watermark, ugly, deformed, noisy, blurry, low contrast",
6,
50,
7,
768,
768,
937383047416961,
"randomize",
1,
"0,45,90,180,270,315"
]
},
{
"id": 3,
"type": "[Comfy3D] Load MVAdapter T2MV Pipeline",
"pos": [
1833.75634765625,
-913.3286743164062
],
"size": [
298.8599548339844,
250
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "mvadapter_t2mv_pipe",
"type": "DIFFUSERS_PIPE",
"links": [
2
]
}
],
"properties": {
"Node name for S&R": "[Comfy3D] Load MVAdapter T2MV Pipeline"
},
"widgets_values": [
"stabilityai/stable-diffusion-xl-base-1.0",
"madebyollin/sdxl-vae-fp16-fix",
"huanngzh/mv-adapter",
"default",
6,
true,
false,
"",
""
]
}
],
"links": [
[
1,
1,
0,
2,
0,
"IMAGE"
],
[
2,
3,
0,
1,
0,
"DIFFUSERS_PIPE"
]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 1.2155062499999998,
"offset": [
-1674.815702398016,
990.4808470306099
]
},
"node_versions": {
"comfy-core": "0.3.35",
"ComfyUI-3D-Pack": "354ab9f7b56129f1f0fc4a42bea1751b266fd222"
},
"frontendVersion": "1.20.4",
"VHS_latentpreview": false,
"VHS_latentpreviewrate": 0,
"VHS_MetadataImage": true,
"VHS_KeepIntermediate": true
},
"version": 0.4
}
@@ -0,0 +1,289 @@
{
"id": "69e3650c-32e7-4419-9cad-665856cf7b64",
"revision": 0,
"last_node_id": 6,
"last_link_id": 5,
"nodes": [
{
"id": 5,
"type": "[Comfy3D] Make Image Grid",
"pos": [
3231.359130859375,
-755.6622924804688
],
"size": [
270,
82
],
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 5
}
],
"outputs": [
{
"name": "image_grid",
"type": "IMAGE",
"links": [
3
]
}
],
"properties": {
"Node name for S&R": "[Comfy3D] Make Image Grid"
},
"widgets_values": [
1,
true
]
},
{
"id": 6,
"type": "PreviewImage",
"pos": [
3233.5966796875,
-612.426513671875
],
"size": [
420.0350036621094,
246
],
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 3
}
],
"outputs": [],
"properties": {
"Node name for S&R": "PreviewImage"
},
"widgets_values": []
},
{
"id": 4,
"type": "PreviewImage",
"pos": [
3230.8134765625,
-1096.0721435546875
],
"size": [
370.7532958984375,
288.39501953125
],
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 4
}
],
"outputs": [],
"properties": {
"Node name for S&R": "PreviewImage"
},
"widgets_values": []
},
{
"id": 1,
"type": "[Comfy3D] Load MVAdapter TG2MV Pipeline",
"pos": [
2334.81494140625,
-1060.3724365234375
],
"size": [
355.9496154785156,
226
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "mvadapter_tg2mv_pipe",
"type": "DIFFUSERS_PIPE",
"links": [
1
]
}
],
"properties": {
"Node name for S&R": "[Comfy3D] Load MVAdapter TG2MV Pipeline"
},
"widgets_values": [
"stabilityai/stable-diffusion-xl-base-1.0",
"madebyollin/sdxl-vae-fp16-fix",
"huanngzh/mv-adapter",
"ddpm",
6,
true,
true,
""
]
},
{
"id": 2,
"type": "[Comfy3D] MVAdapter TG2MV",
"pos": [
2758.3154296875,
-917.5724487304688
],
"size": [
400,
358
],
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "mvadapter_tg2mv_pipe",
"type": "DIFFUSERS_PIPE",
"link": 1
},
{
"name": "mesh_path",
"type": "STRING",
"widget": {
"name": "mesh_path"
},
"link": 2
}
],
"outputs": [
{
"name": "multiview_images",
"type": "IMAGE",
"links": [
4,
5
]
}
],
"properties": {
"Node name for S&R": "[Comfy3D] MVAdapter TG2MV"
},
"widgets_values": [
"",
"Mater, a rusty and beat-up tow truck from the 2006 Disney/Pixar animated film 'Cars', with a rusty brown exterior, big blue eyes.",
"watermark, ugly, deformed, noisy, blurry, low contrast",
6,
50,
7,
768,
768,
192532995061057,
"randomize",
1
]
},
{
"id": 3,
"type": "PrimitiveString",
"pos": [
2330.61767578125,
-762.8717651367188
],
"size": [
343.5000305175781,
67.5549545288086
],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": [
2
]
}
],
"title": "Mesh Path",
"properties": {
"Node name for S&R": "PrimitiveString"
},
"widgets_values": [
"output/StableGenTest/mater.glb"
]
}
],
"links": [
[
1,
1,
0,
2,
0,
"DIFFUSERS_PIPE"
],
[
2,
3,
0,
2,
1,
"STRING"
],
[
3,
5,
0,
6,
0,
"IMAGE"
],
[
4,
2,
0,
4,
0,
"IMAGE"
],
[
5,
2,
0,
5,
0,
"IMAGE"
]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 0.9523809523809523,
"offset": [
-2165.4151899047965,
1209.1222287673186
]
},
"frontendVersion": "1.20.4",
"node_versions": {
"ComfyUI-3D-Pack": "1f6bd293b66069de8bb2182bea8308a9441c961e",
"comfy-core": "0.3.35"
},
"VHS_latentpreview": false,
"VHS_latentpreviewrate": 0,
"VHS_MetadataImage": true,
"VHS_KeepIntermediate": true
},
"version": 0.4
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 MiB

+63 -97
View File
@@ -107,8 +107,8 @@ from Stable3DGen.pipeline_builders import StableGenPipelineBuilder
from MV_Adapter.mvadapter_node_utils import (
prepare_pipeline as mvadapter_prepare_pipeline,
run_pipeline as mvadapter_run_pipeline,
prepare_t2mv_pipeline as mvadapter_prepare_t2mv_pipeline,
run_t2mv_pipeline as mvadapter_run_t2mv_pipeline,
prepare_tg2mv_pipeline as mvadapter_prepare_tg2mv_pipeline,
run_tg2mv_pipeline as mvadapter_run_tg2mv_pipeline,
prepare_texture_pipeline as mvadapter_prepare_texture_pipeline,
download_texture_checkpoints,
)
@@ -4787,7 +4787,7 @@ class StableGen_StableX_Process_Image:
except Exception as e:
raise Exception(f"[StableGen_StableX_Process_Image] Image processing failed: {str(e)}")
# --- MV START ----
class Load_MVAdapter_IG2MV_Pipeline:
"""Loader pipeline for MV-Adapter (Image to Multi-View)"""
CATEGORY = "Comfy3D/Algorithm"
@@ -4806,45 +4806,44 @@ class Load_MVAdapter_IG2MV_Pipeline:
"vae_model": (["madebyollin/sdxl-vae-fp16-fix", "None"],
{"default": "madebyollin/sdxl-vae-fp16-fix"}),
"adapter_path": (["huanngzh/mv-adapter"], {"default": "huanngzh/mv-adapter"}),
"scheduler": (["default", "ddpm", "lcm"], {"default": "default"}),
"scheduler": (["ddpm"], {"default": "ddpm"}),
"num_views": ("INT", {"default": 6, "min": 1, "max": 16}),
"use_fp16": ("BOOLEAN", {"default": True}),
"use_mmgp": ("BOOLEAN", {"default": True}),
},
"optional": {
"unet_model": ("STRING", {"default": ""}),
"lora_model": ("STRING", {"default": ""}),
}
}
@classmethod
def load(cls, base_model, vae_model, adapter_path, scheduler, num_views,
use_fp16, use_mmgp, unet_model="", lora_model=""):
use_fp16, use_mmgp, lora_model=""):
dtype = torch.float16 if use_fp16 else torch.float32
vae_model = None if vae_model == "None" else vae_model
unet_model = None if not unet_model else unet_model
lora_model = None if not lora_model else lora_model
pipe = mvadapter_prepare_pipeline(
base_model=base_model,
vae_model=vae_model,
unet_model=unet_model,
lora_model=lora_model,
adapter_path=adapter_path,
scheduler=scheduler,
num_views=num_views,
device=DEVICE_STR,
dtype=dtype,
use_mmgp=use_mmgp,
adapter_local_path=cls.CKPT_MVADAPTER_PATH
)
# Подготавливаем параметры для пайплайна
pipeline_kwargs = {
"base_model": base_model,
"vae_model": vae_model,
"lora_model": lora_model,
"adapter_path": adapter_path,
"scheduler": scheduler,
"num_views": num_views,
"device": DEVICE_STR,
"dtype": dtype,
"use_mmgp": use_mmgp,
"adapter_local_path": cls.CKPT_MVADAPTER_PATH
}
print("MV-Adapter pipeline loaded successfully")
pipe = mvadapter_prepare_pipeline(**pipeline_kwargs)
print("MV-Adapter IG2MV pipeline loaded successfully")
return (pipe,)
class MVAdapter_Image_To_MultiView:
class MVAdapter_IG2MV:
"""Generate multi-view images from single image and 3D mesh"""
CATEGORY = "Comfy3D/Algorithm"
RETURN_TYPES = ("IMAGE",)
@@ -4902,23 +4901,14 @@ class MVAdapter_Image_To_MultiView:
device=DEVICE_STR,
)
# grid_image = make_image_grid(images, rows=1)
# grid_tensor = pils_to_torch_imgs([grid_image], device=DEVICE_STR)
# print(f"Generated multiview images: {grid_tensor.shape}")
# return (grid_tensor,)
# return images
return_images = pils_to_torch_imgs(images, device=DEVICE_STR)
return (return_images,)
class Load_MVAdapter_T2MV_Pipeline:
"""Loader pipeline for MV-Adapter Text-to-Multi-View"""
class Load_MVAdapter_TG2MV_Pipeline:
"""Loader pipeline for MV-Adapter Text-Guided to Multi-View (TG2MV)"""
CATEGORY = "Comfy3D/Algorithm"
RETURN_TYPES = ("DIFFUSERS_PIPE",)
RETURN_NAMES = ("mvadapter_t2mv_pipe",)
RETURN_NAMES = ("mvadapter_tg2mv_pipe",)
FUNCTION = "load"
CKPT_MVADAPTER_PATH = os.path.join(CKPT_DIFFUSERS_PATH, "huanngzh", "MV-Adapter")
@@ -4932,45 +4922,51 @@ class Load_MVAdapter_T2MV_Pipeline:
"vae_model": (["madebyollin/sdxl-vae-fp16-fix", "None"],
{"default": "madebyollin/sdxl-vae-fp16-fix"}),
"adapter_path": (["huanngzh/mv-adapter"], {"default": "huanngzh/mv-adapter"}),
"scheduler": (["default", "ddpm", "lcm"], {"default": "default"}),
"scheduler": (["ddpm"], {"default": "ddpm"}),
"num_views": ("INT", {"default": 6, "min": 1, "max": 16}),
"use_fp16": ("BOOLEAN", {"default": True}),
"use_mmgp": ("BOOLEAN", {"default": False}),
},
"optional": {
"unet_model": ("STRING", {"default": ""}),
"lora_model": ("STRING", {"default": ""}),
}
}
@classmethod
def load(cls, base_model, vae_model, adapter_path, scheduler, num_views,
use_fp16, use_mmgp, unet_model="", lora_model=""):
use_fp16, use_mmgp, lora_model=""):
if torch.cuda.is_available():
torch.cuda.empty_cache()
dtype = torch.float16 if use_fp16 else torch.float32
vae_model = None if vae_model == "None" else vae_model
unet_model = None if not unet_model else unet_model
lora_model = None if not lora_model else lora_model
pipe = mvadapter_prepare_t2mv_pipeline(
base_model=base_model,
vae_model=vae_model,
unet_model=unet_model,
lora_model=lora_model,
adapter_path=adapter_path,
scheduler=scheduler,
num_views=num_views,
device=DEVICE_STR,
dtype=dtype,
use_mmgp=use_mmgp,
adapter_local_path=cls.CKPT_MVADAPTER_PATH
)
pipeline_kwargs = {
"base_model": base_model,
"vae_model": vae_model,
"lora_model": lora_model,
"adapter_path": adapter_path,
"scheduler": scheduler,
"num_views": num_views,
"device": DEVICE_STR,
"dtype": dtype,
"use_mmgp": use_mmgp,
"adapter_local_path": cls.CKPT_MVADAPTER_PATH
}
print("MV-Adapter T2MV pipeline loaded successfully")
return (pipe,)
try:
pipe = mvadapter_prepare_tg2mv_pipeline(**pipeline_kwargs)
print("MV-Adapter TG2MV pipeline loaded successfully")
return (pipe,)
except Exception as e:
raise e
class MVAdapter_Text_To_MultiView:
"""Generate multi-view images from text prompt"""
class MVAdapter_TG2MV:
"""Generate multi-view images from text prompt using 3D mesh guidance"""
CATEGORY = "Comfy3D/Algorithm"
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("multiview_images",)
@@ -4980,7 +4976,8 @@ class MVAdapter_Text_To_MultiView:
def INPUT_TYPES(cls):
return {
"required": {
"mvadapter_t2mv_pipe": ("DIFFUSERS_PIPE",),
"mvadapter_tg2mv_pipe": ("DIFFUSERS_PIPE",),
"mesh_path": ("STRING", {"default": ""}),
"prompt": ("STRING", {"default": "a high quality 3D model", "multiline": True}),
"negative_prompt": ("STRING", {"default": "watermark, ugly, deformed, noisy, blurry, low contrast", "multiline": True}),
"num_views": ("INT", {"default": 6, "min": 1, "max": 16}),
@@ -4992,30 +4989,18 @@ class MVAdapter_Text_To_MultiView:
},
"optional": {
"lora_scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 2.0, "step": 0.1}),
"azimuth_angles": ("STRING", {"default": "0,45,90,180,270,315"}),
}
}
def run(self, mvadapter_t2mv_pipe, prompt, negative_prompt, num_views,
num_inference_steps, guidance_scale, height, width, seed,
lora_scale=1.0, azimuth_angles="0,45,90,180,270,315"):
def run(self, mvadapter_tg2mv_pipe, mesh_path, prompt, negative_prompt, num_views,
num_inference_steps, guidance_scale, height, width, seed, lora_scale=1.0):
# Parse azimuth angles
try:
azimuth_deg = [int(x.strip()) for x in azimuth_angles.split(",")]
# Ensure we have enough angles for the number of views
if len(azimuth_deg) < num_views:
# Repeat the pattern if we don't have enough angles
while len(azimuth_deg) < num_views:
azimuth_deg.extend(azimuth_deg[:min(len(azimuth_deg), num_views - len(azimuth_deg))])
azimuth_deg = azimuth_deg[:num_views] # Take only the first num_views
except:
# Fallback to default angles
azimuth_deg = [0, 45, 90, 180, 270, 315][:num_views]
if not mesh_path or not os.path.exists(mesh_path):
raise ValueError(f"Mesh path does not exist: {mesh_path}")
# Execute generation
images = mvadapter_run_t2mv_pipeline(
pipe=mvadapter_t2mv_pipe,
images, pos_images, normal_images = mvadapter_run_tg2mv_pipeline(
pipe=mvadapter_tg2mv_pipe,
mesh_path=mesh_path,
num_views=num_views,
text=prompt,
height=height,
@@ -5026,17 +5011,9 @@ class MVAdapter_Text_To_MultiView:
negative_prompt=negative_prompt,
lora_scale=lora_scale,
device=DEVICE_STR,
azimuth_deg=azimuth_deg,
)
# Create image grid
# grid_image = make_image_grid(images, rows=1)
# # Convert PIL image to torch tensor using shared utils
# grid_tensor = pils_to_torch_imgs([grid_image], device=DEVICE_STR)
# print(f"Generated T2MV multiview images: {grid_tensor.shape}")
# return (grid_tensor,)
return_images = pils_to_torch_imgs(images, device=DEVICE_STR)
return (return_images,)
@@ -5063,11 +5040,9 @@ class Load_MVAdapter_Texture_Pipeline:
@classmethod
def load(cls, upscaler_ckpt_name, inpaint_ckpt_name, use_mmgp, auto_download):
# Form full paths from model names
upscaler_ckpt_path = os.path.join(cls.TEXTURE_CKPT_DIR, upscaler_ckpt_name) if upscaler_ckpt_name.strip() else None
inpaint_ckpt_path = os.path.join(cls.TEXTURE_CKPT_DIR, inpaint_ckpt_name) if inpaint_ckpt_name.strip() else None
# Auto-download checkpoints if enabled
if auto_download:
download_texture_checkpoints(cls.TEXTURE_CKPT_DIR, upscaler_ckpt_path, inpaint_ckpt_path)
@@ -5132,11 +5107,9 @@ class MVAdapter_Texture_Projection:
if not mesh_path or not os.path.exists(mesh_path):
raise ValueError(f"Mesh file does not exist: {mesh_path}")
# Convert to absolute paths
save_dir = os.path.abspath(save_dir)
os.makedirs(save_dir, exist_ok=True)
# Parse camera angles with fallback to defaults
try:
azimuth_deg = [float(x.strip()) for x in camera_azimuth_deg.split(",")]
elevation_deg = [float(x.strip()) for x in camera_elevation_deg.split(",")]
@@ -5144,10 +5117,8 @@ class MVAdapter_Texture_Projection:
azimuth_deg = [0, 90, 180, 270, 180, 180]
elevation_deg = [0, 0, 0, 0, 89.99, -89.99]
# Adjust azimuth angles (subtract 90 as in original code)
azimuth_deg_corrected = [x - 90 for x in azimuth_deg]
# Save grid image temporarily
temp_grid_path = os.path.join(save_dir, f"{save_name}_temp_grid.png")
pil_grid.save(temp_grid_path)
@@ -5164,7 +5135,6 @@ class MVAdapter_Texture_Projection:
inpaint_mode=inpaint_mode
)
# Prepare texture pipeline arguments
texture_args = {
"mesh_path": mesh_path,
"save_dir": save_dir,
@@ -5184,7 +5154,6 @@ class MVAdapter_Texture_Projection:
"apply_dilate": apply_dilate,
}
# Add PBR settings if enabled
if create_pbr_model:
texture_args.update({
"base_color_path": temp_grid_path,
@@ -5193,11 +5162,9 @@ class MVAdapter_Texture_Projection:
output = texture_pipeline(**texture_args)
# Cleanup temp file
if os.path.exists(temp_grid_path):
os.remove(temp_grid_path)
# Convert to absolute paths
shaded_path = os.path.abspath(output.shaded_model_save_path) if output.shaded_model_save_path else ""
pbr_path = os.path.abspath(output.pbr_model_save_path) if output.pbr_model_save_path else ""
@@ -5208,8 +5175,7 @@ class MVAdapter_Texture_Projection:
return (shaded_path, pbr_path)
except Exception as e:
# Cleanup temp file on error
if os.path.exists(temp_grid_path):
os.remove(temp_grid_path)
raise e