HighTest / blender_addon.py
elahrami's picture
Upload folder using huggingface_hub
29b17c1 verified
# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT
# except for the third-party components listed below.
# Hunyuan 3D does not impose any additional limitations beyond what is outlined
# in the repsective licenses of these third-party components.
# Users must comply with all terms and conditions of original licenses of these third-party
# components and must ensure that the usage of the third party components adheres to
# all relevant laws and regulations.
# For avoidance of doubts, Hunyuan 3D means the large language models and
# their software and algorithms, including trained model weights, parameters (including
# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,
# fine-tuning enabling code and other elements of the foregoing made publicly available
# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.
bl_info = {
"name": "Hunyuan3D-2 Generator",
"author": "Tencent Hunyuan3D",
"version": (1, 0),
"blender": (3, 0, 0),
"location": "View3D > Sidebar > Hunyuan3D-2 3D Generator",
"description": "Generate/Texturing 3D models from text descriptions or images",
"category": "3D View",
}
import base64
import os
import tempfile
import threading
import bpy
import requests
from bpy.props import StringProperty, BoolProperty, IntProperty, FloatProperty
class Hunyuan3DProperties(bpy.types.PropertyGroup):
prompt: StringProperty(
name="Text Prompt",
description="Describe what you want to generate",
default=""
)
api_url: StringProperty(
name="API URL",
description="URL of the Text-to-3D API service",
default="http://localhost:8080"
)
is_processing: BoolProperty(
name="Processing",
default=False
)
job_id: StringProperty(
name="Job ID",
default=""
)
status_message: StringProperty(
name="Status Message",
default=""
)
# 添加图片路径属性
image_path: StringProperty(
name="Image",
description="Select an image to upload",
subtype='FILE_PATH'
)
# 修改后的 octree_resolution 属性
octree_resolution: IntProperty(
name="Octree Resolution",
description="Octree resolution for the 3D generation",
default=256,
min=128,
max=512,
)
num_inference_steps: IntProperty(
name="Number of Inference Steps",
description="Number of inference steps for the 3D generation",
default=20,
min=20,
max=50
)
guidance_scale: FloatProperty(
name="Guidance Scale",
description="Guidance scale for the 3D generation",
default=5.5,
min=1.0,
max=10.0
)
# 添加 texture 属性
texture: BoolProperty(
name="Generate Texture",
description="Whether to generate texture for the 3D model",
default=False
)
class Hunyuan3DOperator(bpy.types.Operator):
bl_idname = "object.generate_3d"
bl_label = "Generate 3D Model"
bl_description = "Generate a 3D model from text description, an image or a selected mesh"
job_id = ''
prompt = ""
api_url = ""
image_path = ""
octree_resolution = 256
num_inference_steps = 20
guidance_scale = 5.5
texture = False # 新增属性
selected_mesh_base64 = ""
selected_mesh = None # 新增属性,用于存储选中的 mesh
thread = None
task_finished = False
def modal(self, context, event):
if event.type in {'RIGHTMOUSE', 'ESC'}:
return {'CANCELLED'}
if self.task_finished:
print("Threaded task completed")
self.task_finished = False
props = context.scene.gen_3d_props
props.is_processing = False
return {'PASS_THROUGH'}
def invoke(self, context, event):
# 启动线程
props = context.scene.gen_3d_props
self.prompt = props.prompt
self.api_url = props.api_url
self.image_path = props.image_path
self.octree_resolution = props.octree_resolution
self.num_inference_steps = props.num_inference_steps
self.guidance_scale = props.guidance_scale
self.texture = props.texture # 获取 texture 属性的值
if self.prompt == "" and self.image_path == "":
self.report({'WARNING'}, "Please enter some text or select an image first.")
return {'FINISHED'}
# 保存选中的 mesh 对象引用
for obj in context.selected_objects:
if obj.type == 'MESH':
self.selected_mesh = obj
break
if self.selected_mesh:
temp_glb_file = tempfile.NamedTemporaryFile(delete=False, suffix=".glb")
temp_glb_file.close()
bpy.ops.export_scene.gltf(filepath=temp_glb_file.name, use_selection=True)
with open(temp_glb_file.name, "rb") as file:
mesh_data = file.read()
mesh_b64_str = base64.b64encode(mesh_data).decode()
os.unlink(temp_glb_file.name)
self.selected_mesh_base64 = mesh_b64_str
props.is_processing = True
# 将相对路径转换为相对于 Blender 文件所在目录的绝对路径
blend_file_dir = os.path.dirname(bpy.data.filepath)
self.report({'INFO'}, f"blend_file_dir {blend_file_dir}")
self.report({'INFO'}, f"image_path {self.image_path}")
if self.image_path.startswith('//'):
self.image_path = self.image_path[2:]
self.image_path = os.path.join(blend_file_dir, self.image_path)
if self.selected_mesh and self.texture:
props.status_message = "Texturing Selected Mesh...\n" \
"This may take several minutes depending \n on your GPU power."
else:
mesh_type = 'Textured Mesh' if self.texture else 'White Mesh'
prompt_type = 'Text Prompt' if self.prompt else 'Image'
props.status_message = f"Generating {mesh_type} with {prompt_type}...\n" \
"This may take several minutes depending \n on your GPU power."
self.thread = threading.Thread(target=self.generate_model)
self.thread.start()
wm = context.window_manager
wm.modal_handler_add(self)
return {'RUNNING_MODAL'}
def generate_model(self):
self.report({'INFO'}, f"Generation Start")
base_url = self.api_url.rstrip('/')
try:
if self.selected_mesh_base64 and self.texture:
# Texturing the selected mesh
if self.image_path and os.path.exists(self.image_path):
self.report({'INFO'}, f"Post Texturing with Image")
# 打开图片文件并以二进制模式读取
with open(self.image_path, "rb") as file:
# 读取文件内容
image_data = file.read()
# 对图片数据进行 Base64 编码
img_b64_str = base64.b64encode(image_data).decode()
response = requests.post(
f"{base_url}/generate",
json={
"mesh": self.selected_mesh_base64,
"image": img_b64_str,
"octree_resolution": self.octree_resolution,
"num_inference_steps": self.num_inference_steps,
"guidance_scale": self.guidance_scale,
"texture": self.texture # 传递 texture 参数
},
)
else:
self.report({'INFO'}, f"Post Texturing with Text")
response = requests.post(
f"{base_url}/generate",
json={
"mesh": self.selected_mesh_base64,
"text": self.prompt,
"octree_resolution": self.octree_resolution,
"num_inference_steps": self.num_inference_steps,
"guidance_scale": self.guidance_scale,
"texture": self.texture # 传递 texture 参数
},
)
else:
if self.image_path:
if not os.path.exists(self.image_path):
self.report({'ERROR'}, f"Image path does not exist {self.image_path}")
raise Exception(f'Image path does not exist {self.image_path}')
self.report({'INFO'}, f"Post Start Image to 3D")
# 打开图片文件并以二进制模式读取
with open(self.image_path, "rb") as file:
# 读取文件内容
image_data = file.read()
# 对图片数据进行 Base64 编码
img_b64_str = base64.b64encode(image_data).decode()
response = requests.post(
f"{base_url}/generate",
json={
"image": img_b64_str,
"octree_resolution": self.octree_resolution,
"num_inference_steps": self.num_inference_steps,
"guidance_scale": self.guidance_scale,
"texture": self.texture # 传递 texture 参数
},
)
else:
self.report({'INFO'}, f"Post Start Text to 3D")
response = requests.post(
f"{base_url}/generate",
json={
"text": self.prompt,
"octree_resolution": self.octree_resolution,
"num_inference_steps": self.num_inference_steps,
"guidance_scale": self.guidance_scale,
"texture": self.texture # 传递 texture 参数
},
)
self.report({'INFO'}, f"Post Done")
if response.status_code != 200:
self.report({'ERROR'}, f"Generation failed: {response.text}")
return
# Decode base64 and save to temporary file
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".glb")
temp_file.write(response.content)
temp_file.close()
# Import the GLB file in the main thread
def import_handler():
bpy.ops.import_scene.gltf(filepath=temp_file.name)
os.unlink(temp_file.name)
# 获取新导入的 mesh
new_obj = bpy.context.selected_objects[0] if bpy.context.selected_objects else None
if new_obj and self.selected_mesh and self.texture:
# 应用选中 mesh 的位置、旋转和缩放
new_obj.location = self.selected_mesh.location
new_obj.rotation_euler = self.selected_mesh.rotation_euler
new_obj.scale = self.selected_mesh.scale
# 隐藏原来的 mesh
self.selected_mesh.hide_set(True)
self.selected_mesh.hide_render = True
return None
bpy.app.timers.register(import_handler)
except Exception as e:
self.report({'ERROR'}, f"Error: {str(e)}")
finally:
self.task_finished = True
self.selected_mesh_base64 = ""
class Hunyuan3DPanel(bpy.types.Panel):
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'Hunyuan3D-2'
bl_label = 'Hunyuan3D-2 3D Generator'
def draw(self, context):
layout = self.layout
props = context.scene.gen_3d_props
layout.prop(props, "api_url")
layout.prop(props, "prompt")
# 添加图片选择器
layout.prop(props, "image_path")
# 添加新属性的 UI 元素
layout.prop(props, "octree_resolution")
layout.prop(props, "num_inference_steps")
layout.prop(props, "guidance_scale")
# 添加 texture 属性的 UI 元素
layout.prop(props, "texture")
row = layout.row()
row.enabled = not props.is_processing
row.operator("object.generate_3d")
if props.is_processing:
if props.status_message:
for line in props.status_message.split("\n"):
layout.label(text=line)
else:
layout.label("Processing...")
classes = (
Hunyuan3DProperties,
Hunyuan3DOperator,
Hunyuan3DPanel,
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.Scene.gen_3d_props = bpy.props.PointerProperty(type=Hunyuan3DProperties)
def unregister():
for cls in reversed(classes):
bpy.utils.unregister_class(cls)
del bpy.types.Scene.gen_3d_props
if __name__ == "__main__":
register()