diff --git a/utils.py b/utils.py index 7baf4d7..9d6cb57 100644 --- a/utils.py +++ b/utils.py @@ -177,35 +177,105 @@ def _add_rendered_image(scene, name, filepath): scene.sna_rendered_images_index = len(scene.sna_rendered_images) - 1 -def _render_depth(operator): - """Render depth map using Depth_Mat compositor NodeTree.""" +def _create_depth_material(near, far): + """Create a material that renders depth as grayscale (near=white, far=black).""" + mat_name = '__ComfyDepthMat__' + if mat_name in bpy.data.materials: + bpy.data.materials.remove(bpy.data.materials[mat_name]) + + mat = bpy.data.materials.new(name=mat_name) + mat.use_nodes = True + nodes = mat.node_tree.nodes + links = mat.node_tree.links + nodes.clear() + + # Camera Data → View Z Depth + cam_data = nodes.new('ShaderNodeCameraData') + cam_data.location = (0, 0) + + # Map Range: [near..far] → [1..0] (near=white, far=black) + map_range = nodes.new('ShaderNodeMapRange') + map_range.location = (200, 0) + map_range.inputs['From Min'].default_value = near + map_range.inputs['From Max'].default_value = far + map_range.inputs['To Min'].default_value = 1.0 + map_range.inputs['To Max'].default_value = 0.0 + map_range.clamp = True + + # Emission shader (unlit) + emission = nodes.new('ShaderNodeEmission') + emission.location = (400, 0) + + # Output + output = nodes.new('ShaderNodeOutputMaterial') + output.location = (600, 0) + + links.new(cam_data.outputs['View Z Depth'], map_range.inputs['Value']) + links.new(map_range.outputs['Result'], emission.inputs['Color']) + links.new(emission.outputs['Emission'], output.inputs['Surface']) + + return mat + + +def _get_converted_obj(scene, operator): + """Find the converted copy object using saved name.""" + converted_name = scene.get('_comfy_converted_name', '') + converted_obj = bpy.data.objects.get(converted_name) + if converted_obj is None or converted_obj.type != 'MESH': + operator.report({'ERROR'}, message='Cannot find converted copy: ' + converted_name) + return None + return converted_obj + + +def _get_depth_range(scene, converted_obj): + """Calculate the min/max view-Z depth from the camera to the object bounding box.""" + cam = scene.camera + if cam is None: + return 0.1, 10.0 + cam_inv = cam.matrix_world.inverted() + min_z = float('inf') + max_z = float('-inf') + for corner in converted_obj.bound_box: + world_pt = converted_obj.matrix_world @ Vector(corner) + local_pt = cam_inv @ world_pt + z = -local_pt.z # View Z Depth is positive distance along camera -Z + min_z = min(min_z, z) + max_z = max(max_z, z) + # Add padding + padding = max(0.01, (max_z - min_z) * 0.1) + return max(0.001, min_z - padding), max_z + padding + + +def _render_with_material(operator, mat, out_name, label): + """Generic render: apply material to converted copy, render, restore.""" scene = bpy.context.scene output_dir = _ensure_output_dir() - # Force fresh Depth_Mat by removing any cached version - if 'Depth_Mat' in bpy.data.node_groups: - bpy.data.node_groups.remove(bpy.data.node_groups['Depth_Mat']) - - # Append fresh Depth_Mat from Materials.blend - 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') + converted_obj = _get_converted_obj(scene, operator) + if converted_obj is None: return False - # Save original render settings + # Save original state + orig_materials = [slot.material for slot in converted_obj.material_slots] orig_use_nodes = scene.use_nodes - orig_comp_group = scene.compositing_node_group + orig_use_compositing = scene.render.use_compositing orig_filepath = scene.render.filepath orig_format = scene.render.image_settings.file_format orig_color_mode = scene.render.image_settings.color_mode - # Enable compositor and assign Depth_Mat as the compositing node group - scene.use_nodes = True - scene.compositing_node_group = depth_tree - scene.view_layers["ViewLayer"].use_pass_z = True + # Replace ALL material slots with the render material + if len(converted_obj.material_slots) == 0: + converted_obj.data.materials.append(mat) + else: + for i in range(len(converted_obj.material_slots)): + converted_obj.material_slots[i].material = mat + + # Fully disable compositor (both flags needed in Blender 5.0) + scene.use_nodes = False + scene.render.use_compositing = False # Setup output - out_path = os.path.join(output_dir, 'depth.png') + out_path = os.path.join(output_dir, out_name) scene.render.filepath = out_path scene.render.image_settings.file_format = 'PNG' scene.render.image_settings.color_mode = 'RGB' @@ -213,73 +283,42 @@ def _render_depth(operator): # Render bpy.ops.render.render(write_still=True) - # Restore all settings - scene.compositing_node_group = orig_comp_group + # Restore original materials per slot + if len(orig_materials) == 0: + converted_obj.data.materials.clear() + else: + for i, mat_orig in enumerate(orig_materials): + if i < len(converted_obj.material_slots): + converted_obj.material_slots[i].material = mat_orig scene.use_nodes = orig_use_nodes + scene.render.use_compositing = orig_use_compositing scene.render.filepath = orig_filepath scene.render.image_settings.file_format = orig_format scene.render.image_settings.color_mode = orig_color_mode - # Add to rendered images list - _add_rendered_image(scene, 'depth', out_path) - + _add_rendered_image(scene, label, out_path) return True -def _render_normal(operator): - """Render normal map by applying Normal_Mat material to active object.""" +def _render_depth(operator): + """Render depth map using a programmatic depth material.""" scene = bpy.context.scene - obj = scene.sna_activeobject - output_dir = _ensure_output_dir() + converted_obj = _get_converted_obj(scene, operator) + if converted_obj is None: + return False - # Append the Normal_Mat material + near, far = _get_depth_range(scene, converted_obj) + depth_mat = _create_depth_material(near, far) + return _render_with_material(operator, depth_mat, 'depth.png', 'depth') + + +def _render_normal(operator): + """Render normal map using 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 and render settings - orig_materials = [slot.material for slot in converted_obj.material_slots] - orig_use_nodes = scene.use_nodes - orig_filepath = scene.render.filepath - orig_format = scene.render.image_settings.file_format - orig_color_mode = scene.render.image_settings.color_mode - - # Clear and assign Normal_Mat - converted_obj.data.materials.clear() - converted_obj.data.materials.append(normal_mat) - - # Disable compositor for normal pass (direct render) - scene.use_nodes = False - - # Setup output - out_path = os.path.join(output_dir, 'normal.png') - scene.render.filepath = out_path - 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 and settings - converted_obj.data.materials.clear() - for mat in orig_materials: - converted_obj.data.materials.append(mat) - - scene.use_nodes = orig_use_nodes - scene.render.filepath = orig_filepath - scene.render.image_settings.file_format = orig_format - scene.render.image_settings.color_mode = orig_color_mode - - # Add to rendered images list - _add_rendered_image(scene, 'normal', out_path) - - return True + return _render_with_material(operator, normal_mat, 'normal.png', '法向') class Comfy_OT_Mvgen_B6229(bpy.types.Operator): @@ -332,6 +371,8 @@ class Comfy_OT_Mvgen_B6229(bpy.types.Operator): return {"CANCELLED"} bpy.context.scene.sna_activeobject.modifiers['MV_Nodes'].node_group = node_group bpy.ops.object.convert(target='MESH', keep_original=True) + # Save the converted copy name immediately (active object after convert) + bpy.context.scene['_comfy_converted_name'] = bpy.context.active_object.name 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() @@ -393,13 +434,10 @@ class Comfy_OT_CleanupMvgen(bpy.types.Operator): def execute(self, context): scene = context.scene - # Delete the converted copy (active object that is NOT the original) - orig_name = scene.sna_activeobject_name - if orig_name: - # The converted copy is typically named with .001 suffix - for obj in list(bpy.data.objects): - if obj.name != orig_name and obj.name.startswith(orig_name) and obj.type == 'MESH': - bpy.data.objects.remove(obj, do_unlink=True) + # Delete the converted copy using saved name + converted_name = scene.get('_comfy_converted_name', '') + if converted_name and converted_name in bpy.data.objects: + bpy.data.objects.remove(bpy.data.objects[converted_name], do_unlink=True) # Delete the bake camera bake_cam = scene.sna_bakecam @@ -412,11 +450,11 @@ class Comfy_OT_CleanupMvgen(bpy.types.Operator): bpy.data.objects.remove(bpy.data.objects['ObjectCamera'], do_unlink=True) # Restore original object visibility + orig_name = scene.sna_activeobject_name if orig_name and orig_name in bpy.data.objects: orig_obj = bpy.data.objects[orig_name] orig_obj.hide_render = False orig_obj.hide_set(False) - # Make it active and selected bpy.context.view_layer.objects.active = orig_obj orig_obj.select_set(True) @@ -428,6 +466,14 @@ class Comfy_OT_CleanupMvgen(bpy.types.Operator): pcoll = _get_preview_collection() pcoll.clear() + # Remove temp depth material + if '__ComfyDepthMat__' in bpy.data.materials: + bpy.data.materials.remove(bpy.data.materials['__ComfyDepthMat__']) + + # Clear converted name + if '_comfy_converted_name' in scene: + del scene['_comfy_converted_name'] + return {'FINISHED'}