Created on Mon Feb 28 07:17:37 2022; Last updated on Mon Aug 26 19:25:33 2024 @author: Richie Bao

几何体为存在于空间中的对象,定义的空间本身具有空间的三维属性。几何体在空间中的位置 一般是由参考平面Vector->Plane定义与控制,从默认的三维世界空间体系 XY、XZ 及 YZ 方向上的基本参考平面到组成几何体的面所具有的参考平面属性,或者由基本的参考平面偏移、旋转、移动变换得到新的参考平面。几何体不仅存在位置的属性,移动等变换的操作需要向量Vector->Vector来指引变换的方向, 向量同样存在于默认三维世界空间体系的三个方向,X、Y 和 Z 方向上的向量,同时线、面几何体本身具有向量的属性。线上一点的向量一般是该点的切线方向,面则是该点的切面具有的 X、Y 和 Z 方向,是垂直或相切于面的向量。不管是参考平面还是向量,实际操作过程中,除了自行构建向量和参考平面用于空间或几何体形态的变换,应更多关注从几何体本身提取的参考平面和向量,用其进一步操作几何体的变换,例如提取线上各点垂直于该线的参考平面,并在各个参考平面上绘制截面或者将面上的点垂直于该面的向量移动对象等。

参考平面和向量是几何体空间位置和方向的基本属性,建立、提取与操作变换参考平面和向量通常是以空间点Vector->Point为条件,而空间点也是建立线,由线再构成面,由面成体的条件。

1.3.2.1 点(Point):一切的基础

用于操作点的组件位于Vector->Point下,包括由 X、Y 和 Z 坐标建立点,或基于空间几何对象建立点的方法。也包括操作点的基本方法,例如寻找最近点、分组(聚类)点、投影和排序点等。

A组:

B组:

🍋‍🟩代码段-练习-1: (    )

图 1.3.2-1中,用Vector->Field->Point Charge组件建立多个点磁场,用Evaluate Field组件根据提供的点阵提取点所在磁场位置的向量及大小。除了用Point Charge组件输入端Charge控制磁力大小,一般也要将控制磁场的点垂直偏离点阵平面变化磁场。因为磁场中的向量方向和大小是连续变化的,因此可以生成具有连续变化的形态。控制好生成磁场的点位置(包括垂直位置),及大小Charge、衰减距离Decay,可以避免生成的曲线互相穿插。如果要将磁场干扰的图形作为铺地图案处理,则需要调整 Z 方向向量为零;或通过 Z 值的变化构建连续的三维空间。Diconstruct VectorVector XYZDiconstructConstruct Point,及Points to NumbersNumbers to Points经常配合使用,来调整某一组成值的大小。

gh

图 1.3.2-1 代码段(点)-1与结果

代码下载(codeSnippet_position_01.gh)

🍋‍🟩代码段-练习-2: (    )

图 1.3.2-2中代码的目的是计算给定位置建筑遮挡视线数量(面积)百分比,常用于城市开放空间的开敞度分析。通过调整Point Polar组件参数值,控制所要构建视线向量分布的密度,越密的点(向量)计算精度越高,具体精度大小根据分析要求和算力确定。

同时定义了一个随机生成建筑楼群的逻辑,在给定范围内生成随机点,由点建立多个随机大小的矩形进行融合,并垂直拉伸随机高度构建而成。如果在这个简单的逻辑中加入建筑占地面积控制,高度控制,容积率控制,优化简单的立方形几何体,可生成更符合规划需求的建筑体形态,用于规划参照。

gh

图 1.3.2-2 代码段(点)-2与结果

代码下载(codeSnippet_position_02.gh)

C组:

🍋‍🟩代码段-练习-3: (    )

David Rutten在回答How to change a point grid’s density with an attractor?问题时,给出了下述算法,如图 1.3.2-3。将Closest Point组件求得的最近点距离值通过Graph Mapper,将给定区间[0,10](根据图形尺寸,及实际需求调整,即X值)之间的值由曲线映射到[-1,1](即Y值)之间,不在[0,10]区间的值,统一输出为-1(即 y 的最小值),并与生成位于[-0.5,+0.5]区间的随机值相加,这类似于遗传算法中的变异,将值进行微小的随机变化,那么将有一部分非-1值可能小于-1值,而一部分-1值可能到[-1,-0.5]区间。通过上述算法,使得给定点(attractor,吸引子)附近点的提取更加自然。

gh

图 1.3.2-3 代码段(点)-3与结果

代码下载(codeSnippet_position_03.gh)

🍋‍🟩代码段-练习-4: (    )

利用吸引子(attractor)的吸力或斥力作用,通过建立各异的逻辑生成或变化空间几何对象,可以产生出数不胜数的空间形态。图 1.3.2-4 这个练习来源于attractorFields tools,其原始的两个标准化和最小值限制的算法实现是由 C# 编写,这里将其转换为 Python 代码实现,并分别命名为Normalization[0,1]Minimum limit

这个吸引子构建的逻辑是由Closest Point组件获取栅格各点到吸引子(Cloud)距离最近的那个点,即由多个吸引子各自提取最近多个栅格点。建立吸引子到栅格点的直线,通过Normalization[0,1] Python 代码实现距离值(输入端 x)由提供的最大(Max)、最小(Min)值参数映射到 [0,1] 区间。具体算法为,如果距离值位于最大和最小值区间内,则返回$x/Max$;如果大于最大值则为1,小于最小值则为0。值映射到[0,1]区间后,由Graph Mapper组件经 Gaussian 图形函数(从组件上右键菜单中选择),将 [0,1] 区间的映射值变化到给定 [0,1] 区间的图形曲线上,示例中调整的函数曲线将会反转输入的数值,较小区间的值趋近于1,而较大区间的值趋近于 0。Minimum limit可以进一步控制最小值的限制,即如果值x小于给定的最小值参数minV,则将其值置换为minV值。将处理好的数据通过BoundsRemap Numbers组件配合映射到给定的区间[0,1]用于Evaluate Curve组件在直线上提取点。

gh

图 1.3.2-4 代码段(点)-4与结果

Normalization[0,1] (Python Script 组件)

  """
将输入端 x 输入的列表,根据输入端 Max 和 Min 的取值,映射到[0,1]区间(标准化)
Inputs:
    x: list[float]
        用于标准化的数据列表
    Max: float
        控制最大取值,大于该值的 x 均映射为1
    Min: float
        控制最小取值,小于该值的 x 首先均映射为0
    noZeroB: bool
        是否将映射值为0的值,赋予输入值 noZeroV
    noZeroV: float
        如果 noXeroB 为 True,则用该值替换值为0的x的映射
Output:    
    A:list[float]
        标准化 x 后的值列表
"""
import rhinoscriptsyntax as rs

def Normalization_0to1(x,Max,Min,noZeroB,noZeroV):
    '''
    输入参数(Parameters/Inputs)和返回值(Returns/Outputs)同组件注释
    '''
    if x > Min:
      if x < Max:
        b = x / Max
      else:
        b = 1
    else:
      b = 0
    if noZeroB:
      if b == 0:
        A = noZeroV
      else:
        A = b
    else:
      A = b
      
    return A

A=Normalization_0to1(x,Max,Min,noZeroB,noZeroV)
  

Minimum limit(Python Script 组件)

  """
用输入端 minV,截断输入端 x 数值列表,将小于 minV 的值初步均映射为 minV
Inputs:
    x: list[float]
        用于截断的数值列表
    minV: float
        截断值
    noZeroB:bool
        是否将截断后值为0的 x(b) 值赋予值为输入端 noZeroV的值
    noZeroV:float
        替换截断后值为0的 x(b) 的值
Output:
    a: The a output variable
"""
import rhinoscriptsyntax as rs

def Minimum_limit(x,minV,noZeroB,noZeroV):
    '''
    输入参数(Parameters/Inputs)和返回值(Returns/Outputs)同组件注释
    '''
    if x < minV:
      b = minV
    else:
      b = x
    if noZeroB:
      if b == 0:
        A = noZeroV
      else:
        A = b
    else:
      A = b
    return A
    
A=Minimum_limit(x,minV,noZeroB,noZeroV)
  

代码下载(codeSnippet_position_04.gh)

🍋‍🟩代码段-练习-5: (    )

图 1.3.2-5 练习代码段的编写思路为:

  1. 将练习代码段-2 部分的代码调整封装为建筑布局_简版的封装组件,在条件设定上增加了控制线,可以空出控制线所在的区域。并由建筑布局_简版随机生成给定区域的建筑组合;
  2. IsoVist组件计算水平向视域;
  3. 通过Pull Point组件求取位置点到最邻近建筑对象上的点来计算各建筑到路径位置点的“可达”数,相对较高的可达数表明从道路上越容易到达的建筑。 为方便统计频数,GHPython编写frequency组件完成计算。

在 1.3.1 章节部分有部分练习为动态的观察形态变化,使用的方法是直接拖动Number Slider组件产生数值的变化。为避免手动拖动,参考Start Trigger Component with Python给出的方法,用 Python Script 编写计数器,其中输入端Step输入参数可以控制数值变化的步幅。

gh

图 1.3.2-5 代码段(点)-5与结果:动画

gh

图 1.3.2-5 代码段(点)-5与结果

counter(Python Script 组件)

  """
    自定义计数器,类似 GH 中的 Trigger
    Inputs:
        Run: bool
            是否开始计数
        Reset: bool
            重新计数
        Step: float
            步幅值
        Target: float
            目标结束值
        Interval: int(System.Int32)
            解决方案完成和计划解决方案开始之间的延迟(以毫秒 ms 为单位)。如果延迟为 1ms 或更多,则将使用计时器在将来某个时候触发新的解决方案。如果使用 0ms 的延迟,则下一个解决方案将在当前解决方案中递归运行,这可能会可导致 Rhino 进程耗尽堆栈空间,尽量避免配置为 0ms
            ref:https://mcneel.github.io/grasshopper-api-docs/api/grasshopper/html/M_Grasshopper_Kernel_GH_Document_ScheduleSolution_1.htm
        
    Output:
        Counter: dict(collections.Counter)
            以字典形式返回计数结果
"""

import Grasshopper as gh

def updateComponent(interval):   
    
    def callBack(e):
        ghenv.Component.ExpireSolution(False)
        
    ghenv.Component.OnPingDocument().ScheduleSolution(interval,
        gh.Kernel.GH_Document.GH_ScheduleDelegate(callBack))

# 重置计时器变量 count(Instantiate or reset persistent counter variable)
if "count" not in globals() or Reset:
    count = 0

# 更新变量和组件(Update the variable and component)
if Run and not Reset and count < Target:
    count +=Step
    updateComponent(Interval)

# 输出计数(Output counter)
Counter = count
  

frequency(Python Script 组件)

  """
    计算可迭代对象(list)中元素的出现次数。(未将 Python 中的 dict 类型数据转化为 GH 的 tree 型数据输出)
    Inputs:
        List: list[ghdoc Object]
            用于计算频数的输入列表
    Output:
        Counter: dict(collections.Counter)
            以字典形式返回计数结果
"""

import collections
Counter = str(collections.Counter(List))
  

代码下载(codeSnippet_position_05.gh)

🍋‍🟩代码段-练习-6: (    )

直接用Vector->Grid->Populate 2D/Populate 3D/Populate Geometry等组件生成的的随机点是“均匀”分布的,这与自然界中植被的自然散布方式明显不同。在代码段练习3中,引入了一种可以让随机看起来更自然些的算法,但是因为直接使用Graph Mapper组件,需要双击打开Graph Editor编辑截面,配置 X、Y 值等参数,而目前尚不可能在将其封装时还能够引出输入参数,因此用 Python Script 定义了 Sine 图形函数sin,并引出调整 sin 函数的周期sin_period、偏移sin_shift和振幅sin_amplitude等参数。用sin替换Graph Mapper,封装为random points_sin组件,方便调用,以获取更加自然的点分布(图 1.3.2-6)。

Point Groups组件将随机点按照给定的距离分组点,可以辅助植被种植设计。同时,用MetaBall(t) Custom组件绘制林缘线。

gh

图 1.3.2-6 代码段(点)-6与结果:动画

gh

图 1.3.2-6 代码段(点)-6与结果

sin(Python Script 组件)

  """
    正弦函数
    Inputs:
        Values: list[float]
            输入值列表
        Sin_period: float
            函数周期 
        Sin_shift: float
            函数偏移
        Sin_amplitude: float
            函数振幅
    Output:
        Sin_y: float
            正弦函数变换后的值
"""

import math

def sin_y():    
    Sin_y=[]
    for v in Values:    
        if Sin_amplitude:
            Sin_y.append(Sin_amplitude*math.sin((1/Sin_period)*v+math.pi/2+Sin_shift))
        else:
            Sin_y.append(math.sin((1/Sin_period)*v+math.pi/2+Sin_shift))
    return Sin_y

if __name__=="__main__":
    Sin_y=sin_y()
  

代码下载(codeSnippet_position_06.gh)

1.3.2.2 向量(Vector)-寻找空间方向

向量(Vector)是数学、物理学和工程科学等多个自然科学中的基 本概念,指一个同时具有大小和方向的几何对象,因常常以箭头符号 标示区别于其它量而得名。直观上,向量通常被标示为一个带箭头的 线段。线段的长度可以表示向量的大小,而向量的方向也就是箭头所 指的方向。与矢量概念相对的是只有大小而没有方向的标量。

A组:

B组:

C组:

🍋‍🟩代码段-练习-7-A: (    )

依据《圖解木構造》中“木构造”解析图,建立参数化模型(图 1.3.2-7)。在建构过程中,将经常使用到的逻辑封装为单独的组件,方便调用,包括双侧偏移封闭双曲线垂直拉伸垂直拉伸_surface索引 线Rectangle_centor双曲线成体等。用参数化构建较为传统的建筑,因为其具有一定的规制,相较练习3、4的算法,逻辑简单,但是要构建整座木构造,例如这里包括了23个构件对象,则相对较为繁琐。同时需要注意,在构建时,尽量根据实际搭建的过程建立逻辑关系和参数之间的依赖关系;并避开延轴向建立参数化模型,要旋转一定角度,避免正交这一特殊情况。如果模型定位线发生偏转,模型因为向量、参考平面等原因发生错乱,就会影响模型的健壮性。

依据木构造对象,可以进一步封装简化(图 1_3_2_08-01_07)。进行该练习训练时,因为代码连线相对较为繁乱(图 1.3.2-9),可以跟随建立封装组件后,用封装组件结合练习代码(或者直接查看源码),尝试自行建立整个木构造参数化模型。

《圖解木構造》为繁体,因此代码中的木构造组件索引也延续了繁体。

gh

图 1.3.2-7 代码段(向量)-7的参考与结果(图中左上角图引自:山辺豊彦 著,張正瑜译.圖解木構造[M].易博士出版社,台灣 ,2014.08)

gh

图 1.3.2-08-01 双侧偏移 封装组件

gh

图 1.3.2-08-02 封闭双曲线 封装组件

gh

图 1.3.2-08-03 垂直拉伸 封装组件

gh

图 1.3.2-08-04 垂直拉伸_surface 封装组件

gh

图 1.3.2-08-05 索引线 封装组件

gh

图 1.3.2-08-06 Rectangle_center 封装组件

gh

图 1.3.2-08-07 双曲线成体 封装组件

gh

图 1.3.2-9 代码段(向量)-7-A(代码量较大,为方便排版仅保留部分,源码可以从本书代码下载链接处获取)

代码下载(codeSnippet_position_07-A.gh)

🍋‍🟩代码段-练习-7-B: (    )

上述代码完成了木构造参数化模型(图 1.3.2-10),但是并未按照构造对象各自封装。如果要发布分享,实际上通过封装(类似文本编程定义的函数)构成各个对象的内部代码可以让代码更加清晰易读,如图 1_3_2_11-01_23。在封装过程中可以进一步调试代码,确定哪些参数适合作为输入参数,哪些适合作为输出参数。在保持构建逻辑不变的条件下,最终完成的参数化模型应该具有很好的健壮性,通过不同参数的调整,变化为不同的建筑规格,而保持建筑模型对象不发生错乱。

当构造对象分别封装后,实际上可以将所有内容进一步封装为单个组件,只留出该类型木构造的核心输入参数。这样处理有利有弊,利为进一步大量减少了外部组件量,进一步增加可读性;弊为弹性调整进一步削弱。具体封装到哪一步需要根据具体情况确定。

gh

图 1.3.2-10 代码段(向量)-7-B的结果

gh

图 1.3.2-10 代码段(向量)-7-B的结果:动画

gh

图 1.3.2-11-01 基础 封装组件

gh

图 1.3.2-11_02 木地檻 封装组件

gh

图 1.3.2-11-03 格栅托梁 封装组件

gh

图 1.3.2-11-04 樓板支柱 封装组件

gh

图 1.3.2-11-05 樓板格栅 封装组件

gh

图 1.3.2-11-06 地板-1层 封装组件

gh

图 1.3.2-11-07 通柱 封装组件

gh

图 1.3.2-11-08 屋架支柱 封装组件

gh

图 1.3.2-11-09 圈梁 封装组件

gh

图 1.3.2-11-10 樓板梁 封装组件

gh

图 1.3.2-11-11 樓板格栅-2层 封装组件

gh

图 1.3.2-11-12 地板-2层 封装组件

gh

图 1.3.2-11-13 檐桁 封装组件

gh

图 1.3.2-11-14 屋架梁 封装组件

gh

图 1.3.2-11-15 脊桁 封装组件

gh

图 1.3.2-11-16 椽 封装组件

gh

图 1.3.2-11-17 屋架支柱 封装组件

gh

图 1.3.2-11-18 桁條 封装组件

gh

图 1.3.2-11-19 屋面板 封装组件

gh

图 1.3.2-11-20 間柱 封装组件

gh

图 1.3.2-11-21 斜撑 封装组件

gh

图 1.3.2-11-22 管柱 封装组件

gh

图 1.3.2-11-23 楣 封装组件

gh

图 1.3.2-12 代码段(向量)-7-B (代码量较大,为方便排版仅保留部分,源码可以从本书代码下载链接处获取)

代码下载(codeSnippet_position_07-B.gh)

1.3.2.3 参考平面(Plane)-定位空间所在

A组:

B组:

C组:

🍋‍🟩代码段-练习-8-A: (    )

通常不会在设计模型当中直接构建五金构件,而是单独建立参数化模型,并预留出用于对位的参考平面、轴线或定位点等信息。图 1.3.2-13这个练习是从《圖解木構造》的“梁柱構架工法的搭接”中选择“併用五金構建結合(拉引固定五金)”为例,解释构建途径,并结合参考《新编建筑五金速查手册》和《Wood Construction Connectors-2012-2023》,对五金构建稍作调整。构件对位的方法同样可用于类似的五金构件(例如斗拱、饰物等),因此可以制定一些组合对位的模式,并将其封装为应用组件,简化这种在深度设计上不同构建的组合,实现参数化上设计细节的进一步深化。

对拉引五金构件封装为拉引固定五金拉引杆件-定位点两个组件。对位的方法主要使用OrientFlip Plane组件配合,并封装为对位-Orient组件方便调用(图 1_3_2_14-01_04)。在设计五金构建的参数化封装时,需要注意可以调整的预留输入参数,及需要引出的对象和对位参考平面、轴线、直线、点等信息。该部分练习并未对梁柱本身的结合进行处理,实现的代码为图 1.3.2-15。

简光沂主编.新编建筑五金速查手册[M].中国电力出版社.北京.2017.10;

gh

图 1.3.2-13 代码段(参考平面)-8-A的结果(图中左上角图引自:山辺豊彦 著,張正瑜译.圖解木構造[M].易博士出版社,台灣 ,2014.08)

gh

图 1.3.2-14-01 拉引固定五金 封装组件

gh

图 1.3.2-14-02 拉引杆件-定位点 封装组件

gh

图 1.3.2-14-03 对位-Orient 封装组件

gh

图 1.3.2-14-04 bi Line SDL 封装组件

gh

图 1.3.2-15 代码段(参考平面)-8-A

代码下载(codeSnippet_position_08-A.gh)

🍋‍🟩代码段-练习-8-B: (    )

在练习 8-A 中并未对柱梁本身的结合处加以处理,图 1.3.2-16 则试图增加这个细节。在传统的建模中,例如Digital Project(基于 CATIA )或者SolidWorks,及 Rhino 本身在处理几何零件时,通常使用类似零件加工的过程,即机械造型设计,功能通常包括倒角、圆角、加强肋、角度拔模、开槽、肋、螺孔、旋转槽、凸台等。在GH->Surface->Freeform下有类似凸台的Extrude类,GH->Intersect->Shape下有各种几何体布尔操作的模式,但是 GH 本身是节点式编程,很难像手工推拉一样塑造零件。同时,如果在练习7中的设计阶段就加入木构造节点结合方式的处理,显然不符合设计思维的习惯。通常待整体把控住之后,才会开始细节的设计。在设计开始阶段,方案需要不断调整的特点,也没有必要深入到构造细节。因此,如何基于练习7的结果来细化设计,通常采用局部的大样详图。也可以把零件单独设计封装,类似练习 8-A 的对位配置,这样可以更清晰的观察设计的结果。因此,对于柱梁榫卯这样的结构,也采取了类似练习 8-A 的处理方式,把开槽、凸台等操作模式转换为单独的零件(图 1_3_2_17-01_02),对位后再通过Solid Difference(类似实现开槽),Solid Union(类似实现凸台)等组件实现不同对象之间的切割,达到机械造型设计的目的。

在 GH 下实现爆炸图,可以将各个零件根据建立的向量移动到指定的位置,也可以就各零件的某一个面平行置于XY平面,代码如图 1.3.2-18。

gh

图 1.3.2-16 代码段(参考平面)-8-B的结果

gh

图 1.3.2_17-01 短榫 封装组件

gh

图 1.3.2_17-02 矩形中轴线 封装组件

gh

图 1.3.2-18 代码段(参考平面)-8-B

代码下载(codeSnippet_position_08-B.gh)

1.3.2.4 磁场(Field)-隐藏的有方向的力

A组:

#1. Line Charge- 输入一根直线建立磁场;

#2. Point Charge 输入一个点建立磁场;

#3. Spine Force 输入参考平面Plane、强度Strength、半径Radius、衰减Decay和边界Bounds建立磁场;

#4. Vector Force - 根据向量建立磁场;

B组:

#5. Break Field - 分解合并的磁场为各个单独的磁场;

#6. Merge Fields - 合并各个单独的磁场为一个磁场;

C组:

#7. Evaluate Field - 指定点位置Point提取该点磁场的属性,场张量Tensor和强度Strength

#8. Field Line - 根据给定的磁场Field,提取磁力线的点Point,采样的数量Steps,曲线的精度Accuracy来提取磁力线;

D组:

#9. Direction Display - 显示场磁力方向;

#10. Perpendicular Display - 显示垂直域磁场向量正负力向;

#11. Scalar Display - 以颜色变化显示磁场标量(磁力大小);

#12. Tensor Display - 给定截面Section,采样数Samples显示场张量(向量);

🍋‍🟩代码段-练习-9: (    )

给定一个磁场,可以通过点提取磁场中任意一点的磁力线,演变出具有磁场特征的多样图式。图 1.3.2-19 用Spin Force组件通过随机产生的点构建出旋转的磁场,然后等分围绕圆心的半径,获取数个点来提取磁场磁力线。随着半径旋转,提取的磁力线根据点位置发生变化,从而获得动态的一个连续变化的形态。

gh

图 1.3.2-19 代码段(磁场)-9与结果:动画

gh

图 1.3.2-19 代码段(磁场)-9与结果

代码下载(codeSnippet_position_09.gh)

🍋‍🟩代码段-练习-10: (    )

参数化除了构建设计空间几何对象外(这包括直接的设计推敲和基于算法(逻辑)的设计生成),通过建构设计工具能够解决某一类设计的问题。在 SketchUp(SU)结合 AutoCAD(ACAD)的辅助地形设计中,通常先在 ACAD 中绘制平面的等高线(或直接在 SU 中绘制)后,再移动或者赋予 Z 方向的值建立三维等高线后成面。这个过程因为受制于工具的处理能力,使得地形的设计方式笨拙,也违背了地形设计的合理过程。后来有类似于ZBrush或者Lumion等工具可以在三维中通过画笔工具直接雕刻地形,这个过程更符合地形设计的真实过程。这些工具中通常也嵌入了自动生成地形的方法。

在 GH 下如何可控的辅助地形生成设计(图 1.3.2-20),GH->Vector->Field提供了一种思路,通过建立磁场,由点提取磁场力和方向,并移动到新的位置构建地形表面的方法。这里构建了封装的辅助地形设计-磁场工具(图 1.3.2-21_01),可以通过折线、点来控制地形表面的变化,而无需像传统那样绘制等高线,这样设计者可以将更多的精力放置在地形设计空间本身的推敲上来,实时的在三维空间中推敲,快速完成方案设计。同时,有些设计的凹地可以形成雨水花园,因此增加了水面控制点提取邻近的等高线并绘制水平面。

该练习增加了一个封装的简单拱(图 1.3.2-21_02),可以通过3个点快速的生成一个简单的拱桥;以及结合地形的道路-地形表面裁切封装(图 1.3.2-21_03),可以在地形表面绘制道路,提取道路边线并裁切地形。

通过上述各个封装组件的建立,那么地形设计的过程就演变成了控制点和控制线的设计(图 1.3.2-22)。图 1.3.2-20的第1,2幅表述了生成所有内容的设计结构线,仅需调整、增减这些结构线就可以直接调整方案,及迅速的获取多个比较方案。不过,对于半手工的设计模式,设计空间形式的把握还需要设计师自身的设计修养。

gh

图 1.3.2-20 代码段(磁场)-10的结果

gh

图 1.3.2-21_01 辅助地形设计-磁场 封装组件

gh

图 1.3.2-21_02 简单拱 封装组件

gh

图 1.3.2-21_03 道路-地形表皮裁切 封装组件

gh

图 1.3.2-22 代码段(磁场)-10

代码下载(codeSnippet_position_10.gh)

代码下载(codeSnippet_position_10.3dm)

🍋‍🟩代码段-练习-11: (    )

练习10给出的辅助地形设计-磁场封装工具,在设计地形时可以看到明显的人为设计痕迹,更多的强调设计者自身对设计的把控。那么如何生成如图 1.3.2-23 的自然的地形,用于地形设计的参考或者直接作为地形设计?基于辅助地形设计-磁场逻辑,增加根据随机点自动生成空间折线的方法ACO(Python Script 编写代码),并将折线用于磁场构建的线型输入条件,结合手动调整的点输入(也可以自动生成随机点)建立点磁场,融合线和点的磁场用于影响设计区域内点的移动来生成地形表面,封装为组件地形生成-磁场(图 1.3.2-24_01)。除了在建立ACO时使用了随机,在提取点磁场时,也使用Populate Geometry组件建立随机点用于磁场向量的提取;由Point Charge组件建立点磁场时,参数输入条件ChargeDecay也由随机生成值作为输入条件。因为随机值的运用,尽量减少了人为干扰因素,所产生的地形也更趋于自然形态。

地形构建-重分类可视化封装组件可以以重分类地形高度的方式可视化地形,方便观察地形的高度变化(图 1.3.2-24_02)。最终代码如图 1.3.2-25。

gh

图 1.3.2-23 代码段(磁场)-11的结果:动画

gh

图 1.3.2-23 代码段(磁场)-11的结果

gh

图 1.3.2-24_01 地形生成-磁场 封装组件

gh

图 1.3.2-24_02 地形构建-重分类可视化 封装组件

ACO (Python Script 组件)

  """
    用蚁群算法(AntClony Optimization,ACO)求解旅行商(Traveling Salesman Problem,TSP)问题,即对输入的 Cities 点列表排序。(假设有一个旅行商人要拜访 N 个城市,他必须选择所要走的路径,路径的限制是每个城市只能拜访一次,而且最后要回到原来出发的城市。路径的选择目标是要求得的路径路程为所有路径之中的最小值。TSP 问题是一个 NPC 问题)
    Inputs:
        Cities: list[ghdoc Object]
            代表城市的多个点列表
        antCount: int
            蚁群数量
        nMax: int
            最大迭代次数
        run: bool
            运行组件
    Output:
        Route: list(point)
            排序后的点列表
"""

import rhinoscriptsyntax as rs
import os
import sys
import random
import string
from string import *
from math import *
BestTour = []
CitySet = set()
CityList = []
PheromoneTrailList = []
PheromoneDeltaTrailList = []
CityDistanceList = []
AntList = []
BTC = []


class BACA:
    def __init__(self, cityCount=14, antCount=10, q=80,
                 alpha=2, beta=5, rou=0.3, nMax=10):
        self.CityCount = cityCount
        self.AntCount = antCount
        self.Q = q
        self.Alpha = alpha
        self.Beta = beta
        self.Rou = rou
        self.Nmax = nMax
        self.Shortest = 10e6
        random.seed()
        for nCity in range(self.CityCount):
            BestTour.append(0)
        for row in range(self.CityCount):
            pheromoneList = []
            pheromoneDeltaList = []
            for col in range(self.CityCount):
                pheromoneList.append(100)
                pheromoneDeltaList.append(0)
            PheromoneTrailList.append(pheromoneList)
            PheromoneDeltaTrailList.append(pheromoneDeltaList)

    def ReadCityInfo(self, fileName):
        for i in fileName.keys():
            cityN, cityX, cityY = i, fileName[i][1], fileName[i][2]
            CitySet.add(int(cityN))
            CityList.append((int(cityN), float(cityX), float(cityY)))
        for row in range(self.CityCount):
            distanceList = []
            for col in range(self.CityCount):
                distance = sqrt(pow(
                    CityList[row][1]-CityList[col][1], 2)+pow(CityList[row][2]-CityList[col][2], 2))
                distanceList.append(distance)
            CityDistanceList.append(distanceList)

    def PutAnts(self):
        for antNum in range(self.AntCount):
            city = random.randint(1, self.CityCount)
            ant = ANT(city)
            AntList.append(ant)

    def Search(self):
        for iter in range(self.Nmax):
            self.PutAnts()
            for ant in AntList:
                for ttt in range(len(CityList)):
                    ant.MoveToNextCity(self.Alpha, self.Beta)
                ant.UpdatePathLen()
            tmpLen = AntList[0].CurrLen
            tmpTour = AntList[0].TabuCityList
            for ant in AntList[1:]:
                if ant.CurrLen < tmpLen:
                    tmpLen = ant.CurrLen
                    tmpTour = ant.TabuCityList
            if tmpLen < self.Shortest:
                self.Shortest = tmpLen
                BestTour = tmpTour
            print(iter, ":", self.Shortest, ":", BestTour)
            BTC.append(BestTour)
            self.UpdatePheromoneTrail()

    def UpdatePheromoneTrail(self):
        for ant in AntList:
            for city in ant.TabuCityList[0:-1]:
                idx = ant.TabuCityList.index(city)
                nextCity = ant.TabuCityList[idx+1]
                PheromoneDeltaTrailList[city -
                                        1][nextCity-1] = self.Q/ant.CurrLen
                PheromoneDeltaTrailList[nextCity -
                                        1][city-1] = self.Q/ant.CurrLen
            lastCity = ant.TabuCityList[-1]
            firstCity = ant.TabuCityList[0]
            PheromoneDeltaTrailList[lastCity -
                                    1][firstCity-1] = self.Q/ant.CurrLen
            PheromoneDeltaTrailList[firstCity -
                                    1][lastCity-1] = self.Q/ant.CurrLen
        for (city1, city1X, city1Y) in CityList:
            for (city2, city2X, city2Y) in CityList:
                PheromoneTrailList[city1-1][city2-1] = ((1-self.Rou)*PheromoneTrailList[city1-1][city2-1] +
                                                        PheromoneDeltaTrailList[city1-1][city2-1])
                PheromoneDeltaTrailList[city1-1][city2-1] = 0


class ANT:
    def __init__(self, currCity=0):
        self.TabuCitySet = set()
        self.TabuCityList = []
        self.AllowedCitySet = set()
        self.TransferProbabilityList = []
        self.CurrCity = 0
        self.CurrLen = 0.0
        self.AddCity(currCity)
        pass

    def SelectNextCity(self, alpha, beta):
        if len(self.AllowedCitySet) == 0:
            return (0)
        sumProbability = 0.0
        for city in self.AllowedCitySet:
            sumProbability = sumProbability + (pow(PheromoneTrailList[self.CurrCity-1][city-1], alpha) * pow(
                1.0/CityDistanceList[self.CurrCity-1][city-1], beta))
        self.TransferProbabilityList = []
        for city in self.AllowedCitySet:
            transferProbability = (pow(PheromoneTrailList[self.CurrCity-1][city-1], alpha) * pow(
                1.0/CityDistanceList[self.CurrCity-1][city-1], beta))/sumProbability
            self.TransferProbabilityList.append((city, transferProbability))
        select = 0.0
        for city, cityProb in self.TransferProbabilityList:
            if cityProb > select:
                select = cityProb
        threshold = select * random.random()
        for (cityNum, cityProb) in self.TransferProbabilityList:
            if cityProb >= threshold:
                return (cityNum)
        return (0)

    def MoveToNextCity(self, alpha, beta):
        nextCity = self.SelectNextCity(alpha, beta)
        if nextCity > 0:
            self.AddCity(nextCity)

    def ClearTabu(self):
        self.TabuCityList = []
        self.TabuCitySet.clear()
        self.AllowedCitySet = CitySet - self.TabuCitySet

    def UpdatePathLen(self):
        for city in self.TabuCityList[0:-1]:
            nextCity = self.TabuCityList[self.TabuCityList.index(city)+1]
            self.CurrLen = self.CurrLen + CityDistanceList[city-1][nextCity-1]
        lastCity = self.TabuCityList[-1]
        firstCity = self.TabuCityList[0]
        self.CurrLen = self.CurrLen + CityDistanceList[lastCity-1][firstCity-1]

    def AddCity(self, city):
        if city <= 0:
            return
        self.CurrCity = city
        self.TabuCityList.append(city)
        self.TabuCitySet.add(city)
        self.AllowedCitySet = CitySet - self.TabuCitySet


if __name__ == "__main__":
    citydic = {}
    for i in range(len(Cities)):
        citydic[i+1] = [i+1,
                        rs.PointCoordinates(Cities[i])[0], rs.PointCoordinates(Cities[i])[1]]
    cCount = len(Cities)
    antCount = int(antCount)
    nMax = int(nMax)
    if run == True:
        theant = BACA(cityCount=cCount, antCount=antCount,
                    q=80, alpha=2, beta=5, rou=0.3, nMax=nMax)
        theant.ReadCityInfo(citydic)
        theant.Search()
        BTCO = BTC[-1]
        closep = BTCO[:]
        closep.append(BTCO[0])
        Route = [Cities[i-1] for i in closep]
  

gh

图 1.3.2-25 代码段(磁场)-11

用 Python Script 编写decimal places,用于数值精度的控制,较之使用GH->Sets->Text中提供的工具,更加便捷。

decimal places(Python Script 组件)

  results=[round(v,int(decimal_places)) for v in numbers]
  

代码下载(codeSnippet_position_11.gh)

地形生成-磁场方法,自动生成的地形设计,从运算结果来看,可以清晰看出辅助地形设计-磁场地形生成-磁场产生地形的不同。辅助地形设计更倾向于设计自行构建,可以更准确的把握地形开合、高度变化等设计空间形态;地形生成则近乎交给算法逻辑,除了增加外部可控制的折线或者点用于自动地形生成结果的调整外,是无法具体控制设计地形的空间形态,但可以从无以计数的生成结果中,筛选出适合于场地环境和设计目的的地形形式。

练习题

🌟练习-A.

对空间几何的参数化处理,很关键的一部分是对空间参考平面、空间向量的把握。如果能很好的从几何对象中提取、自由变换参考平面和向量,并依此设计几何空间,那么 GH 的应用水平将会进一步提升。

这个设计是思考连接杆件之间连接件的形态,本来上下两个参考平面均是完全水平的,但在构建逻辑时,希望上参考平面可以跟随实际设计对象的方位变化而变化,因此改为可以变换的参考平面。在练习过程中,也可以尝试修改底参考平面,,或增加新的参考平面构成维度更多的连接形式。

gh

图 1.3.2-26 练习-A 结果图:动画

gh

图 1.3.2-26 练习-A 结果图

参考代码下载(practice_132_A.gh)

🌟练习-B.

窗体部分可增加“洞口拉伸”的封装组件,方便门窗突出(飘窗)或者设计门内嵌式的玄关处理。楼梯部分的逻辑构建并没有给出封装,可以尝试增加扶手栏杆后封装用于日后的设计使用。楼梯的逻辑构建采用了部分手工辅助调整的策略,根据标注提示,查看楼梯与底面的距离关系,调整开始、结束、及转折端控制点完成楼梯于底面的衔接。

gh

图 1.3.2-27 练习-B 结果图:动画

gh

图 1.3.2-27 练习-B 结果图

参考代码下载(practice_132_B.gh)

🌟练习-C.

一个楼梯设计的概念形式,以梭柱斜拉索替代底部的支撑结构。

gh

图 1.3.2-28 练习-C 结果图

参考代码下载(practice_132_C.gh)

注释(Notes):

① David Rutten,毕业于TUDelft(代尔夫特理工大学)建筑与城市规划学院。从2006年开始为Robert McNeel & Associates工作,其中最重要的是为Rhinoceros 3D设计的Grasshopper可视化编程环境(https://www.grasshopper3d.com/forum/topic/listForContributor?user=1ak3xlo6iakfj;https://archinect.com/DavidRutten)。

② How to change a point grid’s density with an attractor?,(https://www.grasshopper3d.com/forum/topics/how-to-change-a-point-grid-s)。

③ attractorFields tools (gh),产生基于距离的吸引场工具(https://object-e.net/tools/attractorfields-tools-gh)。

④ Start Trigger Component with Python,用 Python Script 编写计时器(https://discourse.mcneel.com/t/start-trigger-component-with-python/124770)。

⑤ 《Wood Construction Connectors-2012-2023》,Simpson Strong‑Tie 公司致力于建筑产品和技术研发,其木结构连接器目录为木结构应用解决方案指南,提供了包含有规格和安装说明等产品信息(https://www.strongtie.com/woodconnectors/landing-page)。

⑥ CATIA,将传统的计算机辅助设计((computer‑aided design)转变为融合建模和仿真的认知增强设计,利用知识和技能来自动化设计和系统工程(https://www.3ds.com/products/catia)。

⑦ SOLIDWORKS,为任何企业提供功能强大且易于使用的2D和3D产品开发解决方案,专注于为用户提供强大的设计工作流程,并融合人工智能、机器学习和生成式设计等最新技术,为设计师带来领先的功能(https://www.3ds.com/products/solidworks)。

⑧ ZBrush,为数字雕刻和绘画软件工具(https://www.maxon.net/en/zbrush)。

⑨ Lumion,专为建筑师设计的3D渲染软件(https://lumion.com/)。