## 1. 环境搭建与第一个Python脚本
如果你是一名建筑师或BIM工程师,每天在Revit里处理复杂的几何形体,同时又羡慕Rhino和Grasshopper在自由造型与参数化设计上的灵活性,那么**Rhino.Inside.Revit**(简称RIR)对你来说,绝对是一个“神器”。它不是什么遥不可及的黑科技,简单说,就是让Rhino 7和它的好搭档Grasshopper,直接“住”进Revit软件里面。从此,你可以在Revit里无缝使用Rhino的建模工具和Grasshopper的视觉化编程,而Python脚本,就是打通这两个世界、实现自动化操作的“万能钥匙”。
我自己刚开始用的时候,也觉得这组合有点复杂,但实际踩过坑后发现,只要环境搭对了,后面就是一马平川。首先,你得确保电脑上已经安装了**Rhino 7**(WIP版或正式版都行)和**Revit 2018-2024**之间的任何一个版本。然后去McNeel官网下载Rhino.Inside.Revit的安装程序。安装过程基本就是一路“下一步”,完成后重启Revit,你会在“附加模块”选项卡里看到一个犀牛图标,点击它,Revit就会开始加载整个Rhino环境。第一次加载可能会花上一两分钟,耐心等一下,等Rhino和Grasshopper的工具栏出现在Revit界面里,就说明成功了。
环境好了,我们来写第一个能同时“指挥”Rhino和Revit的Python脚本。打开Revit,启动Grasshopper,从“Maths”选项卡里拖一个**Python Script**组件到画布上。双击组件打开编辑器,你会看到一段默认的脚本。先别管它,我们全部替换掉。一个能在RIR环境下工作的标准脚本模板,开头必须导入几个关键的库,这是和纯Rhino脚本最大的区别。
```python
# 1. 导入CLR,这是IronPython调用.NET库的桥梁
import clr
# 2. 添加必要的.NET程序集引用
clr.AddReference('System.Core')
clr.AddReference('RhinoInside.Revit') # RIR的核心API
clr.AddReference('RevitAPI') # Revit API
clr.AddReference('RevitAPIUI')
# 3. 从这些程序集里导入我们需要的命名空间
from System import Enum, Action
import rhinoscriptsyntax as rs # Rhino传统脚本语法,操作简单
import Rhino.Geometry as rg # RhinoCommon几何库,功能强大
import Grasshopper
from RhinoInside.Revit import Revit, Convert
from Autodesk.Revit import DB # Revit数据库操作的核心
# 4. 导入几何转换的扩展方法,这是数据互通的关键!
# 有了它,Rhino的物体可以直接调用.ToXXX()方法变成Revit物体,反之亦然。
clr.ImportExtensions(Convert.Geometry)
# 5. 获取当前活动的Revit文档,后续所有操作都基于它
doc = Revit.ActiveDBDocument
```
把上面这段代码粘贴进去,点击“OK”关闭编辑器。虽然现在组件没有任何输入输出,也没干具体的事,但它的“内力”已经具备了。你可以在脚本最后加一句 `print("Hello RIR!")`,然后在Grasshopper里连接一个`Panel`组件到Python脚本的某个输出端,运行后看到打印信息,就证明你的Python脚本已经成功在Revit内部运行起来了。这个模板我建议你直接保存为一个**用户对象**(User Object)。具体操作是:在Grasshopper画布上右键点击这个Python组件,选择“Create User Object…”,给它起个名字比如“RIR_Python_Template”,保存。以后每次需要写新脚本时,直接从用户对象库里拖这个模板出来,省去了每次重复打那十几行导入语句的麻烦,效率提升非常明显。
## 2. 核心概念:几何与数据的双向转换
在RIR的Python脚本里干活,最核心、也最让人兴奋的一点,就是**几何与数据的双向无缝转换**。你不再需要先把Rhino模型导出为SAT或DWG,再吭哧吭哧地链接或导入到Revit里,还要担心精度丢失或图层混乱。现在,一切都在内存中实时完成。理解这一点,是玩转RIR脚本的关键。
**从Revit到Rhino**:想象一下,你已经在Revit里建好了一个复杂的幕墙系统,现在想在Grasshopper里对其嵌板进行日照或风压的分析。传统流程非常繁琐,而在RIR里,只需要几行Python代码。首先,我们可以通过Revit API的`FilteredElementCollector`收集到所有的幕墙嵌板元素,然后利用RIR提供的转换器,将Revit的`Element`几何体转换为Rhino能识别的`Brep`(边界表示)或`Mesh`。
```python
# 假设输入参数“wall”是一个从Revit通过“Graphical Element”参数拾取到的墙体
import clr
clr.AddReference("RhinoInside.Revit")
from RhinoInside.Revit import Convert
from Autodesk.Revit import DB
# 获取墙体的几何图形。DB.Options()用于设置几何提取的细节级别
geo_options = DB.Options()
geometry_element = wall.Geometry[geo_options]
# 转换几何体:遍历所有几何实例,并将其转换为Rhino的Brep
rhino_breps = []
for geo_obj in geometry_element:
# 使用RIR的转换扩展方法,将Revit几何体转为Rhino Brep
if hasattr(geo_obj, 'ToBrep'):
rhino_brep = geo_obj.ToBrep()
if rhino_brep:
rhino_breps.append(rhino_brep)
# 将rhino_breps列表输出,即可在Grasshopper和Rhino视窗中预览
a = rhino_breps
```
这段代码的精髓在于 `geo_obj.ToBrep()` 这一行。`ToBrep()` 这个魔法般的方法,正是由我们之前导入的 `clr.ImportExtensions(Convert.Geometry)` 所赋予的。它属于**扩展方法**,直接“贴”在了Revit的几何对象上,让转换变得像调用原生方法一样自然。
**从Rhino到Revit**:反过来,你在Grasshopper中生成或计算出来的炫酷形体,如何一键变成Revit里的原生构件呢?最常用的方法是创建 **`DirectShape`** 元素。`DirectShape` 是Revit API中一个非常灵活的元素类型,它可以容纳任何类型的几何形体,并赋予其一个Revit类别(Category),比如“常规模型”、“家具”等,从而纳入BIM管理体系。
```python
# 假设输入参数“rhino_brep”是一个在Grasshopper中生成的Rhino Brep
import clr
clr.AddReference("RhinoInside.Revit")
from RhinoInside.Revit import Revit, Convert
from Autodesk.Revit import DB
doc = Revit.ActiveDBDocument
def create_directshape(revit_doc, geometry, category_name="OST_GenericModel"):
"""将Rhino几何体在Revit中创建为DirectShape"""
# 1. 找到目标类别
category_id = DB.ElementId(DB.BuiltInCategory.OST_GenericModel)
# 你可以通过名称查找自定义类别
# for cat in revit_doc.Settings.Categories:
# if cat.Name == category_name:
# category_id = cat.Id
# 2. 创建DirectShape元素
direct_shape = DB.DirectShape.CreateElement(revit_doc, category_id)
# 3. 为DirectShape设置形状。注意:需要将Rhino几何体转换为Revit的几何体列表
# 使用 .ToSolid() 或 .ToCurve() 等扩展方法进行转换
revit_geoms = []
# 假设我们的geometry是单个Brep
if geometry:
# 将Rhino Brep转换为Revit Solid
revit_solid = geometry.ToSolid()
if revit_solid:
revit_geoms.append(revit_solid)
if revit_geoms:
direct_shape.AppendShape(revit_geoms)
direct_shape.Name = "从Grasshopper创建的形体"
return direct_shape
# 调用函数,并准备在事务中执行
if rhino_brep:
# 对Revit文档的修改必须在事务(Transaction)内进行
with DB.Transaction(doc, "创建DirectShape") as t:
t.Start()
new_element = create_directshape(doc, rhino_brep)
t.Commit()
# 将新创建的元素输出,可供后续组件使用
a = new_element
```
这里引入了RIR脚本中另一个至关重要的概念:**事务(Transaction)**。Revit是一个严格的数据库管理系统,任何对模型数据的修改(创建、删除、修改元素)都必须包裹在一个事务中。这就像数据库的“保存点”,要么全部成功,要么全部回滚,保证了模型的完整性。上面代码中的 `with DB.Transaction(...) as t:` 是一种非常Pythonic且安全的事务写法,确保即使脚本中间出错,事务也会被正确回滚,不会让模型停留在损坏的中间状态。
### 2.1 单位与坐标系统的隐式处理
还有一个让新手容易困惑,但RIR已经默默帮你处理好的问题:**单位**。Rhino默认使用毫米、厘米、米等公制单位,而Revit的项目单位设置则五花八门。在数据转换时,你不需要手动进行单位换算。RIR的几何转换器在背后会自动根据当前Revit项目的单位设置,对Rhino几何体的坐标和尺寸进行缩放,确保一个在Rhino中边长为1000的立方体,导入Revit后,在项目单位设置为毫米时,它依然是1000毫米的立方体。这种隐式的单位协调,极大地减少了出错的可能,让你可以更专注于设计逻辑本身。
## 3. 实战案例:自动化创建参数化幕墙嵌板
光说不练假把式,我们来看一个结合了数据获取、几何处理和创建元素的综合案例:**根据日照分析结果,自动化生成具有不同旋转角度的幕墙遮阳板**。这个案例模拟了一个真实的工作流:从Revit获取幕墙网格,在Grasshopper中进行基于向量(模拟日照方向)的计算,最后将生成的新构件写回Revit。
**第一步:获取Revit幕墙数据**。我们在Revit里先有一面普通的幕墙。在Grasshopper中,使用 `Revit > Select > Element` 组件手动拾取这面幕墙,并连接到Python脚本的输入端 `revit_wall`。
**第二步:在Python脚本中解析幕墙网格**。我们需要获取这面幕墙上所有的网格线,以及每个网格单元的中心点。
```python
import clr
clr.AddReference("System.Core")
clr.AddReference("RhinoInside.Revit")
clr.AddReference("RevitAPI")
clr.AddReference("RevitAPIUI")
from System import Enum
import Rhino.Geometry as rg
from RhinoInside.Revit import Revit, Convert
from Autodesk.Revit import DB
clr.ImportExtensions(Convert.Geometry)
doc = Revit.ActiveDBDocument
# 输入:revit_wall (一个Revit幕墙元素), sun_vector (一个Rhino向量,表示日照方向)
if revit_wall and sun_vector:
# 获取幕墙的网格系统
grid_system = revit_wall.CurtainGrid
if not grid_system:
ghenv.Component.AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "所选元素不是幕墙或没有网格。")
panel_points = []
panel_normals = []
else:
panel_points = [] # 用于存储每个嵌板中心点(Rhino点)
panel_normals = [] # 用于存储每个嵌板基于日照计算出的法线方向(Rhino向量)
# 获取所有嵌板
panels = grid_system.GetPanelIds()
for panel_id in panels:
panel = doc.GetElement(panel_id)
if panel:
# 获取嵌板的几何边界框,以找到中心点
bbox = panel.get_BoundingBox(None)
if bbox:
center = (bbox.Max + bbox.Min) * 0.5
# 将Revit的XYZ点转换为Rhino的Point3d
rhino_center = center.ToPoint3d()
panel_points.append(rhino_center)
# 简化逻辑:根据嵌板中心点到幕墙原点的向量与日照向量的叉积,计算一个旋转轴
# 实际项目中,这里可以接入更复杂的日照分析引擎结果
# 假设幕墙平面法线为Z轴(简化)
base_normal = rg.Vector3d.ZAxis
# 计算一个与日照方向相关的旋转轴(垂直于日照方向和基法线)
rotation_axis = rg.Vector3d.CrossProduct(base_normal, sun_vector)
rotation_axis.Unitize()
# 根据日照向量与基法线的夹角,计算一个旋转角度(这里简化计算)
import math
dot = rg.Vector3d.Multiply(base_normal, sun_vector)
angle = math.acos(min(max(dot, -1.0), 1.0)) * 0.5 # 取一半角度作为示例
# 旋转基法线,得到新的面板方向
rotated_normal = base_normal
rotated_normal.Rotate(angle, rotation_axis)
panel_normals.append(rotated_normal)
# 输出:嵌板中心点和计算出的法线方向
a = panel_points
b = panel_normals
else:
a = []
b = []
```
**第三步:根据计算出的位置和方向,生成遮阳板几何体**。上一步输出了每个嵌板中心点和一个目标法线向量。我们可以在Grasshopper下游用另一个Python脚本,在每个点创建一个小型实体(比如一个倾斜的box),作为遮阳板。
```python
import clr
import Rhino.Geometry as rg
# 输入:panel_points (点列表), panel_normals (向量列表), panel_size (数值)
if panel_points and panel_normals and len(panel_points) == len(panel_normals):
shade_breps = []
size = panel_size
for pt, normal in zip(panel_points, panel_normals):
# 1. 创建一个小立方体,中心在原点
plane = rg.Plane(rg.Point3d.Origin, normal)
# 让平面的X轴朝上(与世界Z轴叉乘得到),确保立方体不倾斜
x_axis = rg.Vector3d.CrossProduct(plane.ZAxis, rg.Vector3d.ZAxis)
if x_axis.Length > 0.001:
plane.XAxis = x_axis
plane.YAxis = rg.Vector3d.CrossProduct(plane.ZAxis, plane.XAxis)
else:
# 如果法线几乎垂直,使用世界XY平面
plane = rg.Plane(rg.Point3d.Origin, rg.Vector3d.XAxis, rg.Vector3d.YAxis)
# 2. 根据尺寸创建长方体
box = rg.Box(plane, rg.Interval(-size/2, size/2), rg.Interval(-size/2, size/2), rg.Interval(0, size*0.2))
brep = rg.Brep.CreateFromBox(box)
if brep:
# 3. 将立方体移动到嵌板中心点
transform = rg.Transform.Translation(pt.X, pt.Y, pt.Z)
brep.Transform(transform)
shade_breps.append(brep)
# 输出生成的遮阳板几何体
a = shade_breps
else:
a = []
```
**第四步:将生成的遮阳板几何体批量创建为Revit的“常规模型”**。这里我们使用前面提到的`DirectShape`方法,但这次要处理批量创建。为了提高效率,我们可以将所有几何体放在一个事务中创建。
```python
import clr
clr.AddReference("System.Core")
clr.AddReference("RhinoInside.Revit")
clr.AddReference("RevitAPI")
clr.AddReference("RevitAPIUI")
from System.Collections.Generic import List
from RhinoInside.Revit import Revit, Convert
from Autodesk.Revit import DB
clr.ImportExtensions(Convert.Geometry)
doc = Revit.ActiveDBDocument
new_elements = []
if shade_breps:
with DB.Transaction(doc, "批量创建遮阳板DirectShape") as t:
t.Start()
try:
category_id = DB.ElementId(DB.BuiltInCategory.OST_GenericModel)
for i, brep in enumerate(shade_breps):
# 将Rhino Brep转换为Revit Solid
revit_solid = brep.ToSolid()
if revit_solid:
# 创建DirectShape
ds = DB.DirectShape.CreateElement(doc, category_id)
ds.AppendShape([revit_solid])
ds.Name = f"参数化遮阳板_{i+1:03d}"
new_elements.append(ds)
t.Commit()
print(f"成功创建 {len(new_elements)} 个遮阳板。")
except Exception as e:
t.RollBack()
ghenv.Component.AddRuntimeMessage(GH_RuntimeMessageLevel.Error, f"创建失败: {e}")
# 输出新创建的元素列表
a = new_elements
else:
a = []
```
通过这个案例,你可以看到,一个完整的自动化流程被拆解成几个逻辑清晰的Python脚本组件,通过Grasshopper的导线连接起来。每个组件各司其职,代码易于理解和维护。你可以随时调整日照向量的输入,或者改变遮阳板的生成规则,整个模型和Revit构件都会实时联动更新。这种动态、可探索的设计流程,正是参数化设计与BIM结合的魅力所在。
## 4. 高级技巧:在代码中调用Grasshopper组件与错误处理
当你越来越熟练后,可能会发现有些逻辑用纯Python写起来很复杂,但Grasshopper里恰好有一个现成的组件能完美解决。难道要放弃Python吗?当然不!RIR提供了一个名为 **“Node In Code”** 的强大功能,允许你在Python脚本内部,像调用函数一样直接调用Grasshopper画布上的任何组件。这相当于你拥有了一个由无数个现成函数组成的超级武器库。
比如,你想在脚本里给创建的元素赋予一个复杂的材质,这个材质有名称、颜色、透明度、纹理等多个参数。自己用Revit API写可能要几十行代码。但如果Grasshopper里有一个 `RhinoInside_AddMaterial` 组件,你可以这样用:
```python
import clr
clr.AddReference("RhinoInside.Revit")
from Rhino.NodeInCode import Components
import System
# 1. 通过组件名称查找组件
add_material_comp = Components.FindComponent("RhinoInside_AddMaterial")
if add_material_comp is None:
ghenv.Component.AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "未找到‘添加材质’组件。")
else:
# 2. 像调用函数一样调用组件。Invoke方法的参数顺序对应组件输入端从上到下的顺序。
# 假设组件输入为:材质名(字符串), 是否着色(布尔值), 颜色(System.Drawing.Color)
material_color = System.Drawing.Color.FromArgb(255, 100, 150, 200) # ARGB颜色
# Invoke 返回的是一个列表,包含了组件所有的输出
result_list = add_material_comp.Invoke("我的自定义蓝色材质", True, material_color)
# 通常第一个输出就是创建的材质元素
if result_list and len(result_list) > 0:
new_material = result_list[0]
# 现在你可以将这个材质赋给之前创建的DirectShape
# ... (后续代码)
```
这个技巧极大地扩展了Python脚本的能力边界。你可以将一系列标准的、复杂的操作封装成Grasshopper电池组,然后在Python脚本里作为“子程序”调用,从而构建出高度模块化、可复用的超级脚本。
**关于错误处理**:在自动化流程中,健壮性至关重要。你的脚本可能会因为各种原因失败:用户选择了错误的元素、几何转换意外返回了`None`、Revit事务冲突等等。一个专业的脚本必须能优雅地处理这些情况,给出明确的提示,而不是让整个Grasshopper定义崩溃或让Revit卡死。除了前面用到的事务中的 `try...except` 块,在Python组件内部,我们应该充分利用 `ghenv.Component.AddRuntimeMessage` 方法向用户反馈信息。
```python
from Grasshopper.Kernel import GH_RuntimeMessageLevel as RML
def validate_input(input_data, input_name):
"""验证输入是否有效"""
if input_data is None:
ghenv.Component.AddRuntimeMessage(RML.Warning, f"输入‘{input_name}’为空,请检查连接。")
return False
if isinstance(input_data, list) and len(input_data) == 0:
ghenv.Component.AddRuntimeMessage(RML.Remark, f"输入‘{input_name}’是空列表。")
return False
return True
# 在脚本主逻辑开始前进行验证
if not validate_input(my_walls, "墙体"):
# 如果验证失败,可以提前返回或设置默认输出
a = []
return
# 对于关键操作,使用更细致的异常捕获
try:
some_risky_operation()
except ValueError as e:
ghenv.Component.AddRuntimeMessage(RML.Error, f"数值错误: {e}")
a = []
except DB.ArgumentException as e:
# 专门捕获Revit API的参数错误
ghenv.Component.AddRuntimeMessage(RML.Error, f"Revit API参数错误: {e.Message}")
a = []
except Exception as e:
# 捕获其他所有未预见的错误
ghenv.Component.AddRuntimeMessage(RML.Error, f"发生未知错误: {type(e).__name__}: {e}")
a = []
```
良好的错误处理和信息反馈,能让你的脚本工具显得更加可靠和专业,也方便其他同事或未来的你进行调试和维护。记住,写脚本不仅仅是让机器执行任务,更是为了让人(包括你自己)能更轻松、更安全地使用它。