docs: 为全部13个源码模块和5个测试文件添加简体中文注释
- 每个文件补充模块级 docstring、类/函数 docstring、关键逻辑行内注释 - 注释风格:"中文说明" 或 # 中文说明 - 代码结构和逻辑完全不变
This commit is contained in:
@@ -1,3 +1,9 @@
|
||||
"""输出规划与文件命名模块。
|
||||
|
||||
提供换板打包任务的输出路径解析、文件命名规则、摘要汇总等功能。
|
||||
支持基于模板的输出文件名生成以及同一批次内文件路径去重。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
@@ -9,13 +15,24 @@ from typing import Callable, Mapping, Sequence
|
||||
from .metadata import read_3mf_summary
|
||||
from .models import PlateJob, ThreeMfSummary
|
||||
|
||||
# 默认输出文件名模板,{plates} 为总盘数,{sources} 为来源标识
|
||||
DEFAULT_OUTPUT_PATTERN = "{plates} Plates - {sources}.3mf"
|
||||
|
||||
# 摘要解析器类型别名,接受 Path 返回 ThreeMfSummary
|
||||
SummaryResolver = Callable[[Path], ThreeMfSummary]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class OutputSummary:
|
||||
"""输出摘要数据类,汇总一次换板打包的整体统计信息。
|
||||
|
||||
属性:
|
||||
plate_count: 总盘数(含份数倍增)。
|
||||
copy_count: 总份数。
|
||||
prediction_seconds: 预估总耗时(秒),不可用时为 None。
|
||||
weight_grams: 总重量(克),不可用时为 None。
|
||||
filament_used_m: 耗材总长度(米),不可用时为 None。
|
||||
"""
|
||||
plate_count: int
|
||||
copy_count: int
|
||||
prediction_seconds: float | None = None
|
||||
@@ -25,16 +42,37 @@ class OutputSummary:
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class OutputNamingOptions:
|
||||
"""输出命名选项,控制生成文件的目录和命名规则。
|
||||
|
||||
属性:
|
||||
output_directory: 输出目录路径,None 表示使用第一个输入文件所在目录。
|
||||
filename_rule: 输出文件名模板字符串。
|
||||
"""
|
||||
output_directory: Path | str | None = None
|
||||
filename_rule: str = DEFAULT_OUTPUT_PATTERN
|
||||
|
||||
|
||||
class SafeFormatDict(dict):
|
||||
"""安全的格式化字典。
|
||||
|
||||
当 str.format_map 请求不存在的键时,返回原始占位符而非抛出 KeyError,
|
||||
保证模板中未提供的占位符被原样保留在输出中。
|
||||
"""
|
||||
def __missing__(self, key: str) -> str:
|
||||
return "{" + key + "}"
|
||||
|
||||
|
||||
def three_mf_summary_from_mapping(data: Mapping[str, object]) -> ThreeMfSummary:
|
||||
"""从类字典的映射对象构建 ThreeMfSummary。
|
||||
|
||||
用于将外部传入的配置数据(如 GUI 表格行)转换为标准摘要对象。
|
||||
|
||||
参数:
|
||||
data: 包含摘要字段的映射对象(键名与 ThreeMfSummary 字段名对应)。
|
||||
|
||||
返回:
|
||||
ThreeMfSummary: 解析后的摘要对象,缺失字段使用默认值。
|
||||
"""
|
||||
source_value = data.get("source_3mf")
|
||||
return ThreeMfSummary(
|
||||
source_3mf=Path(str(source_value)) if source_value is not None else Path(),
|
||||
@@ -50,6 +88,18 @@ def summarize_jobs_for_output(
|
||||
jobs: Sequence[PlateJob],
|
||||
summary_resolver: SummaryResolver = read_3mf_summary,
|
||||
) -> OutputSummary:
|
||||
"""将一组换板作业汇总为 OutputSummary。
|
||||
|
||||
分别计算总盘数、总份数,以及各项可选的累计统计值。
|
||||
支持自定义的摘要解析器,默认为 read_3mf_summary。
|
||||
|
||||
参数:
|
||||
jobs: PlateJob 序列。
|
||||
summary_resolver: 用于获取单个 3MF 摘要的可调用对象。
|
||||
|
||||
返回:
|
||||
OutputSummary: 汇总后的输出摘要。
|
||||
"""
|
||||
plate_count = 0
|
||||
copy_count = 0
|
||||
prediction_total = 0.0
|
||||
@@ -72,6 +122,7 @@ def summarize_jobs_for_output(
|
||||
if summary.filament_used_m is not None:
|
||||
used_m_total += summary.filament_used_m * copies
|
||||
used_m_found = True
|
||||
# 仅当至少有一个有效值时才会设置对应字段
|
||||
return OutputSummary(
|
||||
plate_count=plate_count,
|
||||
copy_count=copy_count,
|
||||
@@ -82,7 +133,20 @@ def summarize_jobs_for_output(
|
||||
|
||||
|
||||
def sanitize_filename(file_name: str) -> str:
|
||||
"""清理文件名中的非法字符,确保生成合法的文件路径。
|
||||
|
||||
将 Windows/Unix 中不允许出现在文件名中的字符替换为下划线,
|
||||
并去掉末尾的点和空格。
|
||||
|
||||
参数:
|
||||
file_name: 原始文件名。
|
||||
|
||||
返回:
|
||||
str: 清理后的安全文件名,至少返回 "packed.3mf"。
|
||||
"""
|
||||
# 将非法字符替换为下划线
|
||||
sanitized = re.sub(r"[\\/:*?\"<>|]+", "_", file_name).strip()
|
||||
# 去除末尾的点号和空格(Windows 禁止)
|
||||
sanitized = sanitized.rstrip(". ")
|
||||
return sanitized or "packed.3mf"
|
||||
|
||||
@@ -93,15 +157,35 @@ def resolve_output_path(
|
||||
summary_resolver: SummaryResolver = read_3mf_summary,
|
||||
now: datetime | None = None,
|
||||
) -> Path:
|
||||
"""根据作业列表和命名选项解析最终输出文件路径。
|
||||
|
||||
使用模板变量(source, sources, plates, copies, date, time)替换
|
||||
filename_rule 中的占位符,生成输出文件名。
|
||||
|
||||
参数:
|
||||
jobs: PlateJob 序列。
|
||||
naming: 输出命名选项。
|
||||
summary_resolver: 摘要解析器,默认 read_3mf_summary。
|
||||
now: 当前时间,None 时使用系统当前时间。
|
||||
|
||||
返回:
|
||||
Path: 解析后的输出文件完整路径。
|
||||
|
||||
抛出:
|
||||
ValueError: 若 jobs 为空。
|
||||
"""
|
||||
if not jobs:
|
||||
raise ValueError("No input 3MF file was provided.")
|
||||
summary = summarize_jobs_for_output(jobs, summary_resolver)
|
||||
first_input = jobs[0].source_3mf
|
||||
# 收集所有输入文件的主文件名(不含扩展名)
|
||||
stems = [job.source_3mf.stem for job in jobs]
|
||||
unique_stems = list(dict.fromkeys(stems))
|
||||
unique_stems = list(dict.fromkeys(stems)) # 保持顺序的去重
|
||||
source_token = first_input.stem
|
||||
# 当有多个不同来源时,生成 "xxx_and_N_more" 格式的标记
|
||||
sources_token = source_token if len(unique_stems) == 1 else f"{source_token}_and_{len(unique_stems) - 1}_more"
|
||||
timestamp = now or datetime.now()
|
||||
# 构建模板变量字典
|
||||
tokens = SafeFormatDict(
|
||||
source=source_token,
|
||||
sources=sources_token,
|
||||
@@ -112,6 +196,7 @@ def resolve_output_path(
|
||||
)
|
||||
pattern = naming.filename_rule.strip() or DEFAULT_OUTPUT_PATTERN
|
||||
file_name = sanitize_filename(pattern.format_map(tokens))
|
||||
# 确保文件扩展名为 .3mf
|
||||
if not file_name.lower().endswith(".3mf"):
|
||||
file_name += ".3mf"
|
||||
output_dir_text = "" if naming.output_directory is None else str(naming.output_directory).strip()
|
||||
@@ -120,6 +205,17 @@ def resolve_output_path(
|
||||
|
||||
|
||||
def make_unique_for_run(path: Path, used_paths: set[Path]) -> Path:
|
||||
"""为单次运行确保输出路径唯一,通过追加递增序号避免冲突。
|
||||
|
||||
若目标路径已被使用,则在文件名后添加 _2、_3 等后缀直至唯一。
|
||||
|
||||
参数:
|
||||
path: 期望的输出路径。
|
||||
used_paths: 已使用路径的集合(会原地修改)。
|
||||
|
||||
返回:
|
||||
Path: 去重后的唯一路径。
|
||||
"""
|
||||
resolved_key = path.resolve(strict=False)
|
||||
if resolved_key not in used_paths:
|
||||
used_paths.add(resolved_key)
|
||||
@@ -128,6 +224,7 @@ def make_unique_for_run(path: Path, used_paths: set[Path]) -> Path:
|
||||
suffix = path.suffix
|
||||
parent = path.parent
|
||||
index = 2
|
||||
# 递增序号直到找到未使用的路径
|
||||
while True:
|
||||
candidate = parent / f"{stem}_{index}{suffix}"
|
||||
candidate_key = candidate.resolve(strict=False)
|
||||
@@ -138,6 +235,14 @@ def make_unique_for_run(path: Path, used_paths: set[Path]) -> Path:
|
||||
|
||||
|
||||
def _optional_float(value: object) -> float | None:
|
||||
"""安全地将任意对象转换为浮点数,失败时返回 None。
|
||||
|
||||
参数:
|
||||
value: 待转换的任意对象。
|
||||
|
||||
返回:
|
||||
float | None: 转换成功返回浮点数,否则返回 None。
|
||||
"""
|
||||
if value is None:
|
||||
return None
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user