diff --git a/__init__.py b/__init__.py index 572db8c..f60b64e 100644 --- a/__init__.py +++ b/__init__.py @@ -1,6 +1,6 @@ bl_info = { "name" : "ComfyProjector", - "author" : "Blender Procedural", + "author" : "小煜のChannel", "description" : "Projector for ComfyUI", "blender" : (5, 0, 0), "version" : (1, 0, 0), diff --git a/utils.py b/utils.py index 9d6cb57..be6ee4e 100644 --- a/utils.py +++ b/utils.py @@ -321,6 +321,145 @@ def _render_normal(operator): return _render_with_material(operator, normal_mat, 'normal.png', '法向') +def _create_white_material(): + """Create a flat white emission material for line art background.""" + mat_name = '__ComfyWhiteMat__' + if mat_name in bpy.data.materials: + return 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() + + emission = nodes.new('ShaderNodeEmission') + emission.location = (0, 0) + emission.inputs['Color'].default_value = (1, 1, 1, 1) + emission.inputs['Strength'].default_value = 1.0 + + output = nodes.new('ShaderNodeOutputMaterial') + output.location = (200, 0) + + links.new(emission.outputs['Emission'], output.inputs['Surface']) + return mat + + +def _render_lineart(operator): + """Render line art using Freestyle (black lines on white) for ControlNet Canny.""" + scene = bpy.context.scene + output_dir = _ensure_output_dir() + + converted_obj = _get_converted_obj(scene, operator) + if converted_obj is None: + return False + + # Save original state + vl = scene.view_layers["ViewLayer"] + orig_materials = [slot.material for slot in converted_obj.material_slots] + orig_use_freestyle = scene.render.use_freestyle + orig_vl_use_freestyle = vl.use_freestyle + orig_line_thickness = scene.render.line_thickness + orig_use_nodes = scene.use_nodes + 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 + orig_film_transparent = scene.render.film_transparent + orig_world = scene.world + + # Apply white emission material to all slots + white_mat = _create_white_material() + if len(converted_obj.material_slots) == 0: + converted_obj.data.materials.append(white_mat) + else: + for i in range(len(converted_obj.material_slots)): + converted_obj.material_slots[i].material = white_mat + + # Enable Freestyle + scene.render.use_freestyle = True + vl.use_freestyle = True + scene.render.line_thickness = 1.0 + + # Configure lineset: black lines, all relevant edge types + fs = vl.freestyle_settings + # Save original linesets state + orig_lineset_settings = [] + for ls in fs.linesets: + orig_lineset_settings.append({ + 'color': tuple(ls.linestyle.color), + 'thickness': ls.linestyle.thickness, + }) + + if len(fs.linesets) == 0: + ls = fs.linesets.new('LineSet') + else: + ls = fs.linesets[0] + + ls.linestyle.color = (0, 0, 0) # Black lines + ls.linestyle.thickness = 1.5 + ls.select_silhouette = True + ls.select_border = True + ls.select_crease = True + ls.select_edge_mark = True + + # White world background + white_world = bpy.data.worlds.new('__ComfyWhiteWorld__') + white_world.use_nodes = True + bg_node = white_world.node_tree.nodes.get('Background') + if bg_node: + bg_node.inputs['Color'].default_value = (1, 1, 1, 1) + bg_node.inputs['Strength'].default_value = 1.0 + scene.world = white_world + scene.render.film_transparent = False + + # Disable compositor + scene.use_nodes = False + scene.render.use_compositing = False + + # Setup output + out_path = os.path.join(output_dir, 'lineart.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 materials + 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 + + # Restore lineset settings + for i, ls_data in enumerate(orig_lineset_settings): + if i < len(fs.linesets): + fs.linesets[i].linestyle.color = ls_data['color'] + fs.linesets[i].linestyle.thickness = ls_data['thickness'] + + # Restore all settings + scene.render.use_freestyle = orig_use_freestyle + vl.use_freestyle = orig_vl_use_freestyle + scene.render.line_thickness = orig_line_thickness + 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 + scene.render.film_transparent = orig_film_transparent + scene.world = orig_world + + # Remove temporary world + if '__ComfyWhiteWorld__' in bpy.data.worlds: + bpy.data.worlds.remove(bpy.data.worlds['__ComfyWhiteWorld__']) + + _add_rendered_image(scene, '线稿', out_path) + return True + + class Comfy_OT_Mvgen_B6229(bpy.types.Operator): bl_idname = "comfyui.mvgen_b6229" bl_label = "生成多视角视图" @@ -384,6 +523,9 @@ class Comfy_OT_Mvgen_B6229(bpy.types.Operator): # --- Render Normal Map --- _render_normal(self) + # --- Render Line Art --- + _render_lineart(self) + return {"FINISHED"} def invoke(self, context, _event): @@ -466,9 +608,10 @@ 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__']) + # Remove temp materials + for mat_name in ('__ComfyDepthMat__', '__ComfyWhiteMat__'): + if mat_name in bpy.data.materials: + bpy.data.materials.remove(bpy.data.materials[mat_name]) # Clear converted name if '_comfy_converted_name' in scene: