给 AI 的说明书(自包含)。 不能联网的 AI? 下载 txt 说明书 然后上传或粘贴给你的 AI。 · Markdown 原文 /ai.md · 索引 /llms.txt · 开发者页面

天河落 THL —— Revit 插件开发说明书(专门给 AI 阅读)

版本 1.1(2026-10)· 官网 https://tianheluo.com · 投稿与联系邮箱 [email protected] 宿主与工具源码(公开,MIT):https://github.com/H8DIDI/tianheluo-core · 网站源码:https://github.com/H8DIDI/tianheluo-web 本说明书是自包含的:规则、接口、完整示例代码都在里面,不需要访问任何链接。

0. 先读这个(30 秒版)

你要做什么:用户想为「天河落」平台开发一个 Revit 2026 插件。你要写出一个文件夹,里面是 4 个文件:plugin.json、script.py、README.md、LICENSE。用户把它压缩成 zip 发给平台维护者,审核后上架。

插件长什么样:一个 Python 脚本(运行在 Revit 里,用 Revit API 读写模型),点一下按钮就运行一次。不需要安装任何东西,也不需要编译。

你必须遵守的 8 条硬规则(后面有详细说明): 1. 语言是 Python(IronPython 3.4),不是 CPython:没有 numpy/pandas;不能用 :=、async/await、match;f-string 可以用。 2. 脚本结构固定:main() 函数 + 末尾 if __name__ == "__main__": main()。 3. 只能用说明书「第 4 节」列出的接口:from pyrevit import revit, DB, UI, forms, script。不要使用没列出的 pyRevit 功能(比如 script.get_output()、script.exit())。 4. 修改模型必须:先弹窗预览并让用户确认 → 只开一个 with revit.Transaction("名称"): → 统计成功/失败。这样用户按一次 Ctrl+Z 就能撤销全部。 5. 所有给用户看的信息用 forms.alert(...),不要用 print 给用户看(print 只写日志文件)。 6. 禁止:执行外部程序/命令、eval/exec、未声明的联网、删除文件、加载外部 DLL、读取密钥/注册表。 7. 长度单位:Revit API 里是英尺。毫米请 mm / 304.8。 8. 你无法运行用户的 Revit,不要声称「已测试通过」。要提醒用户在空白测试项目里先试,把报错发回来继续改。

交付格式:见「第 14 节」。不确定时的做法:见「第 13 节」。


1. 这是什么

天河落(Tianheluo,缩写 THL) 是一个 Revit 2026 的「插件宿主 + 开放插件商店」:

当前支持:Revit 2026(2026.0 ~ 2026.5+)。插件语言:Python(IronPython 3.4)。

2. 宿主架构(Architecture)

Revit 2026
 └─ THL.Host(外接程序,.NET 8 / .NET 10 两套编译,安装脚本按 Revit 版本自动选择)
     ├─ 功能区「天河落」选项卡
     │    ├─ 「插件中心」按钮 → 启动器窗口(搜索 / 运行 / 商店 / 安装 / 更新 / 停用 / 卸载)
     │    └─ 「已装插件」面板:每个已启用插件一个按钮(最多 12 个,按安装顺序占槽位)
     ├─ 插件管理器 Store:扫描 %APPDATA%\Tianheluo\plugins\<id>\plugin.json
     ├─ 热加载:每次点击都重新读取磁盘上的脚本/DLL,更新插件无需重启 Revit
     ├─ Python 运行环境:内置 IronPython 3.4.2 + 标准库 + 精简版 pyrevit 兼容层
     └─ 商店客户端:读取 https://tianheluo.com/plugins.json,下载 .thlp 到本地并解压安装

关键事实:

项 说明
用户数据目录 %APPDATA%\Tianheluo\:plugins\<id>\(已装插件)、logs\(日志)、data\(插件数据文件)、config.json(商店地址、停用列表、功能区槽位)
插件安装 把 .thlp 解压到 plugins\<id>\;同 id 再装即覆盖更新
运行方式 点击按钮 → 宿主读取 script.py → 在新的 IronPython 引擎里以 __main__ 执行 → 结束后释放。脚本之间互相隔离,没有持久进程状态
线程 脚本在 Revit 主线程里同步执行,可以直接读写模型(写模型必须开 Transaction);执行期间 Revit 界面被阻塞,耗时操作请用进度条并允许取消
功能区刷新 在插件中心里安装/卸载/停用后,关闭插件中心窗口,功能区立刻同步(无需重启)
更新 插件中心打开时会自动对比商店清单:已装插件的 version 低于商店里的版本,就在「已安装」页显示「更新到 vX」,用户一键更新(无需重启 Revit)。所以插件要更新,只需递增 version 后重新投稿。 宿主本身的更新信息在 https://tianheluo.com/host.json,更新包会先校验 SHA256,Revit 关闭后自动安装,失败自动回滚。
下载计数 商店下载走 https://tianheluo.com/api/dl/<id>,每个访客对同一插件 1 小时内只计 1 次,不保存个人信息;官网首页显示热门插件榜,数据来自 https://tianheluo.com/api/stats。
商店清单 GET https://tianheluo.com/plugins.json → {"revit":"2026","plugins":[...]},每项含 id/name/version/summary/category/download/author/license...
下载地址 https://tianheluo.com/dl/<id>_<version>.thlp

3. 插件包格式(.thlp)

.thlp 是一个 zip,根目录必须有 plugin.json。社区插件只接受 Python 插件(源码可审核)。

my-plugin_0.1.0.thlp  (zip)
├─ plugin.json        必需
├─ script.py          必需(plugin.json 的 "script" 指向它)
├─ icon.png           可选,按钮图标,建议 96×96 PNG;没有则宿主按 glyph/color 自动生成
├─ LICENSE            社区插件必需(OSI 开源许可证)
└─ 其它 .py / .xaml / 数据文件(可选;插件目录会加入 sys.path,可 import 同目录模块)

plugin.json 字段

{
  "id": "count-by-category",
  "name": "按类别统计",
  "version": "0.1.0",
  "description": "统计当前视图中各类别构件数量。\n只读,不修改模型。",
  "category": "数据处理",
  "revit": "2026",
  "type": "python",
  "script": "script.py",
  "glyph": "统",
  "author": "your-github-name",
  "license": "MIT",
  "homepage": "https://github.com/your-github-name/your-repo"
}
字段 必需 规则
id ✅ 小写字母/数字/连字符,3–41 字符,以字母或数字开头;全平台唯一;发布后不要改
name ✅ 显示名,≤ 20 字符,中文或英文皆可
version ✅ 语义化版本 x.y.z;更新插件时必须递增,宿主据此提示更新
description ✅ ≤ 400 字符。第一行是一句话摘要(商店卡片显示),其后可换行写要点
category ✅ 市政管线 出图打印 数据处理 建模辅助 视图管理 导出 其他 之一
revit ✅ 目前固定 "2026"
type ✅ 社区插件固定 "python"
script ✅ 入口脚本文件名,通常 script.py
author ✅ 你的名字或 GitHub 用户名
license ✅ SPDX 标识,如 MIT Apache-2.0 BSD-3-Clause GPL-3.0-only LGPL-3.0-only MPL-2.0
glyph 建议 图标上显示的 1–3 个字符(如「桩」「DWG」)
color 可选 图标主色,#RRGGBB;默认按分类配色
homepage 可选 项目主页
hostApi 可选 插件所需的宿主接口版本,整数,默认 1(目前宿主支持 1)。写 1 即可;宿主以后若升级了接口,会在发布说明里写明,旧插件不会被破坏
network 可选 默认 false。需要联网才填 true,并在 README 里说明访问了哪些地址、为什么(见第 9 节)

JSON Schema:https://tianheluo.com/schema/plugin.schema.json

4. Python 运行环境契约(非常重要)

4.1 访问 Revit

from pyrevit import revit, DB, UI, forms, script   # 与 pyRevit 同名的兼容层(只实现了下面列出的部分)

doc   = revit.doc        # 当前活动文档  Autodesk.Revit.DB.Document
uidoc = revit.uidoc      # UIDocument(选择集、当前视图)
uiapp = revit.uiapp      # UIApplication
# DB / UI 就是 Autodesk.Revit.DB / Autodesk.Revit.UI 命名空间,可直接用 Revit API:
walls = DB.FilteredElementCollector(doc).OfCategory(DB.BuiltInCategory.OST_Walls).WhereElementIsNotElementType().ToElements()

也可以直接 import clr + clr.AddReference(...) 使用任何已加载的 .NET 程序集(System.*、PresentationFramework 等)。

4.2 兼容层已提供的接口(只有这些,别的不要假设存在)

revit:revit.doc · revit.uidoc · revit.uiapp · revit.Transaction(name)(上下文管理器:with revit.Transaction("名称"):,正常退出提交,异常回滚)· revit.query.get_name(element) · revit.DB · revit.UI

forms(对话框,深色主题):

函数 说明
forms.alert(msg, title=None, sub_msg=None, ok=True, cancel=False, yes=False, no=False, exitscript=False) 提示/确认。有 yes/no/cancel 时返回 True/False(选了「是」才为 True);普通提示返回 True
forms.SelectFromList.show(items, title='选择', button_name='确定', multiselect=False, name_attr=None) 列表选择(带搜索框)。单选返回所选项,多选返回列表,取消返回 None。items 里可放字符串、任意对象(用 name_attr 指定显示属性)或 forms.TemplateListItem 子类实例(重写 name 属性自定义显示文字,.item 是原对象)
forms.CommandSwitchWindow.show(options, message='') 一排大按钮,返回被点的那个选项字符串;取消返回 None
forms.ask_for_string(default='', prompt='', title='') 单行输入,取消返回 None
forms.pick_folder(title=None) 选择文件夹,取消返回 None
forms.pick_file(file_ext='', title=None) 选择文件,取消返回 None
forms.save_file(file_ext='', default_name='', title=None) 保存文件对话框,取消返回 None
forms.ProgressBar(title, cancellable=False) 进度条(可用 with)。pb.update_progress(当前, 总数)、pb.cancelled;标题里的 {value} {max_value} 会被替换
forms.WPFWindow 自定义 XAML 窗口基类:class W(forms.WPFWindow): def __init__(self, xaml): forms.WPFWindow.__init__(self, xaml, literal_string=True);XAML 里带 x:Name 的控件自动成为实例属性;.show_dialog() 显示

script:script.get_logger()(有 debug/info/warning/error,输出到日志)· script.get_document_data_file(id, ext)(按文档分开的数据文件路径)· script.get_universal_data_file(id, ext)(全局数据文件路径)

明确不支持(pyRevit 里有但这里没有):script.get_output() 及输出窗口、script.exit()(请用 sys.exit())、pyrevit.coreutils、pyrevit.userconfig、pyrevit.framework、pyrevit.compat、revit.query 里除 get_name 以外的函数、forms.select_* 系列。需要这些能力时请直接用 Revit API 和 .NET。

4.3 写模型的规则

  1. 所有修改必须在一个 Transaction 里,并且尽量只开一个:用户按一次 Ctrl+Z 就能撤销整个插件的操作。
  2. 先预览/确认再写入:会改很多构件的插件,必须先告诉用户「将修改 N 个构件」并让用户确认。
  3. 不要吞异常:失败要 forms.alert 说明原因;批量操作要统计成功/失败数。
  4. 不要依赖当前视图类型以外的 UI 状态;不要假设文档已保存。
  5. 单位:Revit API 内部长度单位是英尺。毫米请 mm / 304.8。
  6. 耗时操作要用 forms.ProgressBar(..., cancellable=True) 并检查 pb.cancelled。

5. 最小示例

目录 hello-world/:

plugin.json

{
  "id": "hello-world",
  "name": "你好天河落",
  "version": "0.1.0",
  "description": "最小示例:弹出当前项目的名称和选中构件数量。",
  "category": "其他",
  "revit": "2026",
  "type": "python",
  "script": "script.py",
  "glyph": "你",
  "author": "tianheluo",
  "license": "MIT"
}

script.py

# -*- coding: utf-8 -*-
from pyrevit import revit, forms


def main():
    doc = revit.doc
    n = len(list(revit.uidoc.Selection.GetElementIds()))
    forms.alert("项目:{}\n当前选中构件:{} 个".format(doc.Title, n), title="你好天河落")


if __name__ == "__main__":
    main()

更多示例(可直接下载): - https://tianheluo.com/examples/hello-world/script.py - https://tianheluo.com/examples/count-by-category/script.py(只读统计 + 选择视图范围) - https://tianheluo.com/examples/set-comments/script.py(单事务批量写入 + 预览确认 + 可撤销)

6. 本地调试

  1. 安装宿主(https://tianheluo.com/install)。
  2. 把你的插件文件夹复制到 %APPDATA%\Tianheluo\plugins\<id>\(里面直接有 plugin.json)。
  3. 在 Revit 里打开「天河落 → 插件中心」,在「已安装」里点「运行」。关闭插件中心后,功能区也会出现按钮。
  4. 改了脚本不需要重启 Revit:再点一次就是最新代码。
  5. 出错时:会弹「脚本出错」对话框并给出 traceback;print() 输出和 script.get_logger() 在 %APPDATA%\Tianheluo\logs\<id>.log。
  6. 开发时请用空白测试项目,不要在真实工程上试写入类插件。

7. 打包

提交到社区仓库时不需要自己打包(CI 会打包)。如需手动打包用于本地安装:把 plugin.json 和脚本放在 zip 根目录(不是放在子文件夹里),扩展名改成 .thlp。在插件中心点「安装插件包…」选择它即可。

8. 投稿流程(成为社区贡献者)

目前的方式:把做好的插件发邮件给维护者,维护者审核后上传到网站。

  1. 按本文规范开发插件,得到一个目录:<插件id>/,里面有 plugin.json、script.py、README.md、LICENSE(可选 icon.png)。
  2. 自查:下载校验脚本 https://tianheluo.com/tools/validate.py(单文件,只需 Python 3.8+,无需安装任何库),在插件目录的上一级运行: python validate.py <插件id> 必须全部通过(显示「通过」)。也可以直接对 zip 运行:python validate.py my-plugin.zip。
  3. 把插件目录压缩成 zip(zip 里要有 <插件id>/ 这一层目录,或 plugin.json 直接在 zip 根目录,两种都接受),文件名建议 <插件id>_<版本>.zip。
  4. 发邮件到 [email protected]: - 主题:[天河落插件投稿] <插件名> v<版本> - 正文:一句话说明功能;你的署名(author);确认「我同意以 plugin.json 中声明的开源许可证发布,并保证代码为本人所写或有权发布」 - 附件:上面的 zip
  5. 维护者人工审核源码,通过后上传。上线后插件在商店里带「社区」标记和你的署名。你会收到回复邮件。
  6. 更新插件:修改后递增 version,按同样方式重新投稿。
  7. 下架/删除:发邮件说明即可。

规划中:账号与上传密钥。 注册后获得个人上传密钥,用 POST 直接提交到平台的「待审核」队列,维护者在后台审核后发布。上线时会更新本节与 /plugins.json;在此之前请使用邮件投稿。

9. 规则与安全(社区插件会在用户电脑上以其权限运行,所以审核很严)

必须 - 全部源码可读(.py)。不接受 .dll、.exe、压缩/混淆/加密的代码、.pyc。 - 带 OSI 认可的开源许可证;作者署名真实可联系。 - 会修改模型的插件:单个 Transaction、预览确认、可 Ctrl+Z、统计成功/失败。 - README 如实说明插件做什么、改什么、读写哪些文件。

禁止(校验器会扫描,审核时也会人工检查) - 执行外部程序或命令:subprocess、os.system、os.popen、System.Diagnostics.Process、powershell、cmd。 - 动态执行代码:eval(、exec(、compile( 作用于外部/拼接字符串,__import__ 动态加载,importlib 加载网络内容。 - 未声明的联网:urllib、http.client、socket、requests、System.Net、WebClient、HttpClient。确需联网请在 plugin.json 设 "network": true,并在 README 写明地址与用途;维护者会逐条审核。 - 删除或批量改写用户文件:os.remove、shutil.rmtree、os.rmdir、Directory.Delete、File.Delete(插件自己的数据文件除外,且只能在 script.get_*_data_file() 返回的路径下)。 - 读取/外传凭据、注册表、浏览器数据、环境变量中的密钥;ctypes、System.Reflection.Assembly.Load*、Marshal、clr.AddReferenceToFile* 加载外部程序集。 - 修改 Revit/Windows 的安全或系统设置;在启动时自动运行(插件只能在用户点击时运行)。 - 冒用他人名义、侵犯版权、包含恶意或误导性功能。

10. 校验规则(validate.py 与维护者导入时的检查一致)

11. 机器可读资源

资源 地址
本文(Markdown 原文) https://tianheluo.com/ai.md
本文(HTML) https://tianheluo.com/ai
llms.txt(索引) https://tianheluo.com/llms.txt
商店清单 https://tianheluo.com/plugins.json
plugin.json JSON Schema https://tianheluo.com/schema/plugin.schema.json
自查校验脚本 https://tianheluo.com/tools/validate.py
示例插件 https://tianheluo.com/examples/hello-world/script.py 等
投稿邮箱 [email protected]
宿主安装包 https://tianheluo.com/dl/THL-Host.zip

12. AI 工作流程(请照此执行)

当用户说「帮我做一个天河落插件,功能是 X」:

  1. 澄清:这个插件读还是写模型?作用对象是选中构件、当前视图还是全模型?需要用户输入什么?(问不超过 3 个问题,其余按合理默认。)
  2. 设计:用第 4 节的接口写 script.py。结构固定为 main() + if __name__ == "__main__": main();所有对话框用 forms;写模型用 revit.Transaction。
  3. 写 plugin.json:按第 3 节填写,id 用小写连字符,version 从 0.1.0 开始,author 问用户,license 默认 MIT(让用户确认)。
  4. 写 README.md:功能、使用步骤、会修改什么、已知限制。
  5. 自检:逐条核对第 9 节禁止项;确认没有 :=/async;确认没有第 4.2 节「明确不支持」的接口。
  6. 交付:给出完整目录内容(plugin.json、script.py、README.md、LICENSE),并告诉用户:本地调试见第 6 节;投稿见第 8 节(运行 validate.py 自查 → 打包 zip → 发邮件到 [email protected])。
  7. 诚实说明:你无法在用户的 Revit 里运行代码,请提醒用户先在空白测试项目里试,把弹出的错误或日志发回给你继续修。

给 AI 的提示词模板(用户可以直接复制给自己的 AI)

请先完整阅读 https://tianheluo.com/ai.md ,然后按「第 12 节」的流程,为天河落平台开发一个 Revit 2026 插件。
我的需求:<在这里描述你想要的功能>
我的署名(author):<你的名字或 GitHub 用户名>
请输出完整文件:plugin.json、script.py、README.md、LICENSE,并告诉我如何自查(validate.py)和如何投稿。

13. 常见错误与坑(写代码前先看一遍)

语法与运行环境 - ❌ if (n := len(x)) > 3:(海象运算符)→ ✅ 先 n = len(x) 再 if n > 3: - ❌ async def / await → ✅ 一律写同步代码(脚本本来就在 Revit 主线程同步执行)。 - ❌ import numpy / pandas / requests → ✅ 只用标准库和 Revit API / .NET。 - ❌ 在脚本里 print(...) 想给用户看 → ✅ 用 forms.alert(...)。 - ❌ 忘了 if __name__ == "__main__": main() → ✅ 一定要有,否则 main() 不会被调用(宿主以 __main__ 运行脚本)。 - 除法:/ 是真除法(得到小数),整除用 //。字符串默认是 Unicode,文件头写 # -*- coding: utf-8 -*-。 - 读写文本文件时指定编码:io.open(path, "w", encoding="utf-8");路径用 os.path.join,不要硬编码盘符。

Revit API - ❌ element.Id.IntegerValue(在 Revit 2026 里已不存在,会报 AttributeError)→ ✅ element.Id.Value(64 位整数)。 - ❌ 在已打开的事务里再开事务(嵌套)→ ✅ 全脚本只开一个 with revit.Transaction(...);需要多步就在同一个 with 里做。 - ❌ 在事务外修改参数/删除/创建元素 → 会抛异常。读取不需要事务。 - ❌ LookupParameter("中文名") 当作唯一可靠的方式 → 参数名随 Revit 语言/项目而变,优先 element.get_Parameter(DB.BuiltInParameter.XXX);LookupParameter 只取第一个同名参数,可能为 None,使用前必须判空。 - 参数赋值要先判断:p is not None and not p.IsReadOnly;数值/带单位的参数用 p.SetValueString("1500")(按项目单位解析),文本用 p.Set("文字"),整数/双精度用 p.Set(数值)(双精度单位是英尺等内部单位)。 - uidoc.Selection.GetElementIds() 返回 .NET 集合 → 用 list(...) 包一层再 len()。 - doc.GetElement(eid) 可能返回 None(已删除),要判空。 - 过滤构件要用 FilteredElementCollector + OfCategory / OfClass / WhereElementIsNotElementType(),不要遍历全部元素再用 Python 判断类别——大模型会非常慢。 - 大批量操作:用 forms.ProgressBar("正在处理... ({value}/{max_value})", cancellable=True),循环里 pb.update_progress(i + 1, total) 并检查 pb.cancelled。 - 长时间运行会冻结 Revit 界面(脚本在主线程)。不要写死循环,不要 time.sleep 很久。 - 不要创建线程;不要使用 ExternalEvent 或无模态窗口——脚本结束后运行环境会被释放,无法保证之后的回调。所有交互都用模态对话框,在脚本里同步完成。 - 当前文档可能是族文档/没有项目:需要项目时先判断 doc.IsFamilyDocument。 - doc.ActiveView 可能是图纸、明细表等非模型视图;统计模型构件前可判断 isinstance(doc.ActiveView, DB.ViewPlan) 等,或提示用户切换到合适的视图。

对话框 - forms.alert(msg, title="...") 普通提示;要确认写 forms.alert(msg, title="...", yes=True, no=True),返回 True 表示用户点了「是」。 - forms.SelectFromList.show(items, title="...", button_name="确定", multiselect=True):取消返回 None,一定要判空。 - forms.ask_for_string(...) 取消返回 None;留空返回 "",两者不同。 - 所有对话框取消后要 return,不要继续往下执行。

需求超出能力时:如果用户要的功能需要这里没有的东西(例如自定义功能区面板、持续监听事件、调用外部程序、联网服务),请如实告诉用户做不到或需要简化,并给出一个能做的替代方案;不要编造不存在的接口。

14. 给 AI 的交付格式(请严格按这个格式回答用户)

当用户描述完需求后,你的回答按以下顺序:

  1. 一两句话复述你理解的需求,以及你做的关键决定(比如:只读还是会改模型、作用范围是选中构件/当前视图/全项目)。如果有会影响实现的不确定点,最多问 3 个问题再动手;其余用合理默认并说明。
  2. 完整文件——每个文件一个代码块,代码块前一行写文件名,例如:

my-plugin/plugin.json json { ... } my-plugin/script.py python ... my-plugin/README.md 和 my-plugin/LICENSE 同理。文件内容必须完整,不要省略、不要写「其余同上」。 3. 使用与测试说明(用普通话写给不会编程的用户): - 本地试用:把整个文件夹 my-plugin 复制到 %APPDATA%\Tianheluo\plugins\,在 Revit 里打开「天河落 → 插件中心」,在「已安装」里点「运行」。 - 先用空白测试项目试;有错误弹窗或日志(%APPDATA%\Tianheluo\logs\my-plugin.log)把内容发回来继续改。 - 自查:下载 validate.py(https://tianheluo.com/tools/validate.py),运行 python validate.py my-plugin。 - 投稿:压缩成 zip,发邮件到 [email protected],主题 [天河落插件投稿] 插件名 v版本。 4. 诚实声明:「我无法在你的 Revit 里运行这段代码,上面没有经过实际测试」。

plugin.json 的填写规则(再强调一遍):id 小写连字符;version 从 0.1.0 开始;author 问用户;license 默认 MIT(请让用户确认);type 固定 python;script 固定 script.py;glyph 取名字里最有辨识度的 1 个字;category 只能是 市政管线 / 出图打印 / 数据处理 / 建模辅助 / 视图管理 / 导出 / 其他。

README.md 要写:一句话功能、使用步骤、会修改什么(只读则写「不修改模型」)、已知限制、作者。

LICENSE:用户选的开源许可证全文(默认 MIT,署名处填用户的名字和年份 2026)。


附录:三个完整示例插件(全文)

下面是三个已通过校验的完整插件,可以直接照着写。每个插件都是一个文件夹,含 plugin.json、script.py、README.md、LICENSE(LICENSE 为 MIT 全文,此处省略,使用时把年份和署名改成你自己的)。

示例 A:hello-world(最小插件,只读)

hello-world/plugin.json

{
  "id": "hello-world",
  "name": "你好天河落",
  "version": "0.1.0",
  "description": "最小示例:弹出当前项目名称和选中构件数量。\n只读,不修改模型。",
  "category": "其他",
  "revit": "2026",
  "type": "python",
  "script": "script.py",
  "glyph": "你",
  "author": "tianheluo",
  "license": "MIT",
  "homepage": "https://tianheluo.com/ai"
}

hello-world/script.py

# -*- coding: utf-8 -*-
"""最小示例:弹出当前项目名称和选中构件数量。只读。"""
from pyrevit import revit, forms


def main():
    doc = revit.doc
    n = len(list(revit.uidoc.Selection.GetElementIds()))
    forms.alert("项目:{}\n当前选中构件:{} 个".format(doc.Title, n), title="你好天河落")


if __name__ == "__main__":
    main()

hello-world/README.md

# 你好天河落

最小示例,演示插件的基本结构:`main()` + `if __name__ == "__main__"`,用 `forms.alert` 弹窗,只读不改模型。

**使用**:选中任意构件(可不选),点击按钮,弹出项目名和选中数量。

示例 B:count-by-category(只读统计:选择范围 + 收集器 + 结果弹窗)

count-by-category/plugin.json

{
  "id": "count-by-category",
  "name": "按类别统计",
  "version": "0.1.0",
  "description": "统计当前视图或整个项目中各类别构件的数量(前 30 类)。\n只读,不修改模型。",
  "category": "数据处理",
  "revit": "2026",
  "type": "python",
  "script": "script.py",
  "glyph": "统",
  "author": "tianheluo",
  "license": "MIT",
  "homepage": "https://tianheluo.com/ai"
}

count-by-category/script.py

# -*- coding: utf-8 -*-
"""统计各类别构件数量。只读。演示:选择范围、FilteredElementCollector、结果弹窗。"""
from pyrevit import revit, DB, forms


def main():
    doc = revit.doc
    scope = forms.CommandSwitchWindow.show(["当前视图", "整个项目"], message="统计范围:")
    if not scope:
        return  # 用户取消

    if scope == "当前视图":
        collector = DB.FilteredElementCollector(doc, doc.ActiveView.Id)
    else:
        collector = DB.FilteredElementCollector(doc)

    counts = {}
    for e in collector.WhereElementIsNotElementType().ToElements():
        cat = e.Category
        if cat is None:
            continue
        counts[cat.Name] = counts.get(cat.Name, 0) + 1

    if not counts:
        forms.alert("没有找到任何构件。", title="按类别统计")
        return

    rows = sorted(counts.items(), key=lambda kv: -kv[1])
    lines = ["{}:{}".format(name, n) for name, n in rows[:30]]
    more = "\n……(还有 {} 个类别)".format(len(rows) - 30) if len(rows) > 30 else ""
    forms.alert(
        "范围:{}\n共 {} 个类别,{} 个构件\n\n{}{}".format(
            scope, len(rows), sum(counts.values()), "\n".join(lines), more),
        title="按类别统计")


if __name__ == "__main__":
    main()

count-by-category/README.md

# 按类别统计

选择统计范围(当前视图 / 整个项目),按类别统计构件数量并弹窗显示前 30 类。**只读,不修改模型。**

演示:`forms.CommandSwitchWindow`、`DB.FilteredElementCollector`、结果弹窗。

示例 C:set-comments(写模型的标准流程:选择 → 输入 → 预览确认 → 单事务写入 → 统计 → 可撤销)

set-comments/plugin.json

{
  "id": "set-comments",
  "name": "批量写注释",
  "version": "0.1.0",
  "description": "给选中的构件批量写入「注释」参数(先预览确认,可一步撤销)。",
  "category": "数据处理",
  "revit": "2026",
  "type": "python",
  "script": "script.py",
  "glyph": "注",
  "author": "tianheluo",
  "license": "MIT",
  "homepage": "https://tianheluo.com/ai"
}

set-comments/script.py

# -*- coding: utf-8 -*-
"""给选中构件的「注释」参数批量写入文字。演示:写模型的标准流程——
选择对象 → 输入 → 预览确认 → 单个事务写入 → 统计成功/失败 → 一步 Ctrl+Z 可撤销。"""
from pyrevit import revit, DB, forms


def main():
    doc = revit.doc
    ids = list(revit.uidoc.Selection.GetElementIds())
    if not ids:
        forms.alert("请先在 Revit 里选中要写入注释的构件,再运行。", title="批量写注释")
        return

    text = forms.ask_for_string(default="", prompt="要写入「注释」的内容(留空表示清空):", title="批量写注释")
    if text is None:
        return  # 取消

    targets, skipped = [], 0
    for eid in ids:
        elem = doc.GetElement(eid)
        p = elem.get_Parameter(DB.BuiltInParameter.ALL_MODEL_INSTANCE_COMMENTS) if elem is not None else None
        if p is None or p.IsReadOnly:
            skipped += 1
        else:
            targets.append(p)

    if not targets:
        forms.alert("选中的构件都没有可写的「注释」参数。", title="批量写注释")
        return

    # 预览确认:会改多少个,让用户决定
    msg = "将把 {} 个构件的「注释」设为:\n「{}」\n\n(有 {} 个构件没有可写的注释参数,会跳过。写入后可按 Ctrl+Z 撤销。)".format(
        len(targets), text, skipped)
    if not forms.alert(msg, title="确认写入", yes=True, no=True):
        return

    ok, failed = 0, 0
    with revit.Transaction("批量写注释"):  # 只开一个事务:一次 Ctrl+Z 撤销全部
        for p in targets:
            try:
                p.Set(text)
                ok += 1
            except Exception:
                failed += 1
    forms.alert("完成:成功 {},失败 {},跳过 {}。".format(ok, failed, skipped), title="批量写注释")


if __name__ == "__main__":
    main()

set-comments/README.md

# 批量写注释

给选中构件的「注释」参数批量写入文字。**会修改模型**,所以按规范做了:

1. 先选中构件,再运行
2. 输入内容 → **预览确认**(会改几个)
3. **单个事务**写入,成功/失败/跳过都统计
4. 一次 `Ctrl+Z` 即可撤销全部

演示:`forms.ask_for_string`、`forms.alert(yes=True, no=True)`、`revit.Transaction`。