- 新增场景属性 `sna_render_resolution` 用于选择渲染分辨率(1024x1024 或 2048x2048) - 修改 `Comfy_OT_Mvgen_B6229` 操作符,在执行后自动渲染深度图和法线图 - 新增 `_render_depth` 和 `_render_normal` 函数,从 `Materials.blend` 资产文件加载专用材质和节点组进行渲染 - 在 UI 面板中添加分辨率选择下拉菜单并更新按钮文本 - 在 `unregister` 函数中安全清理新增的场景属性
293 lines
11 KiB
Python
293 lines
11 KiB
Python
import bpy
|
|
import bpy.utils.previews
|
|
from mathutils import Vector
|
|
import os
|
|
def property_exists(prop_path, glob, loc):
|
|
try:
|
|
eval(prop_path, glob, loc)
|
|
return True
|
|
except: # noqa: E722
|
|
return False
|
|
|
|
class Comfy_OT_Aovbake_Ad980(bpy.types.Operator):
|
|
bl_idname = "comfyui.aovbake_ad980"
|
|
bl_label = "aovbake"
|
|
bl_description = ""
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
@classmethod
|
|
def poll(cls, _context):
|
|
if bpy.app.version >= (3, 0, 0) and True:
|
|
cls.poll_message_set(
|
|
"Please save your Blender file before running the script."
|
|
)
|
|
return not ("" == bpy.data.filepath)
|
|
|
|
def execute(self, _context):
|
|
if "" == bpy.data.filepath:
|
|
self.report(
|
|
{"ERROR"},
|
|
message="Please save your Blender file before running the script.",
|
|
)
|
|
else:
|
|
if bpy.context.view_layer.objects.active.type == "MESH":
|
|
bpy.data.scenes["Scene"].render.film_transparent = True
|
|
bpy.data.scenes["Scene"].render.engine = "CYCLES"
|
|
bpy.data.scenes["Scene"].view_layers["ViewLayer"].use_pass_z = True
|
|
bpy.data.scenes["Scene"].cycles.samples = 16
|
|
res = int(bpy.context.scene.sna_render_resolution)
|
|
bpy.data.scenes["Scene"].render.resolution_x = res
|
|
bpy.data.scenes["Scene"].render.resolution_y = res
|
|
bpy.data.scenes["Scene"].view_layers["ViewLayer"].use_pass_normal = True
|
|
bpy.context.scene.sna_activeobject = (
|
|
bpy.context.view_layer.objects.active
|
|
)
|
|
bpy.data.scenes["Scene"].use_nodes = True
|
|
import math
|
|
|
|
def show_message_box(message="", title="Message", icon="INFO"):
|
|
"""Shows a pop-up message in Blender."""
|
|
|
|
def draw(self, context):
|
|
self.layout.label(text=message)
|
|
|
|
bpy.context.window_manager.popup_menu(draw, title=title, icon=icon)
|
|
|
|
def create_camera_for_object(obj):
|
|
if obj.type != "MESH":
|
|
show_message_box("Selected object must be a mesh.", "Error")
|
|
return None
|
|
bpy.ops.object.camera_add()
|
|
cam = bpy.context.object
|
|
cam.name = "ObjectCamera"
|
|
bpy.context.scene.camera = cam
|
|
bpy.context.scene.sna_bakecam = bpy.context.scene.camera
|
|
bbox_corners = [
|
|
obj.matrix_world @ Vector(corner) for corner in obj.bound_box
|
|
]
|
|
min_x = min(corner.x for corner in bbox_corners)
|
|
max_x = max(corner.x for corner in bbox_corners)
|
|
min_y = min(corner.y for corner in bbox_corners)
|
|
max_y = max(corner.y for corner in bbox_corners)
|
|
min_z = min(corner.z for corner in bbox_corners)
|
|
max_z = max(corner.z for corner in bbox_corners)
|
|
center = (
|
|
Vector((min_x + max_x, min_y + max_y, min_z + max_z)) / 2
|
|
).to_3d()
|
|
size = max(max_x - min_x, max_y - min_y, max_z - min_z) * 1.5
|
|
cam.location = center + Vector((0, -size, 0))
|
|
cam.data.type = "ORTHO"
|
|
cam.data.ortho_scale = size
|
|
cam.rotation_euler = (math.radians(90), 0, 0)
|
|
bpy.context.scene.sna_activeobject.select_set(state=True)
|
|
bpy.ops.view3d.camera_to_view_selected()
|
|
return cam
|
|
|
|
def main():
|
|
obj = bpy.context.active_object
|
|
if obj is None:
|
|
show_message_box("No active object selected.", "Error")
|
|
return
|
|
create_camera_for_object(obj)
|
|
|
|
main()
|
|
bpy.context.scene.sna_activeobject.select_set(
|
|
state=True,
|
|
)
|
|
bpy.context.view_layer.objects.active = (
|
|
bpy.context.scene.sna_activeobject
|
|
)
|
|
bpy.data.scenes["Scene"].use_nodes = False
|
|
return {"FINISHED"}
|
|
|
|
def invoke(self, context, _event):
|
|
return self.execute(context)
|
|
|
|
|
|
def _ensure_output_dir():
|
|
"""Ensure output directory exists next to the blend file and return its path."""
|
|
blend_dir = os.path.dirname(bpy.data.filepath)
|
|
output_dir = os.path.join(blend_dir, 'output')
|
|
os.makedirs(output_dir, exist_ok=True)
|
|
return output_dir
|
|
|
|
|
|
def _append_from_materials_blend(data_type, name):
|
|
"""Append a data-block from Materials.blend if it doesn't already exist.
|
|
data_type: 'NodeTree' or 'Material'
|
|
"""
|
|
collection_map = {
|
|
'NodeTree': bpy.data.node_groups,
|
|
'Material': bpy.data.materials,
|
|
}
|
|
collection = collection_map[data_type]
|
|
if name in collection:
|
|
return collection[name]
|
|
blend_path = os.path.join(os.path.dirname(__file__), 'assets', 'Materials.blend')
|
|
bpy.ops.wm.append(
|
|
directory=blend_path + '\\' + data_type + '\\',
|
|
filename=name,
|
|
link=False,
|
|
)
|
|
return collection.get(name)
|
|
|
|
|
|
def _render_depth(operator):
|
|
"""Render depth map using Depth_Mat compositor NodeTree."""
|
|
scene = bpy.context.scene
|
|
output_dir = _ensure_output_dir()
|
|
|
|
# Append the Depth_Mat compositor node tree
|
|
depth_tree = _append_from_materials_blend('NodeTree', 'Depth_Mat')
|
|
if depth_tree is None:
|
|
operator.report({'ERROR'}, message='Failed to load Depth_Mat from Materials.blend')
|
|
return False
|
|
|
|
# Save original compositor state
|
|
orig_use_nodes = scene.use_nodes
|
|
orig_comp_group = scene.compositing_node_group
|
|
|
|
# Enable compositor and assign Depth_Mat as the compositing node group
|
|
scene.use_nodes = True
|
|
scene.compositing_node_group = depth_tree
|
|
|
|
# Setup output
|
|
scene.render.filepath = os.path.join(output_dir, 'depth.png')
|
|
scene.render.image_settings.file_format = 'PNG'
|
|
scene.render.image_settings.color_mode = 'RGB'
|
|
scene.view_layers["ViewLayer"].use_pass_z = True
|
|
|
|
# Render
|
|
bpy.ops.render.render(write_still=True)
|
|
|
|
# Restore original compositor state
|
|
scene.compositing_node_group = orig_comp_group
|
|
scene.use_nodes = orig_use_nodes
|
|
|
|
return True
|
|
|
|
|
|
def _render_normal(operator):
|
|
"""Render normal map by applying Normal_Mat material to active object."""
|
|
scene = bpy.context.scene
|
|
obj = scene.sna_activeobject
|
|
output_dir = _ensure_output_dir()
|
|
|
|
# Append the Normal_Mat material
|
|
normal_mat = _append_from_materials_blend('Material', 'Normal_Mat')
|
|
if normal_mat is None:
|
|
operator.report({'ERROR'}, message='Failed to load Normal_Mat from Materials.blend')
|
|
return False
|
|
|
|
# Find the converted object (the active object after convert)
|
|
converted_obj = bpy.context.view_layer.objects.active
|
|
if converted_obj is None or converted_obj.type != 'MESH':
|
|
converted_obj = obj
|
|
|
|
# Save original materials
|
|
orig_materials = [slot.material for slot in converted_obj.material_slots]
|
|
|
|
# Clear and assign Normal_Mat
|
|
converted_obj.data.materials.clear()
|
|
converted_obj.data.materials.append(normal_mat)
|
|
|
|
# Disable compositor for normal pass (direct render)
|
|
orig_use_nodes = scene.use_nodes
|
|
scene.use_nodes = False
|
|
|
|
# Setup output
|
|
scene.render.filepath = os.path.join(output_dir, 'normal.png')
|
|
scene.render.image_settings.file_format = 'PNG'
|
|
scene.render.image_settings.color_mode = 'RGB'
|
|
|
|
# Render
|
|
bpy.ops.render.render(write_still=True)
|
|
|
|
# Restore original materials
|
|
converted_obj.data.materials.clear()
|
|
for mat in orig_materials:
|
|
converted_obj.data.materials.append(mat)
|
|
|
|
scene.use_nodes = orig_use_nodes
|
|
|
|
return True
|
|
|
|
|
|
class Comfy_OT_Mvgen_B6229(bpy.types.Operator):
|
|
bl_idname = "comfyui.mvgen_b6229"
|
|
bl_label = "生成多视角视图"
|
|
bl_description = "Generate multi-view maps including depth and normal"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
@classmethod
|
|
def poll(cls, _context):
|
|
if bpy.app.version >= (3, 0, 0) and True:
|
|
cls.poll_message_set(
|
|
"Please save your Blender file before running the script."
|
|
)
|
|
return not ("" == bpy.data.filepath)
|
|
|
|
def execute(self, _context):
|
|
if "" == bpy.data.filepath:
|
|
self.report(
|
|
{"ERROR"},
|
|
message="Please save your Blender file before running the script.",
|
|
)
|
|
else:
|
|
if (None is not bpy.context.view_layer.objects.active):
|
|
if bpy.context.view_layer.objects.active.type == 'MESH':
|
|
bpy.context.scene.sna_activeobject = bpy.context.view_layer.objects.active
|
|
bpy.context.scene.sna_activeobject_name = bpy.context.scene.sna_activeobject.name
|
|
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
|
|
if property_exists("bpy.data.node_groups['UV_New']", globals(), locals()):
|
|
pass
|
|
else:
|
|
before_data = list(bpy.data.node_groups)
|
|
bpy.ops.wm.append(directory=os.path.join(os.path.dirname(__file__), 'assets', 'geonodesuvpack.blend') + r'\NodeTree', filename='UV_New', link=False)
|
|
new_data = list(filter(lambda d: d not in before_data, list(bpy.data.node_groups)))
|
|
appended_4ED0B = None if not new_data else new_data[0]
|
|
bpy.context.view_layer.objects.active = bpy.context.scene.sna_activeobject
|
|
bpy.context.scene.sna_activeobject.select_set(state=True, )
|
|
modifier_5D814 = bpy.context.scene.sna_activeobject.modifiers.new(name='MV_Nodes', type='NODES', )
|
|
node_group = bpy.data.node_groups.get('UV_New')
|
|
if node_group is None:
|
|
self.report({'ERROR'}, message='Missing node group: UV_New')
|
|
return {"CANCELLED"}
|
|
bpy.context.scene.sna_activeobject.modifiers['MV_Nodes'].node_group = node_group
|
|
bpy.ops.object.convert(target='MESH', keep_original=True)
|
|
bpy.data.objects[bpy.context.scene.sna_activeobject_name].hide_render = True
|
|
bpy.ops.comfyui.aovbake_ad980('INVOKE_DEFAULT', )
|
|
bpy.data.objects[bpy.context.scene.sna_activeobject_name].modifiers.clear()
|
|
bpy.context.view_layer.objects[bpy.context.scene.sna_activeobject_name].hide_set(state=True, )
|
|
|
|
# --- Render Depth Map ---
|
|
_render_depth(self)
|
|
|
|
# --- Render Normal Map ---
|
|
_render_normal(self)
|
|
|
|
return {"FINISHED"}
|
|
|
|
def invoke(self, context, _event):
|
|
return self.execute(context)
|
|
|
|
|
|
class Comfy_PT_ComfyProjectorPanel(bpy.types.Panel):
|
|
bl_label = "ComfyProjector"
|
|
bl_idname = "Comfy_PT_comfyprojector"
|
|
bl_space_type = "VIEW_3D"
|
|
bl_region_type = "UI"
|
|
bl_category = "ComfyProjector"
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.prop(context.scene, "sna_render_resolution", text="Resolution")
|
|
layout.operator("comfyui.mvgen_b6229", text="生成多视角视图")
|
|
|
|
|
|
classes = (
|
|
Comfy_OT_Aovbake_Ad980,
|
|
Comfy_OT_Mvgen_B6229,
|
|
Comfy_PT_ComfyProjectorPanel,
|
|
)
|