在CAD图纸中，经常需要统计不同文字内容的出现次数，比如房间名称、设备编号、材料标注等。当文字种类达到十几种甚至几十种时，手动计数或重复使用“快速选择”会非常低效。本文将介绍几种实用的统计方法，并重点给出一种能够在图纸内自动生成统计表格的完整解决方案。 一、常见统计方法对比 方法 核心操作 适用场景 是否支持一键分类 TEXTCOUNT / TC 直接输入命令，全选文字 只需文字总数 否 查找和替换 (FIND) 输入具体文字串，查看匹配数 统计某一种特定文字 否（每次一种） 快速选择 (QSELECT) 按“内容”特性筛选指定文字 文字种类较少时 否（需重复操作） 数据提取 + Excel 将文字内容导出为CSV/XLS，用透视表统计 任意数量种类，一次性导出 是 专用LISP插件 运行程序，自动生成表格 频繁需要统计并出表 是 当文字种类达到16种以上时，数据提取法和LISP插件法是最佳选择。 二、方法一：数据提取 + Excel（无需插件） 该方法利用AutoCAD自带的“数据提取”功能，将所有文字内容导出到外部表格，再借助Excel的数据透视表完成分类计数。 操作步骤 启动数据提取 命令行输入 DATAEXTRACTION（或 DX），回车。 创建新提取 选择“创建新提取”，为.dxe文件命名并保存。 选择对象 选择“在当前图形中选择对象” 点击“选择对象”，框选整个图形或按Ctrl+A全选 确认后进入下一步。 选择对象类型 在对象类型列表中只勾选“文字”和“多行文字”。（若需统计块内文字或属性文字，可额外勾选相应类型） 选择特性 只勾选“内容”（TextString / Content），其余特性（如图层、位置）可不选，以保持数据纯净。 优化输出 推荐不合并相同行，保留每条原始记录，以便后续在Excel中灵活透视。 输出到外部文件 选择输出格式为XLS或CSV，指定保存路径，完成导出。 Excel中统计 打开导出的文件，选中“内容”列。 插入“数据透视表”，将“内容”拖入“行”区域和“值”区域（计数）。 立即得到所有不同文字及其出现次数，无论多少种，整个过程只需几分钟。 三、方法二：专用LISP插件（生成图纸内表格） 如果需要直接在CAD图形中生成表格（TABLE对象），并且希望以后反复快速运行，可以加载以下AutoLISP程序。该程序会： 让用户框选（或全选）文字对象 统计每种文字内容的出现次数 按内容升序排序 在当前图形中创建一个两列表格：“文字内容”和“数量” 完整LISP代码 将以下代码保存为 TextCountTable.lsp（编码ANSI）： ;;; ============================================================ ;;; 文字内容统计并生成 AutoCAD 表格 (TABLE) ;;; 命令: TCT ;;; 功能: 统计所选文字对象(单行/多行)的内容，生成统计表格 ;;; ============================================================ (defun c:TCT (/ ss i ent txt lst pair item pt rowCnt tblObj colWidth1 colWidth2 rowHeight tblHeight) (princ "\n请选择需要统计的文字对象 (可框选或直接回车选择全部): ") ;; 选择文字对象（支持单行文字、多行文字） (setq ss (ssget '((0 . "*TEXT")))) (if (null ss) (progn (alert "没有选中任何文字对象，程序退出。") (exit) ) ) ;; 统计文字内容 (setq i 0) (while (< i (sslength ss)) (setq ent (entget (ssname ss i)) txt (cdr (assoc 1 ent)) ) ;; 忽略空字符串 (if (and txt (/= txt "")) (if (setq pair (assoc txt lst)) (setq lst (subst (cons txt (1+ (cdr pair))) pair lst)) (setq lst (cons (cons txt 1) lst)) ) ) (setq i (1+ i)) ) ;; 检查统计结果 (if (null lst) (progn (alert "所选文字中没有有效内容。") (exit) ) ) ;; 按文字内容排序 (升序) (setq lst (vl-sort lst '(lambda (a b) (< (car a) (car b))))) ;; 显示简要统计信息 (princ (strcat "\n共统计 " (itoa (sslength ss)) " 个文字对象，" (itoa (length lst)) " 种不同内容。\n")) ;; 准备表格参数 (setq rowCnt (1+ (length lst)) ; 总行数 = 表头 + 数据行 colWidth1 30.0 ; 第一列宽度 (内容列) colWidth2 15.0 ; 第二列宽度 (数量列) rowHeight 8.0 ; 行高 tblHeight (* rowHeight rowCnt) ) ;; 获取插入点（表格左上角） (initget 1) ; 不允许空输入 (setq pt (getpoint "\n请指定表格左上角插入点: ")) ;; 创建 AutoCAD 表格对象 (ActiveX) (setq acadObj (vlax-get-acad-object) doc (vla-get-ActiveDocument acadObj) mSpace (vla-get-ModelSpace doc) ) ;; 创建表格: (vla-addtable 空间 插入点 行数 列数 行高 列宽) (setq tblObj (vla-addtable mSpace (vlax-3d-point pt) rowCnt 2 rowHeight colWidth1)) ;; 设置第二列宽度 (vla-SetColumnWidth tblObj 1 colWidth2) ;; 设置表头样式 (加粗、居中) (vla-SetTextHeight tblObj 1 5.0) ; 表头文字高度 (vla-SetTextHeight tblObj 2 4.0) ; 数据行文字高度 (vla-SetText tblObj 0 0 "文字内容") (vla-SetText tblObj 0 1 "数量") ;; 填充数据行 (setq i 0) ; 数据行索引从1开始 (0是表头) (foreach item lst (setq content (car item) count (cdr item) rowIdx (1+ i) ) (vla-SetText tblObj rowIdx 0 content) (vla-SetText tblObj rowIdx 1 (itoa count)) (setq i (1+ i)) ) (vla-Update tblObj) (princ "\n✅ 统计表格已生成。") (princ) ) (princ "\n文字统计表格程序已加载，请输入 TCT 运行。") (princ) 加载与运行 保存文件：将上述代码保存为 TextCountTable.lsp。 加载程序：在CAD命令行输入 AP（或 APPLOAD），找到并加载该文件。 执行命令：输入 TCT 并按回车。 操作提示： 框选需要统计的文字对象（也可直接按回车选择全部对象）。 在图纸中点击一个位置作为表格左上角插入点。 结果：CAD自动生成一个表格，列出每种文字内容及其数量，按内容升序排列。 表格效果示例 文字内容 数量 卧室 12 客厅 8 卫生间 6 厨房 5 ... ... 参数自定义 列宽调整：修改代码中的 colWidth1（内容列）和 colWidth2（数量列），例如 30.0 和 15.0。 行高调整：修改 rowHeight。 排序方式：若想按数量降序排列，将排序语句替换为： (setq lst (vl-sort lst '(lambda (a b) (> (cdr a) (cdr b))))) 全图自动统计（无需手动选择）：将选择语句改为： (setq ss (ssget "_X" '((0 . "*TEXT")))) 四、两种方案的选择建议 需求场景 推荐方案 一次性统计，希望导出数据到Excel做进一步分析 数据提取 + Excel 经常需要统计文字数量，且希望结果以表格形式保留在图纸中 LISP插件（TCT命令） 图纸中包含大量块内文字或属性文字 数据提取法更灵活（可勾选多种对象类型） 追求最快操作、无需安装任何外部文件 数据提取法（原生功能） 五、总结 当文字种类达到16种以上时，避免使用重复的“快速选择”或手动计数。 AutoCAD自带的数据提取功能可以一次性将所有文字内容导出到Excel，配合透视表快速分类统计。 若希望在图纸内部直接生成统计表格，加载提供的LISP程序，输入TCT命令，即可一键完成统计与制表。 两种方法均无需额外付费插件，适用于AutoCAD 2010及以上版本。 通过上述方法，设计师可以轻松应对大量文字标注的统计工作，提升图纸信息管理的效率。
火山引擎全站加速证书自动更新脚本实践
背景 火山云全站加速配置SSL证书，手动上传并绑定证书的过程虽然简单，但多域名重复操作既繁琐又容易出错。于是决定写一个自动化脚本，利用火山引擎 CLI 工具（ve）完成证书上传与绑定的全流程。 工具选型与思路 火山引擎提供了统一的命令行工具 ve，支持 CDN、DCDN 等多个服务。一开始查看了 DCDN 相关的命令： ve dcdn CreateCertBind --help 发现 CreateCertBind 需要一个已存在于证书中心的 CertId，而不能直接传递证书文件内容。这意味着必须先将证书上传到火山引擎的证书中心，拿到 CertId 后再绑定到 DCDN 域名。 庆幸的是，ve cdn AddCertificate 命令可以将证书上传至火山引擎证书中心（设置 Source 为 volc_cert_center），并且返回的 CertId 可以在 DCDN 中直接使用。这样一来，流程就清晰了： 读取本地证书文件（fullchain.pem 和 privkey.pem） 调用 AddCertificate 上传，获取 CertId（若证书已存在则复用） 调用 CreateCertBind 将证书绑定到 DCDN 域名 遇到的两个坑 1. 通配符域名不被 DCDN 接受 一开始脚本中的 DomainNames 写成了 ["example.com", "*.example.com"]，执行后报错： DomainNotExist: Domain Name [*.example.com] does not exist 通过 ve dcdn ListDomainConfig 查看实际已添加的加速域名，发现 DCDN 控制台并不接受 *.example.com 这样的通配符形式。实际存在的域名是 example.com、sub.example.com 以及一个以点开头的特殊域名 .example.com（可能是历史原因或内部标识）。因此最终使用的域名列表需要与实际配置完全一致。 2. 错误响应的解析路径不一致 ve cdn AddCertificate 的错误信息位于 .ResponseMetadata.Error，而 ve dcdn CreateCertBind 的错误信息却直接在根节点 .Error 中。最初脚本只检查了 .ResponseMetadata.Error，导致 CreateCertBind 明明失败了，脚本却输出“绑定成功”。修正后同时检查两个可能的路径。 最终脚本 以下脚本实现了自动上传证书并绑定到 DCDN 域名。使用时请根据实际情况修改证书存放目录、日志目录以及目标域名列表。 #!/bin/bash #set -xv log_date() { date '+[%Y/%m/%d %H:%M:%S]' } # 配置 - 请按实际修改 CERT_STORE_DIR="/path/to/your/certificates" LOG_DIR="/path/to/log" mkdir -p "$LOG_DIR" exec > >(tee -a "$LOG_DIR/cert.log") 2>&1 echo "$(log_date) 脚本开始执行" # 加载证书内容 cert_content=$(cat "$CERT_STORE_DIR/fullchain.pem") key_content=$(cat "$CERT_STORE_DIR/privkey.pem") echo "$(log_date) 证书内容长度: ${#cert_content}" echo "$(log_date) 私钥内容长度: ${#key_content}" # 1. 上传证书到火山引擎证书中心 upload_json_request() { jq -n \ --arg cert "$cert_content" \ --arg key "$key_content" \ '{ "Source": "volc_cert_center", "CertType": "server_cert", "Certificate": $cert, "PrivateKey": $key }' } echo "$(log_date) 正在上传证书到火山引擎证书中心..." response=$(ve cdn AddCertificate --body "$(upload_json_request)") if echo "$response" | jq -e '.ResponseMetadata.Error' > /dev/null 2>&1; then error_code=$(echo "$response" | jq -r '.ResponseMetadata.Error.Code') if [ "$error_code" = "InvalidParameter.Certificate.Duplicated" ]; then # 证书已存在时从错误消息中提取已有证书ID cert_id=$(echo "$response" | jq -r '.ResponseMetadata.Error.Message | capture("ID为 (?<id>[^。]+)。") | .id') echo "$(log_date) 证书已存在，使用已有证书ID: $cert_id" else error_msg=$(echo "$response" | jq -r '.ResponseMetadata.Error.Message') echo "$(log_date) 上传失败: $error_msg" exit 1 fi else request_id=$(echo "$response" | jq -r '.ResponseMetadata.RequestId') cert_id=$(echo "$response" | jq -r '.Result.CertId') echo "$(log_date) 上传成功，RequestId: $request_id" echo "$(log_date) 证书ID: $cert_id" fi # 2. 绑定证书到 DCDN 域名（按实际域名列表修改） deploy_json_request() { jq -n \ --arg cert_id "$cert_id" \ '{ "CertSource": "volc", "CertId": $cert_id, "DomainNames": ["example.com", "sub.example.com", ".example.com"] }' } echo "$(log_date) 正在将证书绑定到全站加速域名..." response=$(ve dcdn CreateCertBind --body "$(deploy_json_request)") # 增强错误检测：同时检查根级 .Error 和 .ResponseMetadata.Error if echo "$response" | jq -e '.Error' > /dev/null 2>&1 || echo "$response" | jq -e '.ResponseMetadata.Error' > /dev/null 2>&1; then error_msg=$(echo "$response" | jq -r '.Error.Message // .ResponseMetadata.Error.Message') echo "$(log_date) 绑定失败: $error_msg" exit 1 else request_id=$(echo "$response" | jq -r '.ResponseMetadata.RequestId // "unknown"') echo "$(log_date) 绑定成功，RequestId: $request_id" exit 0 fi 执行效果 脚本执行时会输出详细日志： [2026/05/19 14:38:06] 脚本开始执行 [2026/05/19 14:38:06] 证书内容长度: 2864 [2026/05/19 14:38:06] 私钥内容长度: 226 [2026/05/19 14:38:06] 正在上传证书到火山引擎证书中心... [2026/05/19 14:38:11] 上传成功，RequestId: xxx [2026/05/19 14:38:11] 证书ID: cert-xxxx [2026/05/19 14:38:11] 正在将证书绑定到全站加速域名... [2026/05/19 14:38:15] 绑定成功，RequestId: yyy 如果证书已经存在，会直接复用原有证书 ID，避免重复上传。 总结 通过结合 ve cdn AddCertificate 和 ve dcdn CreateCertBind，可以高效地完成火山引擎全站加速的证书自动更新。关键点在于： 证书中心是统一的，CDN 上传的证书可供 DCDN 使用 DCDN 的 DomainNames 必须精确匹配控制台中已添加的加速域名（不支持通配符形式的域名） 不同命令的错误响应结构可能不同，需要编写健壮的解析逻辑 上述脚本配合1Panel执行，即可在证书更新后自动部署，告别手动操作的烦恼。
批量改变的矩形上下方向的高度（竖向长度），用一条水平线作为目标，将多个矩形的一侧（上边或下边）拉伸到那条线上，同时保持矩形的宽度和另一侧边位置不变。 一个一键批量拉伸矩形高度的 LISP 程序，支持选择水平参考线，并将矩形上边（或下边）对齐到该线。 ✅ 以线为界，批量调整矩形高度（竖向拉伸） 程序功能 选择一条水平直线（程序自动获取其 Y 坐标）。 选择多个矩形（轴对齐矩形，边平行于坐标轴）。 选择拉伸模式： 上边对齐到线（底部固定，顶部拉伸到参考线） 下边对齐到线（顶部固定，底部拉伸到参考线） 矩形宽度和另一侧位置完全不变。 代码（直接粘贴到 CAD 命令行） (defun C:StretchHeightToLine ( / lineEnt lineY ss i ent pts minX maxX minY maxY newMinY newMaxY mode newPts newEnt) (setq lineEnt (car (entsel "\n选择水平参考线（直线）："))) (if (not lineEnt) (progn (prompt "\n未选择直线") (exit))) (setq lineY (cadr (vlax-curve-getStartPoint lineEnt))) ; 取直线的 Y 坐标 (setq ss (ssget '((0 . "LWPOLYLINE") (70 . 1)))) ; 只选闭合多段线 (if (not ss) (progn (prompt "\n未选择任何矩形") (exit))) (initget "Top Bottom") (setq mode (getkword "\n对齐方式 [上边对齐到线(Top)/下边对齐到线(Bottom)]: ")) (if (not mode) (exit)) (setq i 0) (repeat (sslength ss) (setq ent (ssname ss i)) (setq pts (mapcar 'cdr (vl-remove-if '(lambda (x) (/= (car x) 10)) (entget ent)))) (if (>= (length pts) 4) (progn (setq minX (apply 'min (mapcar 'car pts))) (setq maxX (apply 'max (mapcar 'car pts))) (setq minY (apply 'min (mapcar 'cadr pts))) (setq maxY (apply 'max (mapcar 'cadr pts))) (cond ((= mode "Top") ;; 顶部对齐到线：底边不变，顶边改为 lineY (setq newMinY minY) (setq newMaxY lineY) ) ((= mode "Bottom") ;; 底部对齐到线：顶边不变，底边改为 lineY (setq newMinY lineY) (setq newMaxY maxY) ) ) ;; 构建新顶点（左下 -> 右下 -> 右上 -> 左上） (setq newPts (list (list minX newMinY) (list maxX newMinY) (list maxX newMaxY) (list minX newMaxY) )) ;; 更新多段线 (setq newEnt (entget ent)) (setq newEnt (vl-remove-if '(lambda (x) (= (car x) 10)) newEnt)) (foreach p newPts (setq newEnt (append newEnt (list (cons 10 p)))) ) (entmod newEnt) ) ) (setq i (1+ i)) ) (prompt (strcat "\n已修改 " (itoa i) " 个矩形，参考线 Y = " (rtos lineY 2 2))) (princ) ) 操作步骤 加载代码：在 CAD 命令行粘贴全部代码，按回车。 如果提示“函数已加载”，则可直接使用。 如果粘贴不成功（比如多行提示），请先输入 VLIDE 打开编辑器，新建文件粘贴，保存为 .lsp 文件，再用 APPLOAD 加载。 执行命令：输入 StretchHeightToLine 回车。 选择参考线：单击一条水平直线（多段线的直线段也可，程序会自动取它的 Y 坐标）。 选择矩形：框选所有要修改的矩形，按回车。 选择对齐方式： 输入 T 选择「上边对齐到线」 → 矩形底部固定，顶部拉伸到参考线。 输入 B 选择「下边对齐到线」 → 矩形顶部固定，底部拉伸到参考线。 程序自动完成所有矩形的竖向拉伸。 🔁 相反方向：固定顶边，底部拉伸？固定底边，顶部拉伸？ 程序已经通过模式选择实现了两种需求： Top 模式：底部固定，顶部移动（改变高度） Bottom 模式：顶部固定，底部移动（改变高度） 完全满足“一条线作为目标，矩形上下长度拉伸过去”。 📐 特殊情况：如果参考线位于矩形中间（压缩或拉伸） 程序不判断参考线相对于矩形的位置。如果参考线 Y 坐标位于矩形原有高度范围之内： Top 模式：新高度变小（顶部下移）→ 压缩矩形。 Bottom 模式：新高度变小（底部上移）→ 压缩矩形。 如果需要反向拉伸（比如从中间往外拉），请手动移动参考线或使用其他命令。
市面上的任务管理工具，但凡带个日历视图、数据导出功能，不是按月收费就是按年订阅。自己动手，用 Microsoft Graph API 一小时就能搭一个专属的 To Do 看板——月视图、周视图、CSV 导出，全部免费，数据还留在自己手里。 为什么做这个？ Microsoft To Do 本身非常轻量，但缺少一个直观的月视图/周视图。你只能按列表查看任务，无法一眼看出哪天有哪些事、哪些已经完成。更别说导出数据做周报、月报了。 市面上的“日历+任务”类应用，像 Todoist、TickTick 的高级版确实提供日历视图，但都放在了付费墙后面。对于个人开发者或者小团队，为了一个视图功能去订阅并不划算。 于是我花了一个晚上，基于 Microsoft Graph API 和纯前端技术，写了一个完全免费的 To Do 看板。它支持： 月视图（周一为起始，符合国内习惯） 周视图（显示 ISO 周数，每天的任务完成比例） 一键导出 CSV（按时间排序，包含创建时间、完成时间、备注等字段） 切换账号、实时统计（全部任务/已完成数量） 而且个人 Microsoft 账号（outlook.com、hotmail.com）和 E5 订阅账号都可以直接使用。 技术选型 需求 技术方案 登录与授权 MSAL.js (Microsoft Authentication Library) 获取任务数据 Microsoft Graph API (/me/todo/lists 和 /tasks) 日历渲染 手写 HTML/CSS/JS（轻量，不依赖第三方库） 数据导出 动态生成 CSV 文件，浏览器下载 性能优化 并行请求 + 限流控制 之所以不直接用 FullCalendar 等现成库，是为了完全控制 UI 样式，并且避免额外的依赖加载。 核心功能实现 1. 登录与权限 使用 MSAL.js 实现 OAuth 2.0 授权码流（PKCE）。只需要在 Azure AD 中注册一个应用，配置重定向 URI 为 SPA 类型，然后请求 Tasks.Read 和 User.Read 权限即可。 const msalConfig = { auth: { clientId: '你的客户端ID', authority: 'https://login.microsoftonline.com/common', redirectUri: window.location.origin } }; const loginRequest = { scopes: ['Tasks.Read', 'User.Read'] }; 个人账号注意：Azure 应用注册时，必须选择“任何组织目录中的帐户和个人 Microsoft 帐户”，否则个人账号无法登录。 2. 获取所有任务 Microsoft To Do 的数据模型是：用户有多个任务列表，每个列表下有多个任务。所以需要两步： 请求 GET /me/todo/lists 获取所有列表。 对每个列表请求 GET /me/todo/lists/{id}/tasks?$top=500 获取其中的任务。 性能瓶颈：如果列表较多（比如十几个），串行请求会非常慢（每个请求约 200~300ms，总耗时 = 列表数 × 单次耗时）。例如 16 个列表，串行需要 5 秒左右。 3. 并行请求 + 限流处理 为了提速，我们使用 Promise.all 并发请求，但并发量过大会触发 Microsoft Graph 的限流（HTTP 429）。解决方案是限制并发数，同时加入简单的重试机制。 // 限制同时最多 3 个请求 async function asyncPool(poolLimit, array, iteratorFn) { // 实现略 } const fetchListTasks = async (list) => { // 遇到 429 则等待 Retry-After 秒后重试 }; const results = await asyncPool(3, lists, fetchListTasks); 实测并发数 3 时，16 个列表的加载时间从 5 秒降到 1.2 秒左右，且不会触发限流。 4. 月视图实现（周一为起始） 纯手写一个日历表格，关键点在于计算每月第一天是周几，并转换为周一为起始的索引。 let startWeekday = firstDay.getDay(); // 0=周日 startWeekday = (startWeekday === 0 ? 6 : startWeekday - 1); // 周一=0, 周日=6 然后生成 42 个格子（6 行×7 列），根据日期填充任务。每个格子内显示当天任务列表，按“进行中 → 已完成”排序。 5. 周视图与 ISO 周数 周视图展示当前周的 7 天，每天的任务以列表形式纵向排列。为了显示“第几周”，实现了一个 ISO 周数计算函数（根据周一为一周的第一天）。 function getWeekNumber(date) { // 国际标准 ISO 8601 周数算法 } 6. CSV 导出 导出功能是看板的重要补充。将当前显示的任务（全部/本月/本周）转换为 CSV 格式，并自动下载。 排序：按显示日期（北京时间）从早到晚，同一天内按创建时间升序。 字段：创建日期、创建时间、状态、重要标记、销售（任务列表名）、任务内容、完成日期、备注。 文件名：任务导出_账号_范围_时间戳.csv，避免覆盖。 7. 时区处理 Graph API 返回的时间是 UTC 格式（例如 2026-04-17T07:55:54Z）。为了正确显示北京时间（UTC+8），我们在前端进行转换： function utcToBeijing(utcDateStr) { const date = new Date(utcDateStr); return new Date(date.getTime() + 8 * 3600 * 1000); } 这样任务显示日期和详情弹窗的时间都与本地一致。 遇到的坑与解决 1. MSAL 弹窗被拦截 某些浏览器会阻止跨域弹窗。解决方案是在用户点击“登录”按钮时再调用 loginPopup，而不是页面加载时自动弹出。 2. 令牌过期 acquireTokenSilent 可能因会话过期失败，此时需要回退到 acquireTokenPopup 重新交互登录。 3. Graph API 字段不存在 尝试使用 $select 只获取必要字段（如 dueDateTime）时，可能会返回 invalidRequest。稳妥做法是先不筛选，后续再优化。 4. 并行请求限流 如上所述，必须控制并发数。我们用了 3 个并发，并加入指数退避重试（实际只重试一次就够）。 成果展示 最终实现的效果： 月视图：清晰展示整个月的任务分布，每天右上角显示 已完成/总数。 周视图：以表格形式展示本周七天，每行一个任务，每天下方显示 📋 已完成/总数。 统计卡片：顶部显示全部任务总数和已完成数。 导出：一键导出 CSV，可用 Excel 打开。 切换账号：一键登出并返回登录页，支持多账号切换。 性能数据 数据规模 列表数 串行耗时 并行+限流耗时 6天 107条 16 ~5.0 秒 ~1.2 秒 1个月 900条 16 ~5.2 秒 ~1.3 秒 1年 1万条 16 ~5.5 秒 ~1.5 秒 可见，请求次数是瓶颈，而非数据量。通过并行请求优化后，即使一年上万条任务，也能在 2 秒内完成加载。 开源与自定义 这个看板的所有代码都是纯前端的，你可以任意修改样式、增加字段、调整布局。唯一的“秘密”是你在 Azure 注册的应用客户端 ID，这个公开也无妨（因为重定向 URI 白名单和权限范围保证了安全）。 如果你想快速体验，只需： 在 Azure 注册一个应用，启用“个人帐户”支持。 添加重定向 URI 为你的本地地址（如 http://localhost:3000）。 将代码中的 CLIENT_ID 替换成你自己的。 用任意静态服务器打开 HTML 文件。 全程不需要任何后端，数据直接从 Microsoft Graph 获取，安全又隐私。 总结 从想法到落地，这个 To Do 看板只用了不到一天。它证明了：通过开放的 API，开发者完全可以绕过付费工具的壁垒，自己打造满足特定需求的工具。 如果你也觉得任务管理工具的付费功能不够“值”，不妨试试这种 DIY 思路。技术栈简单（HTML + JS + MSAL），部署零成本，还能学到 OAuth 2.0、并行请求优化等实用技能。 最后，所有代码已整理成单个 HTML 文件，鉴于篇幅就不再贴了。 本文仅作技术分享，不涉及任何商业推广。使用 Microsoft Graph API 请遵守微软服务条款。
在 Home Assistant (HA) 中，Utility Meter（公共事业仪表）是一个用于累计计量的辅助集成组件。它的核心作用是将一个随时间变化的数值（如电量消耗、燃气流量、用水量）按照指定周期（小时、日、月、年）进行累加、重置并存储历史数据，最终生成类似于电表、水表的读数。 简单来说：如果你的传感器提供的是瞬时功率（W，瓦），Utility Meter 能帮你算出总用电量（kWh，千瓦时）；如果你的传感器提供的是水流的瞬时流量（L/min，升/分钟），它能帮你算出总用水量（m³，立方米）。 1. 核心概念与工作原理 源实体 (Source Entity)：需要被累加的传感器实体，通常是瞬时速率（W、L/min、m³/h）或已经累加但需要周期性重置的值。 周期 (Cycle)：重置计数的间隔。可选：hourly、daily、weekly、monthly、yearly。 计量类型 (Meter Type)： accumulating：适用于表计本身读数一直在增长的设备（如智能电表读数）。Utility Meter 会计算差值（本期读数 - 上期读数）。 incremental：适用于传感器每次上报的是增量值（如 5 kWh）。直接累加这个值。 gauge：直接将当前值作为统计值，不累加，不推荐用于能耗统计。 计量偏移 (Meter Offset)：定义周期的起点。例如，想让“月”周期从每月 5 号开始，而不是 1 号。 2. 典型使用场景 场景 源实体 (Source) Utility Meter 输出 典型周期 家庭总用电 智能电表读数 (kWh, accumulating) 今日用电量、本月用电量 日、月、年 太阳能发电 逆变器发电量 (kWh, incremental) 今日发电量、本月发电量 日、月 燃气消耗 燃气流量计 (m³/h) 或 累加表 本月燃气用量 月 电动汽车充电 充电桩功率传感器 (W) 每次充电电量 按需手动重置 区域空调能耗 空调功率传感器 (W) 客厅今日空调用电 日 3. 如何配置 Utility Meter 有两种主要配置方法： 方法一：通过 UI 配置（推荐新手） 进入 设置 → 设备与服务 → 辅助元素。 点击右下角 添加辅助元素 → 选择 累计计量表 (Utility Meter)。 填写参数： 名称：Electricity Daily，Water Monthly 等。 计量对象 (Meter Type)：选择 accumulating（如果你的电表直接给读数）。 源实体 (Source Entity)：选择你的传感器实体（如 sensor.electricity_meter_reading）。 重置周期 (Reset Cycle)：选择 monthly。 周期性偏移 (Periodic offset)：一般保持默认（月周期从每月 1 号开始）。 点击 创建。系统会自动生成一个新的 sensor.xxx 实体。 方法二：通过 configuration.yaml 配置（更灵活） utility_meter: # 示例：日用电量 daily_electricity: source: sensor.electricity_meter_reading # 源实体 cycle: daily # 日周期 meter_type: accumulating # 累加型表计 # 示例：月用电量（基于日累计表） monthly_electricity: source: sensor.daily_electricity # 可以嵌套，将日表作为源 cycle: monthly meter_type: incremental # 因为源已重置，这里是增量型 # 示例：带偏移的月用水量（从每月15号开始新周期） monthly_water_offset: source: sensor.water_meter cycle: monthly offset: 14 # 15号开始（0-based索引？实际测试：offset: 14 表示周期从每月15日0点开始） meter_type: accumulating 配置后需要重启 HA 或重载核心配置。 4. 在能源仪表板中使用 Utility Meter 最常见的使用场景是作为 HA 官方能源仪表板 (Energy Dashboard) 的数据源。 进入 设置 → 仪表板 → 能源。 在 能源配置 中，选择 用电量 → 添加耗能设备。 在“使用实体”中选择你创建的 Utility Meter 实体（如 sensor.daily_electricity）。 关键点：你需要将 Utility Meter 的源实体（智能电表读数）也添加为电网输入的实体。HA 会自动利用 Utility Meter 进行周期统计和显示。 5. 重要注意事项与最佳实践 状态类属性 (State Class)：源实体的 state_class 属性至关重要。 对于 accumulating 类型，源实体应具有 state_class: total_increasing。 对于 incremental 类型，源实体应具有 state_class: total。 否则 Utility Meter 可能无法正确计算或历史数据异常。 数据缺失问题：如果源实体上报数据频率很低（如每10分钟一次），Utility Meter 在重置瞬间可能错过最后几个增量，导致统计偏小。建议源数据上报间隔不超过5分钟。 重置时刻的行为：在周期重置的那一瞬间（如每月1日0:00），Utility Meter 的数值会变为 0（incremental 类型）或显示新的差值起点。旧的周期值会自动存储为历史属性，可通过 recorder 查看或调用服务获取上周期读数。 手动重置：你可以通过服务调用 utility_meter.calibrate 手动设置 Utility Meter 的值，或调用 utility_meter.reset 手动重置周期。 6. 实际示例：统计空调夜间用电 目标：统计每天晚上 22:00 到次日 06:00 的空调耗电量。 步骤： 拥有一个空调功率传感器 sensor.ac_power（单位 W）。 使用 Integral（积分）集成将其转换为累计电量（kWh），创建 sensor.ac_energy_total（state_class: total_increasing）。 创建 Utility Meter，设置周期 monthly，但利用 自动化 在每晚 22:00 重置它？ 不行，Utility Meter 固定周期不支持任意时间窗。 变通方案：创建两个辅助日期时间实体，配合自动化手动调用 utility_meter.reset 服务，但更推荐使用 utility_meter + 偏移 + 自定义时间戳 或改用 history_stats 集成。 对于非标准时间窗（如夜间电价时段），history_stats 比 Utility Meter 更合适。 7. 常见问题排查 问题 可能原因 解决方法 Utility Meter 数值一直为 unknown 源实体状态为 unknown 或 unavailable 检查源传感器是否正常工作 数值异常巨大 源实体是瞬时功率(W)但未经过积分；meter_type 选错 先用 Integral 集成累加，或改用 incremental 但确保源值很小 重置后新周期不从0开始 使用了 accumulating 类型且源实体读数未重置 正常行为。若想从0开始，使用 incremental 类型 历史数据不显示在能源仪表板 Utility Meter 的源实体 state_class 缺失 为源实体添加 state_class: total_increasing 或 total 总结 Utility Meter = 周期性的累加器 + 自动重置 + 历史存档 最适合与能源仪表板配合，实现日/月/年级别的能耗统计。 源实体必须是累计型数值（自己积分或用累加表），而不是瞬时速率。 配置简单、性能好，是 HA 中能耗管理的基石组件。
针对末日/战争场景下的家庭药箱，我按优先级和功能分类，给你一份可直接照单准备的清单。所有药品请存放在阴凉、干燥、避光、儿童够不到的地方，并每半年检查一次有效期。 一、外伤处理（最常用，战斗或劳作难免受伤） 物品 数量 用途与备注 碘伏棉球或碘伏液+棉签 1瓶+1包 清洗伤口，比酒精刺激性小 75%医用酒精 1小瓶 消毒工具、针头，不要直接洗大伤口 无菌纱布块 2包 覆盖伤口、止血 绷带卷 3卷 固定纱布，可做三角巾 弹性绷带 1卷 扭伤固定关节 医用胶带 1卷 固定纱布 创可贴（各种尺寸） 20片 小划伤 止血带（或军用橡皮管） 1条 大出血时保命用，需学会使用 云南白药粉（含保险子） 1瓶 止血、化瘀，对刀伤有效 无菌缝合针线（或一次性皮肤吻合器） 1套 深长伤口临时缝合，平时用针线煮沸消毒可替代 镊子+小剪刀 1套 挑刺、剪坏死组织 二、抗感染药物（没有抗生素，小伤可能致命） 药品 规格 用途 阿莫西林（青霉素类） 2盒 呼吸道、皮肤软组织感染（需不过敏） 头孢克肟或头孢拉定 2盒 较严重感染，与阿莫西林二选一备 左氧氟沙星片 1盒 肠道感染、尿路感染 甲硝唑片 2瓶 牙周炎、阑尾炎预防、厌氧菌感染 红霉素软膏 2支 皮肤脓疱、小烧伤 莫匹罗星软膏（百多邦） 1支 更有效对付金黄色葡萄球菌 注意：抗生素属处方药，现在合法途径难大量备，可托人从乡镇卫生院少量购买，或备鱼腥草、蒲公英干品煮水作为替代（效果弱但有总比没有强）。 三、消化道用药（脏水、变质食物易引起腹泻） 药品 数量 用途 口服补液盐（Ⅲ） 10袋 最重要，防脱水，可自制（1升水+6勺白糖+半勺盐） 蒙脱石散（思密达） 2盒 物理止泻，吸附毒素 黄连素片 2瓶 细菌性肠炎 藿香正气水（不含酒精型） 2盒 中暑、湿滞肠胃、恶心呕吐 健胃消食片 1盒 吃粗粮过多消化不良 开塞露 5支 严重便秘（长期吃干粮易便秘） 四、呼吸道与退烧镇痛 药品 数量 用途 布洛芬片（或胶囊） 2瓶 退烧、止痛（头痛、关节痛、痛经） 对乙酰氨基酚（扑热息痛） 1瓶 退烧（与布洛芬交替使用） 复方氨酚烷胺（感康） 2盒 普通感冒症状 氨溴索片（沐舒坦） 1盒 化痰 急支糖浆或川贝枇杷膏 2瓶 止咳（甜，可补充热量） 氯苯那敏（扑尔敏） 1瓶 过敏、流涕、助眠 五、外用止痛与皮肤 物品 数量 用途 双氯芬酸二乙胺乳胶剂（扶他林） 1支 关节扭伤、肌肉痛 风油精或清凉油 5盒 蚊虫叮咬、提神、防中暑 炉甘石洗剂 1瓶 湿疹、荨麻疹、痱子 高锰酸钾片（外用） 10片 化水清洗感染伤口、坐浴、消毒果蔬（极稀） 凡士林 1罐 防皮肤干裂、润滑、轻度烫伤保护 六、特殊场景与工具 物品 数量 用途 一次性注射器（2ml、5ml） 各5支 临时冲洗伤口、喂药（不带针头） 针灸针或三棱针 1套 放血急救（中风、高热惊厥），需懂穴位 电子体温计 1支 不推荐水银，易碎 急救手册（小本） 1本 关键时刻查阅 酒精棉片 50片 快速消毒 医用橡胶手套 10副 防交叉感染 七、长期保存与替代方案（农村特色） 石灰：半斤生石灰块，密封保存。加水可发热（热敷）、可消毒厕所。 食盐：2斤粗盐，配水成盐水（0.9%）可洗伤口、洗鼻。 艾绒：干艾叶搓绒，卷成艾条可艾灸（止痛、温经）、也可熏蚊。 大蒜：捣碎外敷治痈肿，内服抗肠道菌（生吃2瓣/日）。 高度白酒（60度以上）：半斤，替代酒精消毒工具。 八、存放建议 用一个防水的塑料收纳箱（红色十字标记），放在卧室角落不碍事处。 附一张手写清单，注明每种药的用法、用量、失效期。 每3个月检查一次，补充消耗品。 最后提醒：不要盲目囤积过期药品，以上清单总花费约200~300元（不含抗生素，抗生素需另想办法）。如果完全买不到抗生素，多备金银花、连翘、黄连等草药干品，煮水内服或外洗。保持伤口清洁比抗生素更重要——干净的水+碘伏+无菌纱布能解决大部分小伤。 现在就可以开始收集一个鞋盒大小的“基础药包”，从碘伏、纱布、布洛芬、蒙脱石散、口服补液盐这五样起步。
末日生存指南：一份让你活下来的实操手册 不张扬、不浪费、不放弃 —— 农村生存的十二字真言 当城市陷入混乱，当电网崩溃、通讯中断、秩序瓦解，农村老家将是你最坚实的避风港。这里有一望无际的庄稼地、压不垮的水井、砖墙土院和世代传承的生存智慧。但光有土地还不够，你需要一份真正接地气、可操作的生存指南。本文整合了电力、水源、医疗、通信、防御等所有关键环节，全部基于农村的实际条件，不搞花架子，只讲保命干货。 一、核心原则：为什么农村比城市更安全？ 自给自足：粮食、水源、燃料就在身边 人口稀疏：降低冲突和感染风险 隐蔽性强：独门独院，易于伪装 工具丰富：农具可攻可守，柴火垛能藏人 但也要面对现实挑战：电力中断、水泵停转、医疗资源匮乏、通讯孤岛。下面我们逐项破解。 二、家庭药箱：没有医院，你就是医生 在末日，一场腹泻或一处伤口感染就可能致命。按以下清单准备，总花费约200-300元（抗生素除外）。 外伤处理（最常用） 碘伏棉球、75%酒精、无菌纱布、绷带、医用胶带 创可贴、止血带、云南白药粉（含保险子） 无菌缝合针线（或普通针线煮沸消毒替代） 镊子、小剪刀 抗感染药物（关键） 阿莫西林、头孢拉定、左氧氟沙星、甲硝唑 红霉素软膏、莫匹罗星软膏（百多邦） 备选草药：蒲公英、鱼腥草干品煮水 消化道用药（脏水易致腹泻） 口服补液盐Ⅲ（最重要，防脱水） 蒙脱石散、黄连素、藿香正气水（无酒精型） 健胃消食片、开塞露 退烧镇痛与呼吸 布洛芬、对乙酰氨基酚（可交替使用） 复方氨酚烷胺（感康）、氨溴索（化痰） 氯苯那敏（扑尔敏，抗过敏/助眠） 外用皮肤与工具 扶他林（关节扭伤）、风油精、炉甘石洗剂 高锰酸钾片（外用消毒）、凡士林 一次性注射器（冲洗伤口）、体温计、急救手册 农村特色替代品 生石灰：消毒厕所、加水发热热敷 粗盐：配成0.9%盐水洗伤口 艾绒：艾灸止痛、熏蚊 高度白酒（60度以上）：替代酒精消毒 存放：防水收纳箱，标注用法和有效期，每3个月检查一次。 三、电力中断：没有国家电网，你还有太阳和双手 农村常见“细水井”（管径同普通水管），电力中断后水泵停转、冰箱失效、夜晚漆黑。解决方案分层如下： 3.1 照明 蜡烛：备50根以上，铁盒存放防软 油灯：碗+食用油+棉绳（鞋带），一碗油≈20小时 手电/头灯：用5号电池的LED款，备20节电池 火把：柴油+破布缠棍（慎用，防火灾） 3.2 食物保存（冰箱停摆） 地窖/红薯窖：恒温10-15℃，存放萝卜、白菜、土豆 井水吊篮：食物吊入井内离水面半米，鲜肉可存1-2天 长期无电方法：晒干（豆角、茄子）、盐腌（酸菜、腊肉）、油封（肉块炸后浸猪油）、粮食装塑料袋扎紧放入陶缸盖草木灰 3.3 水源（细水井解决方案） 你的细水井（小口径塑料管井）可以配合直流光伏深井潜水泵使用。关键步骤： 选泵：测量井管内径，选择3英寸或4英寸直流潜水泵（12V-24V），功率150W-500W（根据井深和用水量） 配光伏板：功率高出水泵20%-30%，推荐单晶硅板 不必须配电池：白天抽水存入高位水塔，靠重力实现24小时用水，比电池更可靠 预算：基础抽水套装约1500-4000元；含蓄电池和逆变器约1-3万元 购买渠道：淘宝/1688搜索“直流光伏深井泵 套装”，或咨询县城光伏网点 储水容器：所有大可乐瓶、油桶、浴缸、太阳能热水器（约150升）都可临时储水。 3.4 取暖与烹饪 柴火灶：备足玉米芯、树枝、秸秆 火盆：铁盆烧木炭，注意通风（防一氧化碳） 土暖气：烟道盘成炕，睡人 放大镜点火：晴天可点燃干草，省打火机 3.5 长期无电技巧 太阳能充电：旧光伏板+汽车电瓶+逆变器，可带LED灯和手机 手摇发电机：旧自行车摩擦带动12V电机 盐水电池：铜片+锌片插入盐水，串联得1-2V，点LED 蒸发冷却罐：两陶罐套叠中间填湿沙，内罐降温10℃ 手摇石磨：磨小麦、玉米 四、通信：当手机变成砖头 移动基站和宽带在极端情况下会迅速瘫痪。你的通信方案必须不依赖公共设施。 4.1 必备设备金字塔 层级 设备 作用 预算 基础 全波段短波收音机（手摇充电） 接收外界信息，生命线 200-500元 基础 免执照公众对讲机（人手一台） 团队内部1-5公里通信 200-800元/对 进阶 业余电台（UV段手台） 数十公里通信，需考取执照 500-2000元 进阶 短波电台 数百至数千公里通联 3000-8000元 核心 北斗短报文终端/卫星电话 地面网络完全崩溃时求救 数千至万元 4.2 重要提醒 普通对讲机不能当收音机用（频段不同），但少数型号集成FM收音机 所有设备提前备好电池、充电线，利用光伏板供电 和家人约定“失联后的家庭通信计划”：对讲机频道、汇合地点、卫星求救暗号 4.3 信息获取 收音机调频FM弱时，用中波MW 村里大喇叭（蓄电池）也可听通知 派人骑车到乡镇政府打听，回村传达 五、防御：让敌人觉得你家“不值得、不容易、太危险” 防御不是主动攻击，而是拒止、拖延、预警和保护生命。遵循“隐蔽-阻碍-感知-反击”四层。 5.1 第一层：隐蔽与伪装 外观：门口堆杂草、破农具，墙上写“危房勿近”。院墙故意扒缺口，用柴火挡。 屋顶：光伏板用旧草席遮盖，避免反光。 生活：晚上不用白炽灯，改用红布罩手电。狗拴在屋内，防乱叫暴露。 气味：垃圾深埋加石灰，不固定时间飘出肉香。 5.2 第二层：障碍与阻延 外围：撒碎玻璃渣、荆棘条（酸枣枝）、埋短铁钉板 院墙：墙头插碎玻璃片、倒立铁钉或带刺铁丝 入口：木门内加粗门杠，堆沙袋，只留侧身缝隙 院内：浅坑覆塑料膜再盖土（踩踏出声），关键通道洒草木灰留脚印 5.3 第三层：感知与预警 被动：养土狗，院门挂空铁皮罐（拉细绳连接），鱼线+铃铛 主动：无人机白天环飞侦察1公里；夜晚趴地听音；小镜子反光传信号 值守：与邻居组3-5人巡逻队，每人2小时，带农具和哨子（一声注意、二声接近、三声攻击） 5.4 第四层：对抗与防御（被迫时） 远程驱离：弹弓（玻璃珠）、自制吹箭、燃放二踢脚 近身：铁锹（拍）、镰刀（钩）、锄头（抡），长柄优先。备石灰粉/辣椒粉顺风撒眼 掩体：门后堆沙袋半人高，院内挖深50cm交通壕连接房屋与柴房 撤退：预留逃生通道（拆后墙砖用稻草帘挡），约定紧急集合点（村外树林、废弃砖窑） 5.5 心理防御 不传谣、只信多方印证的信息 善待俘虏：搜身、给食物、绑手脚放到门外，避免结仇 示弱：故意说“粮也快断了，明天去借粮”，降低被抢概率 5.6 农村特有优势 柴火垛（藏人藏粮）、粪坑（造臭区）、石碾（重型障碍） 玉米秸秆捆（吸低速弹）、地窖（藏身）、酸枣树篱 现在就能做的三件事： 绕院子走一圈，记下三个易翻墙处，堆上酸枣枝 和邻居约定哨声含义 院角备一桶沙、一桶水（灭火+防御） 六、无人机与电台：要不要买？ 无人机：高级工具，非必需品 价值：空中侦察、小件运输、心理威慑 风险：空域管制、信号干扰、暴露位置、成本高 建议：优先投资粮食、药品、光伏板。预算充足可备基础款（3000元内），学会手动模式飞行，并严格自律“不黑飞”。 电台 vs 对讲机 对讲机：团队内部几公里，买免执照公众对讲机即可 业余电台：远距离通联，需考取执照，但能让你在末日中获取关键信息、联络幸存者 收音机：必须备一台手摇充电的全波段短波收音机，它是你与外界最后的联系 七、立即行动清单（今天就能做） 事项 预估成本 截止 买50根蜡烛、2个打火机、1板火柴 30元 本周 备10节1号电池和2个LED手电 40元 本周 清理地窖或挖一个简易菜窖 0元 本周末 收集20个废弃大可乐瓶，洗净装水 0元 今天 学会用放大镜点火 0元 明天 和家人约定无电时集合点与分工 0元 今晚 买一台手摇充电短波收音机 200-500元 下周 网购一套直流光伏抽水套装（细水井用） 1500-4000元 视预算 八、最后的话 生存不是靠最顶尖的设备，而是靠冗余的备份、熟练的技能和平静的心态。农村有土地、有水井、有砖墙，还有世代相传的勤俭与坚韧。你不必成为特种兵，只需做一个不露富、不放弃、不惊慌的普通人。 把这份指南放在手边，和家人一起演练一次停电之夜，挖一锹土加固院墙，晒一簸箕干菜——这些动作本身，就是在为末日播种希望。 记住：让坏人觉得麻烦，从而去找更简单的目标。你的目标永远是活着，并且不引人注目地活着。 祝平安。
使其能够自动识别 STEP 文件中的每个独立部件（即导入后的每个对象），为每个部件单独计算其边界框尺寸和体积，并生成序号（同一文件内从 1 开始递增）。输出 CSV 文件在原有列（文件名、长、宽、高、三维尺寸、体积）前增加一列“序号”，其他格式处理（如尺寸整数化、保留两位小数）保持不变。 修改后的完整脚本 BatchStepBBox # -*- coding: utf-8 -*- import os import csv import FreeCAD import Part from PySide2 import QtWidgets def select_folder(): """弹出文件夹选择对话框，返回所选文件夹路径""" folder = QtWidgets.QFileDialog.getExistingDirectory( None, "选择包含 STEP 文件的文件夹", "", QtWidgets.QFileDialog.ShowDirsOnly ) return folder def format_dimension(value): """ 格式化尺寸值：如果是整数则返回整数形式，否则保留两位小数 """ # 判断是否为整数（容差1e-6） if abs(value - round(value)) < 1e-6: return str(int(round(value))) else: return f"{value:.2f}" def process_step_files(folder_path, output_csv): """ 批量处理文件夹内的STEP文件，识别每个独立部件，提取尺寸并导出为CSV """ extensions = ('.step', '.stp') results = [] for filename in os.listdir(folder_path): if filename.lower().endswith(extensions): file_path = os.path.join(folder_path, filename) print(f"正在处理: {filename}") try: doc = FreeCAD.newDocument("TempDoc") Part.insert(file_path, doc.Name) # 收集所有具有形状的对象 shapes = [obj for obj in doc.Objects if hasattr(obj, 'Shape') and obj.Shape] if not shapes: print(f"警告: {filename} 没有有效形状") FreeCAD.closeDocument(doc.Name) continue # 为每个对象单独处理 for idx, obj in enumerate(shapes, start=1): bbox = obj.Shape.BoundBox # 获取三边长度并排序（长、宽、高） lengths = [bbox.XLength, bbox.YLength, bbox.ZLength] lengths.sort(reverse=True) length, width, height = lengths volume = length * width * height # 格式化 length_str = format_dimension(length) width_str = format_dimension(width) height_str = format_dimension(height) dimension_str = f"{length_str}*{width_str}*{height_str}" volume_str = format_dimension(volume) results.append([ filename, idx, # 新增序号 length_str, width_str, height_str, dimension_str, volume_str ]) FreeCAD.closeDocument(doc.Name) except Exception as e: print(f"处理 {filename} 时出错: {e}") # 写入CSV文件（表头增加“序号”列） with open(output_csv, 'w', newline='', encoding='utf-8') as f: writer = csv.writer(f) writer.writerow(['文件名', '序号', '长', '宽', '高', '三维尺寸', '体积(mm³)']) writer.writerows(results) print(f"处理完成，结果保存至: {output_csv}") if __name__ == "__main__": folder = select_folder() if not folder: print("未选择文件夹，程序退出。") exit() output = os.path.join(folder, "dimensions.csv") process_step_files(folder, output) 主要修改点说明 移除全局边界框合并：原代码通过 combined_box.union 将所有形状合并为一个整体边界框，现改为逐个形状处理。 单独处理每个对象：遍历 doc.Objects 中所有带有 Shape 属性的对象，为每个对象独立计算边界框尺寸和体积。 添加序号：使用 enumerate(shapes, start=1) 为每个文件内的部件生成连续序号，并作为新列写入 CSV。 更新 CSV 表头：在输出列中加入“序号”列，位于文件名之后。 效果示例 假设一个 STEP 文件中包含 3 个独立部件，CSV 将输出如下内容： 文件名 序号 长 宽 高 三维尺寸 体积(mm³) part1.step 1 100 50 20 1005020 100000 part1.step 2 80 60 30 806030 144000 part1.step 3 40 40 40 404040 64000 注意事项 本方法将导入 STEP 后 FreeCAD 文档中的每个独立对象视为一个“件”。若 STEP 中某些零件由多个对象组成（如装配体），仍会分别列出，如需合并请另行说明。 原脚本中的 format_dimension 函数未作改动，尺寸格式化规则保持不变。 若某个文件无有效形状，会给出警告并跳过。 可以将上述函数替换原脚本中的对应部分，其余代码（如 select_folder、if __name__ == "__main__" 等）保持不变即可直接使用。
在AutoCAD中，为图块添加属性（即可变的文字，如编号、型号、尺寸等）主要有两种情况：创建新块时加入属性，以及为现有块添加属性。 以下是具体操作步骤： 一、 创建新块时加入属性 如果正在制作一个需要带文字标注的块（例如粗糙度符号、标高、门窗编号），建议在创建块之前先定义属性。 定义属性 先绘制好块的图形部分。 输入命令 ATTDEF （或点击菜单：绘图 → 块 → 定义属性）。 在弹出的对话框中设置： 标记：这是属性的名称（例如“编号”、“长度”）。（必填） 提示：插入块时提示用户输入的内容（例如“请输入设备编号：”）。如果不填，默认显示“标记”。 默认：如果不修改，默认显示的文字。 文字设置：设置文字高度、对齐方式等。 点击“确定”，将属性文字放置在图形旁边的合适位置。 创建块 输入命令 BLOCK （或 B）。 在“选择对象”时，框选图形和刚才定义的属性文字。 指定插入点后，点击确定。 注意：在“块定义”对话框中，不要勾选“分解”，否则属性会失效。 二、 为现有块添加属性 如果图块已经存在，想要给它增加新的属性（例如给一个已有的椅子图块增加“型号”属性），需要使用 块编辑器（BEDIT）。 进入块编辑器 输入命令 BEDIT （或 BE）。 在弹出的对话框中选择你要修改的那个块，点击“确定”。 添加属性 进入块编辑器界面后，输入命令 ATTDEF 定义属性。 设置好“标记”、“提示”和“默认值”等。 将属性文字放置在块图形中的合适位置。 保存并同步 点击工具栏上的 “关闭块编辑器”。 选择“保存更改”。 关键步骤：为了让已经插入图纸中的旧块也显示出新属性，必须输入命令 ATTSYNC （属性同步），选择该块并回车。如果不做这一步，旧块不会自动获得新属性。 三、 插入块并填写/编辑属性 插入时填写：输入 INSERT 选择该块，在图纸中点击插入点后，会自动弹出对话框（或命令行提示），输入对应的属性值即可。 插入后修改： 双击带有属性的块，会弹出“增强属性编辑器”，可以在这里修改属性值。 选中块，按 Ctrl + 1 打开特性面板，在“属性”栏中也可以修改。 其他相关设置（在同一对话框中） 文字选项：可以提前设置好文字的样式、高度、对齐方式。高度建议根据块的实际比例设置，例如在 1:1 图纸中，属性文字高度设为 2.5 或 3 比较常用。 预设（Preset）：勾选后，插入块时不会提示，直接使用默认值，适合固定内容（如材料名称）。 不可见（Invisible）：勾选后属性在图纸中隐藏，但数据仍然存在，可用于存储不显示的信息（如设备编号数据库）。 验证（Verify）：勾选后插入块时会要求用户确认输入的值，较少使用。 固定（Constant）：勾选后属性值不能修改，相当于固定的文字。 常见提示 属性不显示/显示为标签？ 如果插入块后显示的是“标记”里的内容（如“编号”）而不是你输入的值，输入命令 ATTMODE，将值设置为 1 或 2，然后输入 REGEN 重生成模型。 属性文字太大或太小？ 在定义属性（ATTDEF）时预设的文字高度要合适。如果是现有块，可以用 BEDIT 进入块编辑器调整属性文字的高度。 总结建议 标记：用英文标识，无空格，用于后台识别。 提示：用清晰的中文问句，指导填写。 默认值：填入最常用的值，减少重复输入。 设置好这三个字段，再配合块编辑器和属性同步（ATTSYNC），就能让图块变得既规范又高效。
以下是直接获取写入量的命令（使用内核统计，最准确）： eMMC 总写入量 awk '{printf "eMMC 总写入量: %.2f GB\n", $7*512/1024/1024/1024}' /sys/block/mmcblk0/stat SATA SSD 总写入量（假设设备为 /dev/sda） awk '{printf "SATA 总写入量: %.2f GB\n", $7*512/1024/1024/1024}' /sys/block/sda/stat 如果 SATA 设备名不是 sda（例如 sdb），请用 lsblk 确认挂载 /volume 的设备并替换。