下面的代码是干什么用的,请生成说明注释,同时还有什么改进:
【# -*- coding: utf-8 -*-
bl_info = {
"name": "1插件启动耗时分析器 (最终色彩校正版)",
"author": "提取、汉化与修正 by Gemini",
"version": (9, 1, 0),
"blender": (3, 0, 0),
"location": "编辑 > 偏好设置 > 插件",
"description": "【最终版】采用版本一的完整日志算法,确保数据完整性;结合版本二的稳定UI,并校正了UI色块颜色,使其鲜艳分明。",
"warning": "此插件会直接修改Blender的安装文件,属于高风险操作,请务必在理解其原理后再使用!",
"doc_url": "",
"category": "Development",
}
import bpy
import os
import sys
import tempfile
from bpy.props import StringProperty, FloatVectorProperty, CollectionProperty
from bpy.types import Operator, AddonPreferences, PropertyGroup
# ===================================================================================================
# 核心辅助函数 (无改动)
# ===================================================================================================
def find_addon_utils_py():
for path in sys.path:
candidate = os.path.join(path, "addon_utils.py")
if os.path.isfile(candidate): return candidate
try:
import bpy as _bpy
blender_scripts_path = os.path.join(os.path.dirname(_bpy.__file__), "scripts", "modules", "addon_utils.py")
if os.path.isfile(blender_scripts_path): return blender_scripts_path
except (ImportError, AttributeError): pass
return None
def already_injected(lines): return any("#startup_profiler_helper" in line for line in lines)
def get_version_specific_log_path():
try: config_dir = bpy.utils.user_resource('CONFIG'); return os.path.join(config_dir, "blender_startup_log.txt")
except: version_str = ".".join(map(str, bpy.app.version)); return os.path.join(tempfile.gettempdir(), f"blender_startup_log_{version_str}.txt")
# ===================================================================================================
# 核心注入算法 (采用版本一的完整、健壮逻辑,无改动)
# ===================================================================================================
class TIMEPROFILER_OT_InjectCode(Operator):
bl_idname = "time_profiler.inject_code";bl_label = "注入计时代码";bl_description = "【高风险操作】\n修改Blender核心文件 addon_utils.py。\n点击此按钮会先清空旧日志,然后注入代码以进行全新的记录"
def execute(self, context):
log_path = get_version_specific_log_path()
if os.path.exists(log_path):
try:
os.remove(log_path)
self.report({'INFO'}, f"已清空旧日志文件: {log_path}")
except OSError as e:
self.report({'WARNING'}, f"无法清空旧日志文件: {e}")
addon_utils_path = find_addon_utils_py()
if not addon_utils_path: self.report({'ERROR'}, "未能定位到核心文件 addon_utils.py!操作失败。"); return {'CANCELLED'}
try:
with open(addon_utils_path, "r", encoding="utf-8") as f: lines = f.readlines()
except Exception as e: self.report({'ERROR'}, f"读取核心文件失败: {e}"); return {'CANCELLED'}
if already_injected(lines): self.report({'INFO'}, "检测到计时代码已存在,无需重复注入。"); return {'CANCELLED'}
new_lines, injected, in_enable_func, return_inserted = [], False, False, False
safe_log_path = get_version_specific_log_path().replace('\\', '/')
for line in lines:
if not injected and line.strip().startswith("def enable("):
new_lines.extend([
line, " import time #startup_profiler_helper\n",
f" LOG_FILE_PATH = r'{safe_log_path}' #startup_profiler_helper\n",
" start_time = time.time()#startup_profiler_helper\n"])
injected, in_enable_func = True, True; continue
if in_enable_func and not return_inserted and line.strip().startswith("return mod"):
new_lines.extend([
" elapsed = time.time() - start_time#startup_profiler_helper\n",
" log_message = f\"{elapsed:.4f}s | {module_name}\\n\"#startup_profiler_helper\n",
" print(f\"[计时分析] 耗时: {log_message.strip()}\")#startup_profiler_helper\n",
" try:#startup_profiler_helper\n",
" with open(LOG_FILE_PATH, 'a', encoding='utf-8') as f:#startup_profiler_helper\n",
" f.write(log_message)#startup_profiler_helper\n",
" except Exception:#startup_profiler_helper\n", " pass#startup_profiler_helper\n",
line])
return_inserted, in_enable_func = True, False; continue
new_lines.append(line)
if not injected or not return_inserted: self.report({'ERROR'}, "未能在核心文件中找到合适的注入点,操作失败。"); return {'CANCELLED'}
try:
with open(addon_utils_path, "w", encoding="utf-8") as f: f.writelines(new_lines)
self.report({'INFO'}, f"成功注入计时代码到: {addon_utils_path}")
except PermissionError: self.report({'ERROR'}, "权限不足,无法写入核心文件。请尝试以管理员身份运行Blender。"); return {'CANCELLED'}
except Exception as e: self.report({'ERROR'}, f"写入核心文件时发生错误: {e}"); return {'CANCELLED'}
return {'FINISHED'}
class TIMEPROFILER_OT_RemoveCode(Operator):
bl_idname = "time_profiler.remove_code";bl_label = "移除计时代码";bl_description = "【恢复操作】\n从 addon_utils.py 中移除之前注入的计时代码"
def execute(self, context):
addon_utils_path = find_addon_utils_py()
if not addon_utils_path: self.report({'ERROR'}, "未能定位到核心文件 addon_utils.py!操作失败。"); return {'CANCELLED'}
try:
with open(addon_utils_path, "r", encoding="utf-8") as f: lines = f.readlines()
except Exception as e: self.report({'ERROR'}, f"读取核心文件失败: {e}"); return {'CANCELLED'}
if not already_injected(lines): self.report({'INFO'}, "未检测到已注入的计时代码,无需移除。"); return {'CANCELLED'}
new_lines = [line for line in lines if "#startup_profiler_helper" not in line]
try:
with open(addon_utils_path, "w", encoding="utf-8") as f: f.writelines(new_lines)
self.report({'INFO'}, f"已成功从 {addon_utils_path} 移除计时代码。")
except PermissionError: self.report({'ERROR'}, "权限不足,无法写入核心文件。请尝试以管理员身份运行Blender。"); return {'CANCELLED'}
except Exception as e: self.report({'ERROR'}, f"写入核心文件时发生错误: {e}"); return {'CANCELLED'}
return {'FINISHED'}
class TIMEPROFILER_OT_OpenLogFolder(Operator):
bl_idname = "time_profiler.open_log_folder";bl_label = "打开日志文件夹";bl_description = "在系统的文件浏览器中直接打开存放日志文件的文件夹"
def execute(self, context):
log_dir = os.path.dirname(get_version_specific_log_path())
if not os.path.isdir(log_dir):
try: os.makedirs(log_dir, exist_ok=True)
except OSError as e: self.report({'ERROR'}, f"无法创建目录: {e}"); return {'CANCELLED'}
bpy.ops.wm.path_open(filepath=log_dir); return {'FINISHED'}
# ===================================================================================================
# 报告获取与显示
# ===================================================================================================
class TIMEPROFILER_OT_FetchReport(Operator):
bl_idname = "time_profiler.fetch_report";bl_label = "加载/刷新报告";bl_description = "读取累积的日志文件,并显示去重后的分析报告"
def execute(self, context):
prefs = context.preferences.addons[__name__].preferences
log_path = get_version_specific_log_path()
if not os.path.exists(log_path):
prefs.report_items.clear()
prefs.header_str = "未找到日志文件。请先注入代码并重启Blender以生成报告。"
self.report({'WARNING'}, prefs.header_str)
return {'CANCELLED'}
try:
with open(log_path, 'r', encoding='utf-8') as f: lines = f.readlines()
unique_addons = {}
for line in lines:
if not line.strip() or '|' not in line: continue
try:
time_part, name_part = [x.strip() for x in line.strip().split('|', 1)]
elapsed_time = float(time_part.rstrip('s'))
if name_part not in unique_addons or elapsed_time > unique_addons[name_part][0]:
unique_addons[name_part] = (elapsed_time, time_part)
except (ValueError, IndexError): continue
if not unique_addons: raise ValueError("日志文件为空或格式不正确。")
processed_list = [(data[0], data[1], name) for name, data in unique_addons.items()]
processed_list.sort(key=lambda x: x[0], reverse=True)
version_str = ".".join(map(str, bpy.app.version))
prefs.header_str = f"Blender {version_str} 启动耗时报告 (共 {len(processed_list)} 个插件)"
prefs.report_items.clear()
for elapsed_time, time_str, name_str in processed_list:
item = prefs.report_items.add()
item.time_str = time_str
item.name_str = name_str
# ★★★ 核心修正:使用纯正、鲜艳的RGB值 ★★★
color_val = (0.7, 0.7, 0.7) # 默认 - 中灰色
if elapsed_time >= 3.0: color_val = (1.0, 0.0, 1.0) # 紫色/洋红
elif elapsed_time >= 1.0: color_val = (1.0, 0.0, 0.0) # 红色
elif elapsed_time >= 0.5: color_val = (1.0, 1.0, 0.0) # 黄色
elif elapsed_time >= 0.2: color_val = (0.0, 0.7, 1.0) # 蓝色/青色
elif elapsed_time >= 0.05: color_val = (0.0, 1.0, 0.0) # 绿色
item.color = color_val
except Exception as e:
prefs.report_items.clear()
prefs.header_str = f"读取或解析日志时出错: {e}"
self.report({'ERROR'}, prefs.header_str)
return {'CANCELLED'}
return {'FINISHED'}
class TIMEPROFILER_ReportItem(PropertyGroup):
time_str: StringProperty()
name_str: StringProperty()
color: FloatVectorProperty(subtype='COLOR', size=3, min=0.0, max=1.0, default=(0.7, 0.7, 0.7))
class TimeProfilerAddonPreferences(AddonPreferences):
bl_idname = __name__
report_items: CollectionProperty(type=TIMEPROFILER_ReportItem)
header_str: StringProperty(default="请点击“加载/刷新报告”按钮来查看。")
def draw(self, context):
layout = self.layout
box = layout.box(); row = box.row(); row.label(text="第一步:管理计时代码", icon='MODIFIER')
alert_box = box.box(); row = alert_box.row(); row.alert = True
row.label(text="警告:此操作会修改Blender安装文件,请谨慎使用!", icon='ERROR')
row = box.row(align=True); row.scale_y = 1.5
row.operator("time_profiler.inject_code", icon='MOD_TIME')
row.operator("time_profiler.remove_code", icon='X')
box2 = layout.box(); row = box2.row(align=True)
row.label(text="第二步:分析报告", icon='TEXT')
row.operator("time_profiler.open_log_folder", icon='FILE_FOLDER', text="")
row.operator("time_profiler.fetch_report", icon='FILE_REFRESH', text="")
report_box = box2.box(); report_box.label(text=self.header_str)
if self.report_items:
report_box.separator()
main_col = report_box.column(align=True)
for item in self.report_items:
row = main_col.row(align=True)
split = row.split(factor=0.25, align=True)
left_part = split.row(align=True)
left_part.prop(item, "color", text="")
left_part.label(text=item.time_str)
split.label(text=item.name_str)
# ===================================================================================================
# 注册与注销
# ===================================================================================================
classes = (
TIMEPROFILER_ReportItem, TIMEPROFILER_OT_InjectCode, TIMEPROFILER_OT_RemoveCode,
TIMEPROFILER_OT_OpenLogFolder, TIMEPROFILER_OT_FetchReport, TimeProfilerAddonPreferences,
)
def register():
for cls in classes: bpy.utils.register_class(cls)
def unregister():
for cls in reversed(classes): bpy.utils.unregister_class(cls)
if __name__ == "__main__":
register()】
最新发布