你好,这里是BIMBOX。
今天是临时加餐,分享一个很有意思的教程给你,是我们和一位小伙伴两个通宵的成果。
它能解决一个很麻烦的问题,更重要的是它能启发大家的思路:怎样用尽量简单的自动化,帮助我们去做那些重复枯燥的工作。
事情的缘由是这样的。
还记得@Vctcn93吗?那个跟我们一起合作,出了《为所欲为:Dynamo信息可视化脚本实战》的Dynamo小神仙。
最近他一直在琢磨怎么通过Python编程和Dynamo带来更高的生产力,也经常在群里和大家探讨,回答教程问题,已经成了群宠。
上周三,在我们的BIMBOX莱茵河清流群里,小伙伴@Null提问,对于大量形状类似、尺寸不同的族,有没有办法快速的建出来。
Vctcn93回答说,生成多个类似的族是有规律的,可以用Dynamo来实现。也有小伙伴提出,可以通过参数化建族来搞定,但用Dynamo能打开更多的思路。
于是,Vctcn93就和Null要来了图纸,答应大家周末抽空把节点和教程给写出来。
不过,在叫了对方好几声哥们之后,经@开开提醒,Null是女生,事情的发展又比预想的迅猛很多。
周六一早,我就收到了Vctcn93的微信,一看时间是凌晨五点发来的。本来答应周末慢慢搞的事,居然搞了个通宵。
尽管他和我说,主要是因为本来觉得很简单的一个脚本,越琢磨越有意思,好玩的知识点很多,于是越写越兴奋,这才搞了8个小时。
我仔细看了一下他的思路,确实很有启发。于是答应下来,拿出时间把他的思路和成果整理成大家更容易阅读的文章,免费分享给所有人看。
这份教程中涉及到一些Dynamo的基础知识,以及几段Python代码,如果你暂时搞不明白原理,也不影响阅读今天的文章,我们会在代码处加入中文说明。
暂时不想做,也可以花十几分钟读一遍,重点看看思路,领略一下Dynamo和Python的自动化之美,以及用自动化解决重复问题时那种灵光乍现的酸爽。
下面正式开始——
01 自适应族的拓扑关系
首先你需要掌握一点基础知识。
Revit 中有一个十分强大族类型——自适应构件(Adaptive Component),一般多用于处理幕墙相关的问题。
我们先到 Revit 中制作一个简单的自适应族,来搞清楚自适应构件的原理。
➤ 打开 Revit 2018,新建一个 自适应构件 族。
➤ 在面板中找到 点图元 工具,并且添加四个点。
➤ 全选点,然后点击上部面板下的 使之自适应 按钮。
它们就变成了这个样子:
➤ 使用线工具,并打开 三维捕捉。
➤ 依照 点 上方的序号(index),依次链接各点。
➤ 选中全部的线,点击 创建实体形状。
这时,我们就成功创建了一个简单的 自适应嵌板。
左侧的属性栏可以调整厚度。
➤ 保存一下,新建一个项目,把建立的族加载进项目中,只要给定任意的四个点(按顺序点击四下),就能得到依照点生成的嵌板。
以上就是制作一个简单自适应族的过程。
想要弄清楚点击四下的时候发生了什么,我们先要了解一个图形学名词 ——拓扑关系(Topological Relation)。
拓扑关系是图形学中的常用概念,本质就是把各种图形,抽象简化为各个点之间的关系。有了这些基本的关系,我们则不需要在乎它绘制出来究竟是什么形状。
比如正方形、梯形、四边形、菱形等等,尽管在形状上有很多不同,但是在拓扑的视角看,这些点都满足依次使点成环的关系,它们是拓扑等价的。
我们在制作 自适应族构件的过程中,有一个重点操作叫使之自适应,它的作用是记录各个点的位置、顺序、谁与谁相连等拓扑关系。
这时每个点都会生成一个序号,它将记录自己在这一拓扑关系中的顺序。
当我们使用线将各个点连接起来的时候,这个点就会记录下另外哪两个点和自己相连,而它不会在乎这两个点在空间中的什么位置。
这些顺序和连接关系就是预先保留在族里的默认信息。
我们在使用这个族的时候,点击四下,这个操作是告诉Revit预先设定的四个点的空间坐标,它们是我们使用族的输入信息。
族在项目里最终呈现什么状态,就是下面的公式:
族的输出状态=预设定默认信息 使用时的输入信息
而我们想要让族「自动化」,就是尽量多的解决默认信息,从而减少输入信息的工作量。
在这个族里,输入信息的重点在于顺序,它决定了点的序号,决定了谁去找谁连线,决定了拓扑关系,从而决定了最终的形状。
想让 Dynamo 来代替我们放正确的 自适应族 的时候,一定要保证输入的点顺序是正确的。
02 Dynamo中的强大节点在 Dynamo 中,有一个使用自适应族的强大节点:
AdaptiveComponents.ByPoints
这个节点有两个输入,第一个是points , 一个二维数组的点。
第二个是familyType ,它是自适应族的类别。
结合我们刚刚所学的拓扑关系知识,我们来模拟与理解一下这个节点的输入要求。
假设我们有一个需要四个点生成的自适应嵌板,则我们先来模拟建立点的二维数组,下面的代码你不懂python也能大概看懂:
points = [ [ Point.ByCoordinates(0, 0, 0), Point.ByCoordinates(1000, 0, 0), Point.ByCoordinates(1000, 1000, 0), Point.ByCoordinates(0, 1000, 0) ], [ Point.ByCoordinates(1000, 0, 0), Point.ByCoordinates(2000, 0, 0), Point.ByCoordinates(2000, 1000, 0), Point.ByCoordinates(1000, 1000, 0) ], [ Point.ByCoordinates(2000, 0, 0), Point.ByCoordinates(3000, 0, 0), Point.ByCoordinates(3000, 1000, 0), Point.ByCoordinates(2000, 1000, 0) ] ];
这段代码,Dynamo 中的 CodeBlock 与 Python 都可以直读,结果会生成一个 点阵(Points Array)。
在上面的 points数组(List)中,每一个元素都是一个小数组,每个小数组里又包含了四个点。
这四个点,就是我们所需要每生成一块嵌板所需要的点,点的顺序则是我们在 Revit 界面中放置嵌板的点击顺序。
我们把数据点阵和族类型Family Types连接到AdaptiveComponents.ByPoints这个节点上。
依照上面的点数据,Dynamo 便会依照每一个嵌板的输入点,生成 3个 自适应嵌板。在Dynamo 中看不见族的加载结果,我们得切换回 Revit 界面中才能看到。
有多少个 points 数据,将决定我们生成多少块玻璃嵌板,嵌板中的点,即是嵌板的拓扑关系,Dynamo 将根据这四个点的位置与顺序,把嵌板放进去。
这就是在AdaptiveComponents.ByPoints节点中发生的事情,也是 Dynamo 要求输入这种数据结构的原因。
顺序非常关键。
正确地抽象我们要的形状,把抽象所得的点正确地排序与组合,是我们在使用这个节点之前的关键操作。
03 解决问题@Null在群里的问题是,怎样建出多个形状类似的窗族。
为了降低难度,我将把她的图简化为方方正正的矩形,制作的时候也先暂时忽略尺寸,我们需要根据这个矩形,来运用一下刚刚所学到的东西。
➤ 制作Dynamo脚本之前,我们需要先思考一下:
· 我想要那些参数? · 未来能否扩充与异化? · 是否有优化效率的可能?
先来看第一个问题,对于这个简化版的脚本,我需要的参数是:
· 每块面板的长度 · 每块面板的高度 · 面板的数量
➤ 这些参数当然不能一个个手动输入,所以在一开始,我们就把这三个参数做成 Number Slider节点,放置到界面的最左边,把它们重命名,并编组为 参数调节:
这里需要注意两点:
· 单位是毫米,所以面板长度与面板高度的间距(step)最好以 100 为基准单位。
· 嵌板的个数必须是整数,所以面板个数的间距(step)一定要是整数。
➤ 下面,我们需要一个原点(origin),作为面板的起始位置,在这里我将使用 (0,0,0)点,你也可以设置其它原点。
➤ 接下来,我们要依照给定的面板长度以及面板数量,生成一个以面板长度为间距(step),以面板数量为个数的数组(list),创造需要用到的第一组点。
此处使用 Code Block 代码来完成:
0..#quentity..#step
➤ 然后把面板长度与面板个数分别输入到step与quentity两个输入之中,便可生成第一个数组,也就是未来各点的 x 坐标值:
➤ 有了各点的 x 坐标值,我们便可以使用 Geometry.Translate 节点,把原点按照坐标批量生产。
➤ x轴方向搞定后,接下来以同样的方法,把这条线上的点,依照嵌板高度,在 y 方向上做条一模一样的出来:
到此为止,我们便制作了一个参数化、实时可调的点阵(Points Array)。
➤ 接下来要做的就是要把生成的点阵,处理为一个按照顺序、包含正确拓扑关系的二维数组。
根据我们前面说的拓扑知识,输入进 AdaptiveComponents.ByPoints 节点中的点顺序,一定要正确反应嵌板的拓扑关系。
比如我在使用Revit建立嵌板族的时候,点击的顺序是从左下角开始逆时针旋转:
所以,我们的点组顺序,也要按照这样的方式排列:(注意Dynamo第一个点的序号是0而不是1)。
我们将刚刚生成的两组点,使用 List.Create 节点打包为一组数据。这样,就可以使用 Python处理我们的点阵了。
➤ 接下来就是Python 数据处理。
当然,到这一步不使用Python也可以使用,但回顾一下我们开始写脚本之前思考的第二个问题:
· 我想要那些参数? · 未来能否扩充与异化? · 是否有优化效率的可能?
虽然目前我们的数据很少、也很具体:两条线,每条线几十个点,我们可以用简单的代码把它写出来。但是,为了日后的扩充与异化,我们一定要把代码进一步处理成代数问题,把它抽象成 m 条线,每条线 n 个点。
在这种思路下,我们来开始写下面的脚本。这里有点烧脑,看不懂代码没关系,可以看看中文代码说明:
# 启用 Python 支持和加载 DesignScript 库 import clr clr.AddReference('ProtoGeometry') from Autodesk.DesignScript.Geometry import * # 该节点的输入内容将存储为 IN 变量中的一个列表。 dataEnteringnode = IN # 将代码放在该行下面 __author__ = 'Vctcn93' __date__ = 20190706 __publisher__ = 'ArchiPython' data = IN[0] # 将刚刚的数据,赋予到变量 data 中 result = list() # 创建一个用以装载结果的空列表 for i in range(len(data) - 1): # 遍历每条线 current_line = data[i] # 当前的线 next_line = data[i 1] # 下一条线 for k in range(len(current_line) - 1): # 拿取线上的每一个点 node = list() # 嵌板点组 node.append(current_line[k]) # 添加第 0 号点 node.append(current_line[k 1]) # 添加第 1 号点 node.append(next_line[k 1]) # 添加第 2 号点 node.append(next_line[k]) # 添加第 3 号点 result.append(node) # 把嵌板点组添加到结果中 # 将输出内容指定给 OUT 变量。 OUT = result # 输出结果
搞定之后,就可以在Dynamo中使用以上代码,把点阵处理清楚了。
➤ 最后使用 AdaptiveComponents.ByPoints 节点,在 Revit 中成功生成玻璃嵌板:
➤ 再来测试一下参数实时调整,所以依据面板长度、面板高度、面板个数几个参数的不同,可以有很多的结果,并且可实时变化:
我们把到此为止的成果储存为「初始版.dyn」,下载链接见文末。
04 异化到此为止,我们用Dynamo实现的功能还和一个嵌套阵列族差不多。
不过,每次建立一个程序,我们都要思考,能不能再它的基础上,用抽象思维去解决更多的事?
这个操作就叫异化。
比如这个案例,可以思考一下:能不能任意形状都能自动生成?
这里我将把上面这个脚本做一种异化,修改为根据任意 N 条 Revit 中的线,生成自适应异形幕墙。
➤ 首先,在 Revit 中,使用模型线工具绘制 4条 三维曲线:
注意:这些线可以足够异型,但不可以在同一标高上,这样才能构成正确的拓扑关系。
➤ 下面,我们在 Dynamo 中使用 Select Model Elements 节点,将这四条线由下至上(你看,顺序真的很重要)选进 Dynamo 中。
➤ 接下来,再使用 Element.Geometry 的方式让这些线在 Dynamo 中可见,且能被处理。
➤ 关于参数的设置,因为目前是弧线且不等长的缘故,再想让嵌板数量与嵌板长度兼得可调是不可能了,所以我们可以仅要嵌板长度这一个可调参数。
还没完,嵌板数量也是一个很重要的参数,我们该怎样获取呢?
这时候就要请出高等数学了!
呃,是小学数学,就是取个平均值啦。
➤ 先算出四条线的平均长度,再使用平均长度除以嵌板长度,便是我们的嵌板数量。可是由于这个值除出来之后很有可能是小数,我们可以取最接近这个值的整数,作为每条边的嵌板数量。
这一步是怎么来的呢?
你还需要明白一个重要知识点:样条曲线的parameter。
parameter 是把样条曲线的起点值当作 0,终点值当作 1,不管样条曲线多长,多曲折,都具有这样的属性。
比如,任何一条样条曲线中点的 parameter 都为 0.5,三分点无限趋近于 0.333 或 0.666。
知道了这样的一个特性,那么我们想把一条样条曲线均分为 嵌板个数 段,只要把 0 到 1 均分为长度为嵌板个数的数组就好了。
使用 Code Block 代码:
0..1..#steps
➤ 再将嵌板个数输入进 steps 中,就做到了。
➤ 然后再使用针对样条曲线的节点 Curve.PointAtParameter 获得在这些值上的点,便得到了我们需要的点阵。
➤ 有了这样一个点阵,便可以使用刚刚的 Python 代码,把这些点处理为含有正确拓扑关系的点组。
刚刚编写代码的时候,是用的抽象方法,所以不用改一个字,就能直接让新的脚本用上它,这就是抽象的好处。
➤ 最后使用 AdaptiveComponents.ByPoints 节点,就能在 Revit 中找到做好的嵌板了。
同样,这个结果也是实时可调的,不过由于样条曲线运算复杂,调整起来会有点卡。
我们把到此为止的成果储存为「异化版.dyn」,下载链接见文末。
05 小姐姐的需求写到这儿有点放飞自我了,差点把小姐姐最初的需求给忘了。
之前我们实现的是方方正正的族和奇奇怪怪的族,而这个案例最终还要解决图纸上窗户族两侧的三角形。
实现它并不复杂,也就是两端的点有些许偏移而已。
按照我们的顺序,是第一块嵌板的最后一点,与第最后一块嵌板的第二点。
只需要在初始版成果的基础上,加多两个参数:左端偏移与右端偏移就可以解决问题了。
➤ 先打开初始版.dyn,加两个参数:
➤ 再回到我们第一次用 Python 处理完成之后的点组位置:
➤ 由于 Dynamo 自带的节点更改数据十分麻烦,所以我们再请出 Python 写一个脚本,修改目前点数组中,第一组第4点,以及最后一组第2点的值,给它们做一个偏移。
Python代码如下:(同样,看不懂没关系,看看中文说明了解个大概。)
# 启用 Python 支持和加载 DesignScript 库 import clr clr.AddReference('ProtoGeometry') from Autodesk.DesignScript.Geometry import * # 该节点的输入内容将存储为 IN 变量中的一个列表。 dataEnteringNode = IN # 将代码放在该行下面 __author__ = 'Vctcn93' __date__ = 20190706 __publisher__ = 'ArchiPython' data = IN[0] # 将刚刚的数据,赋予到变量 data 中 left_offset = IN[1] # 左端偏移值 right_offset = IN[2] # 右端偏移值 point1 = data[0][3] # 拿取第一组最后一点的点坐标 point2 = data[-1][1] # 拿取最后一组第二点的点坐标 data[0][3] = Point.ByCoordinates(point1.X - left_offset, point1.Y, point1.Z) # 重新创造偏移后的点,取代原先的点 data[-1][1] = Point.ByCoordinates(point2.X right_offset, point2.Y, point2.Z) # 重新创造偏移后的点,取代原先的点 # 将输出内容指定给 OUT 变量。 OUT = data # 输出结果
在Dynamo里形成这样一个脚本,它有IN[0]、IN[1]、IN[2]三个预留的输入,作用就是「输入一组数,然后修改其中的两个,再输出」。
随后将我们原始点阵连接到 IN[0]; 左端偏移连接到 IN[1]; 右端偏移连接到 IN[2]。这样就生成了我们所需要的新点阵。
➤ 新点阵通过 AdaptiveComponents.ByPoints 节点,输入到 Revit 中,我们便得到了左端偏移、右端偏移、嵌板个数、嵌板长度、嵌板高度,这五个数值可调的参数化构件了。
我们把最终的成果储存为「小姐姐版.dyn」,下载链接见文末。
Vctcn93的教程结束了,最后来给你总结一下,说说我们的观点。
这个教程主要帮你梳理了三个知识点:
· 拓扑关系与自适应族的概念 · 样条曲线的 Parameter · 如何有效地使用 Python 为自己加速
打通了这几个思路,再遇到类似的幕墙、描述规律等问题,都不再会是难点了,也为今后参数化设计打下坚实的基础。
这也是他为之前在知乎上免费开源写的一系列文章《Dynamo速成大法》填的一个坑。
说起这个系列,Vctcn93已经停止更新了,原因是本来希望做开源分享的事。结果被人抄袭,还申请了原创保护,维权很难,Vctcn93一气之下就把对方还没来得及抄袭的内容全删了。
侵权真的会让人伤心的。
BIMBOX长期以来对版权非常重视,既不抄袭转发,也对抄袭者绝不姑息,加上我们有强大的小伙伴们长期帮忙投诉抄袭者,Vctcn93才愿意把这篇心血之作发布在这里。
大家最终看到的这篇东西,来自于社群里每一个人的高质量提问、不撕逼的交流氛围、对版权的共同维护、还有小伙伴们像朋友一样的彼此信任。
谢谢大家了。
发文多了,我们也会经常收到这样的评论:「你的这个不行,XXX用XXX早就实现了。」
我们一般对这类回复的态度是不抬杠、也不理会。
原因并非因为他说的不对,而是我们觉得,人的表达分为两种:
第一种是告诉你「这件事我知道」;第二种是「我想告诉给你,让你也知道。」
高水平的人,也会有低水平的分享,而我们更关注「分享」的水平。
因此,我们践行的始终是第二种表达方法。这也是从不转发、对投稿审核极严的我们愿意精编和发布这篇文章的原因。
正如Vctcn93自己所说:拒绝嘴遁,从我做起。
学习今天的教程,关于Dynamo部分如果感觉有点吃力,可以知乎搜索(一怒断更的)「Dynamo速成大法」,也可以支持一下Vctcn93与我们共同发布的视频教程《Dynamo信息可视化脚本实战》,里面有大量基础知识给你学习。
如果对Python感兴趣,想深入学习,知乎上搜索「九章Python」,是Vctcn93写的入门学习教程。
最近群里也常聊Python和Dynamo,网上动辄100G的教程资料让人望而却步,我们正在和Vctcn93计划,一起做一系列的好教程,留下最有用的东西,帮大家节省时间。
今天的教程完全免费,建议收藏随时看。Vctcn93做的三个Dynamo成果也免费分享给大家,下载链接:https:///s/1sSEfsQK_VVdFzk88RZgIsw
提取码:z808
有态度,有深度,BIMBOX,咱们下次见!
,