Created on 2024-09-10T21:09:58+08:00 @author: Richie Bao

2.7.1.1 间梁布局

中国古代单座建筑的房屋(殿堂房舍等)平面构成一般都是“柱网”或者“屋顶结构”的布置方式来表示,因此建筑的平面通常就为结构的平面。纵向轴线之间的面积称为“间”,间的数量大多采用奇数,延中间开始一般命名为明间、次间、稍间和尽间等。横向轴线之间习惯上以“架”来表示(架指的是檩木,檩木之间的水平距离在宋代称为“步”)。因此,平面的形状大小就可以简单的用“间”和“架”的数量来完全表示,如《唐会要•舆服•杂录》(第三十一卷)的规定说:三品以上堂屋,不得过五间九架。厅厦两头门屋,不得过五间五架。五品以上堂屋,不得过五间七架,厅厦两头门屋,不得过三间两架。六品七品以下堂舍,不得过三间五架,门屋不得过一间两架。其士庶公私宅第,皆不得造楼阁,临视人家。又庶人所造堂舍,不得过三间四架,门屋一间两架。单座建筑的平面形式通常为规矩的矩形,但也有三角形、方形、圆形、连环、六角、八角、梅花、扇形、十字、方胜、工字等平面形状的亭、楼、阁和榭等。如表 2.7.1-1截取了圆明园九洲清宴的圆明园殿、奉三无私、九州清宴 、慎德堂和东佛堂等建筑尺寸,及其平面图(图 2.7.1-1)。

表 2.7.1-1 道光十六年九洲清宴建筑尺寸(截自参考文献[3]194)

建筑名称 阶段 建筑形式 开间尺寸 进深尺寸(尺) 立面尺寸 其它(尺)
平面 明间 东次间 西次间 东梢间 西梢间 东尽间 西尽间 左右廊 前卷 中卷 后卷 前后廊 檐柱高 柱径 下出 台明高
圆明园殿 道光十六年 五开间带前后廊 13 12 12 12 12 22 6 12 1.2 3 1.8 虎皮石台帮:南至驳岸 35
奉三无私 道光十六年 七开间带周围廊 13 12 12 12 12 12 12 6 26 6 13 1.2 3.2 1.8 斗板石;前院深105.4,宽94
九州清宴 道光十六年 七开间带周围廊,后出3间抱厦带周围廊 13 12 12 12 12 12 12 6 24 14 6 12 1.1 3 1.8 水文石台帮;前院深 55.7, 宽117; 北至驳岸32
慎德堂 道光十六年 5间3卷带周围廊 13 12 12 12 12 6 23.5 23.5 23.5 6 13.5 3 1.8
东佛堂 道光十六年 三开间带前后廊,前出抱厦1间 10.2 10.2 10.2 11 20 4 10 前院深45.8

pys

图 2.7.1-1 圆明园殿、奉三无私、九州清宴总图(样式雷排架 005-34 号)及单独平面(样式雷排架007-9-1号、样式雷排架 006-5号、样式雷排架004-5号)和慎德堂平面图(样式雷排架017-12号), 图片引子参考文献[3]

2.7.1.2 构建间梁布局工具集

1)营造尺

pys

图 2.7.1-2 间梁布局工具集

因为古建筑的单体设计平面通常具有典型的规制(间梁局部),因此为了方便辅助中国古代建筑群平面设计建立组件工具集,如图 2.7.1-2。从表 2.7.1-1的数据可以看到使用的单位为营造尺,为唐以来历代营造工程中所用的尺子。营造尺库平制为清康熙五十二年(1713)制的《御制律吕正义》及《御制数理精蕴》都定度量衡表,而光绪三十四年(1908年)《推行划一度量衡章程四十条》,形成营造尺、库平两的标准化。长度单位营造尺等于0.32米,有 1丈 = 10尺 = 100寸 = 1000分。 定义丈尺寸分⭢米组件实现营造尺到米的转化,为了简化输入,又有定义组件丈尺寸分[Str]⭢米,由输入的字符串表示丈尺寸分,例如1,6,1,5为1丈6尺1寸5分,如图 2.7.1-3。

pys

图 2.7.1-3 营造尺到米制转换组件

丈尺寸分⭢米 (Python Script 组件)

  ghenv.Component.Name = "丈尺寸分⭢米"
ghenv.Component.NickName = "丈尺寸分⭢米"
ghenv.Component.Description = "中国传统营造尺转换为米"
ghenv.Component.Message = "0.0.1"
ghenv.Component.Category = "Moths"
ghenv.Component.SubCategory = "BayBeam Layout"
ghenv.Component.PanelSection = 1

def constructionRuler2meter():

    return (Zhang + Chi * 0.1 + Cun * 0.01 + Fen * 0.001) * ConstructionRuler

if __name__ == "__main__":
    if ConstructionRuler is None:
        ConstructionRuler = 3.2
    Meter = constructionRuler2meter()
  

丈尺寸分[Str]⭢米 (Python Script 组件)

  ghenv.Component.Name = "丈尺寸分[Str]⭢米"
ghenv.Component.NickName = "丈尺寸分[Str]⭢米"
ghenv.Component.Description = "中国传统营造尺转换为米"
ghenv.Component.Message = "0.0.1"
ghenv.Component.Category = "Moths"
ghenv.Component.SubCategory = "BayBeam Layout"
ghenv.Component.PanelSection = 1


def constructionRuler2meter_str(constructionRuler=3.2):    
    zhang, chi, cun, fen = [float(i) for i in ZhangChiCunFen.strip().split(",")]

    return (zhang + chi * 0.1 + cun * 0.01 + fen * 0.001) * constructionRuler


if __name__ == "__main__":
    if ConstructionRuler is None:
        ConstructionRuler = 3.2

    if ZhangChiCunFen:
        Meter = constructionRuler2meter_str(ConstructionRuler)
  

代码下载(2_7_1_01.gh)

2)基本间架, + 抱厦

图 2.7.1-4 为正大光明殿平面图,其明间面宽一丈六尺一寸五,二次间面宽一丈五尺一寸,四梢间面宽一丈三尺五寸,廊宽六尺。金柱中进深三丈八尺(指中心间距),柱皮空进深三丈六尺六寸五,为周围廊式,但后廊被封入室内,仅三面有外廊,两山的廊子内无门窗装修。外檐装修全部为隔扇门,前后檐共十樘,后檐两端的两间未安木装修,均砌墙。以正大光明殿平面图为参照,建立基本间架组件(图 2.7.1-5 中基本间架)。基本间架组件包括明间、次间、梢间和尽间,及周围廊的配置,在位置朝向确定上则依据输入的直线作为定位轴线,将其初始端作为建筑平面的中心位置。API 选择上主要使用rhinoscriptsyntax库,并使用自行建立的辅助 PyPI 包moths(通过pip install moths安装,具体参考2.1部分),调用nestedListGrouping4方法组织点为可以构建四边面的点组织形式。

pys

图 2.7.1-4 正大光明殿平面图(样式雷排架001-4),图片引子参考文献[3]163

pys

图 2.7.1-5 基本间架、选择 Tree 中的对象和+抱厦组件构建

基本间架 (Python Script 组件)

  '''
    指定轴线,建立古建筑的基本间架布局,包含明间、(东西)次间、(东西)梢间、(东西)尽间和前后左右廊
    Inputs:
        Axis: Item Access; ghdoc Object
            定位中轴线
        MingJianWidth: Item Access; float
            明间宽
        CiJianWidth: Item Access; float
            次间宽
        Num_CiJian: Item Access; int
            次间数
        ShaoJianWidth: Item Access; float
            梢间宽 
        Num_ShaoJian: Item Access; int
            梢间数
        JinJianWidth: Item Access; float
            尽间宽
        Num_JinJian: Item Access; int
            尽间数
        FrontCorridorDepth: Item Access; float
            前廊深
        RearCorridorDepth: Item Access; float
            后廊深
        LeftCorridorWidth: Item Access; float
            左廊宽
        RightCorridorWidth: Item Access; float
            右廊宽
        BuildingDepth: Item Access; float
            建筑进深

    Output:
        JianJiaPts: Point3d
            几何点
        JianJiaPtsGrouped: Point3d
            按照间架围合的单元组织的点
        JianJiaRectangles: Polyline
            间架围合单元的矩形区域
'''

ghenv.Component.Name = "基本间架"
ghenv.Component.NickName = "基本间架"
ghenv.Component.Description = "单座建筑的间架平面布局"
ghenv.Component.Message = "0.0.1"
ghenv.Component.Category = "Moths"
ghenv.Component.SubCategory = "BayBeam Layout"
ghenv.Component.PanelSection = 2

import ghpythonlib.treehelpers as th
import rhinoscriptsyntax as rs
import moths.utility as moths_utility
import numpy as np


def basic_building_framework():
    # 绘制中轴线上金柱位置参考点
    axis_start_pt = rs.CurveStartPoint(Axis)
    axis_end_pts = rs.CurveEndPoint(Axis)

    vector_axis = axis_end_pts - axis_start_pt
    unitizedVec_axis = rs.VectorUnitize(vector_axis)
    halfDepthVec_goldenPillar = rs.VectorScale(
        unitizedVec_axis, BuildingDepth / 2
    )
    halfDepthVec_goldenPillar_reversed = rs.VectorReverse(
        halfDepthVec_goldenPillar
    )
    end_pt_goldenPillar = axis_start_pt + halfDepthVec_goldenPillar_reversed
    start_pt_goldenPillar = axis_start_pt + halfDepthVec_goldenPillar

    # 绘制中轴线上檐柱位置参考点
    if FrontCorridorDepth:
        halfDepthVec_frontEavePillar = rs.VectorScale(
            unitizedVec_axis, BuildingDepth / 2 + FrontCorridorDepth
        )
        start_pt_eavePillar = axis_start_pt + halfDepthVec_frontEavePillar
    else:
        start_pt_eavePillar = None

    if RearCorridorDepth:
        halfDepthVec_rearEavePillar = rs.VectorScale(
            rs.VectorReverse(unitizedVec_axis),
            BuildingDepth / 2 + RearCorridorDepth,
        )
        end_pt_eavePillar = axis_start_pt + halfDepthVec_rearEavePillar
    else:
        end_pt_eavePillar = None

    # 绘制间架柱网
    width_lst = (
        [MingJianWidth / 2]
        + [CiJianWidth] * Num_CiJian
        + [ShaoJianWidth] * Num_ShaoJian
        + [JinJianWidth] * Num_JinJian
    )

    if RightCorridorWidth:
        width_right_lst = width_lst + [RightCorridorWidth]
    else:
        width_right_lst = width_lst
    total = 0
    width_right_lst_accumulated = [total := total + i for i in width_right_lst]

    if LeftCorridorWidth:
        width_left_lst = width_lst + [LeftCorridorWidth]
    else:
        width_left_lst = width_lst
    total = 0
    width_left_lst_accumulated = [total := total + i for i in width_left_lst]

    unitizedVec_axis_vertical_r = rs.VectorRotate(
        unitizedVec_axis, 90, [0, 0, 1]
    )
    unitizedVec_axis_vertical_l = rs.VectorRotate(
        unitizedVec_axis, -90, [0, 0, 1]
    )
    vecs_r = [
        rs.VectorScale(unitizedVec_axis_vertical_r, i)
        for i in width_right_lst_accumulated
    ]
    vecs_l = [
        rs.VectorScale(unitizedVec_axis_vertical_l, i)
        for i in width_left_lst_accumulated
    ]

    pts_onAxis = [
        start_pt_eavePillar,
        start_pt_goldenPillar,
        end_pt_goldenPillar,
        end_pt_eavePillar,
    ]
    pts_onAxis = [x for x in pts_onAxis if x is not None]

    columnGrid_r = [[pt + vec for pt in pts_onAxis] for vec in vecs_r]
    columnGrid_l = [[pt + vec for pt in pts_onAxis] for vec in vecs_l[::-1]]
    columnGrid = columnGrid_l + columnGrid_r
    columnGrid_tree = th.list_to_tree(columnGrid)

    # 绘制间架
    grouped_pts = moths_utility.nestedListGrouping4(columnGrid)
    grouped_pts_tree = th.list_to_tree(grouped_pts)
    grouped_pts_array = np.array(grouped_pts)
    grouped_pts_array = np.c_[grouped_pts_array, grouped_pts_array[:, :1]]

    jianjia_rec = list(
        np.apply_along_axis(rs.AddPolyline, 1, grouped_pts_array)
    )

    return columnGrid_tree, grouped_pts_tree, jianjia_rec


if __name__ == "__main__":
    if Axis and MingJianWidth:
        JianJiaPts, JianJiaPtsGrouped, JianJiaRectangles = (
            basic_building_framework()
        )
  

抱厦为围绕于厅堂、正屋的房屋,或指于原建筑前或后加建的小屋,增加建筑的空间深度,通常用于额外的空间或功能目的。抱厦依附于主体建筑,因此在组件设计时需要从已有主体建筑平面上选择柱点(间),建立选择 Tree 中的对象 组件(图 2.7.1-5 中选择 Tree 中的对象),根据输入的行、列提取点对象。在输入行和列的语法设计上包括 [],其包括收尾值;() 不包括收尾值;及其组合,例如 (]等;及指定单个值或多个值,如3,4,5,6。为了方便点的拾取,其中定义transpose_matrix()函数,可以翻转矩阵,将行列置换。

选择 Tree 中的对象 (Python Script 组件)

  '''
    从 Tree 型矩阵中,指定(多)行和(多)列,选择对象。对输入端 Rows 和 Cols 定义的语法包括 [],包括收尾值,() 不包括收尾值,及其组合,例如 (]等。及指定单个值或多个值,如3,4,5,6
    Inputs:
        Tree: Tree Access; ghdoc Object
            输入的矩阵,例如点数据
        Transpose: Item Access; bool
            是否翻转矩阵
        Rows: Item Access str
            指定行
        Cols: Item Access str
            指定列
    Output:
        ChosenObjects: List[ghdoc Object]
            输出选择的对象
'''

ghenv.Component.Name = "选择 Tree 中的对象"
ghenv.Component.NickName = "选择 Tree 中的对象"
ghenv.Component.Description = "根据行列索引选择 Tree 中的对象"
ghenv.Component.Message = "0.0.1"
ghenv.Component.Category = "Moths"
ghenv.Component.SubCategory = "BayBeam Layout"
ghenv.Component.PanelSection = 1

import ghpythonlib.treehelpers as th
import rhinoscriptsyntax as rs
import numpy as np
import moths.utility as moths_utility


def args_formatting4rowcol(rowcol):
    args_str = moths_utility.flatten_lst(
        [[*i] for i in rowcol.strip().split(",")]
    )
    start_symbol = args_str[0]
    end_symbol = args_str[-1]

    start_num = None
    end_num = None

    if start_symbol == "[":
        start_num = int(args_str[1])
    elif start_symbol == "(":
        start_num = int(args_str[1]) + 1

    if end_symbol == "]":
        end_num = int(args_str[-2])
    elif end_symbol == ")":
        end_num = int(args_str[-2]) - 1

    if start_num is not None:
        idxes = list(range(start_num, end_num + 1))
    else:
        idxes = [int(i) for i in args_str]

    return idxes


def transpose_matrix(matrix):
    return [list(row) for row in zip(*matrix)]


def chosen_objects(tree, rows, cols, transpose=False):
    lst = th.tree_to_list(tree)

    if transpose:
        lst = transpose_matrix(lst)

    chosenObjs = [[lst[row][col] for col in cols] for row in rows]
    chosenObjs_tree = th.list_to_tree(list(chosenObjs))

    return chosenObjs_tree


if __name__ == "__main__":
    if Tree and Rows and Cols:
        rows = args_formatting4rowcol(Rows)
        cols = args_formatting4rowcol(Cols)
        ChosenObjects = chosen_objects(Tree, rows, cols, Transpose)
  

以九洲清宴殿建筑为参照(图 2.7.1-1 ),其后抱厦从前殿后檐柱位置接出,利用前殿的后廊作为两者之间的过渡。后抱厦中柱与前殿柱间进深5尺8寸,中柱与后金柱进深7尺,柱高1丈2尺,台高1尺8寸。建立+抱厦组件((图 2.7.1-5 中+抱厦)),由选择 Tree 中的对象组件选择点作为JunctionColumnPts输入端参数。

``+抱厦` (Python Script 组件)

  '''
    为间梁平面添加抱厦
    Inputs:
        JunctionColumnPts: Tree Access; Point3d
            选择的连接点(柱)
        Direction: Item Access; bool
            是否翻转方向
        FrontCorridorDepth: Item Access; float
            前廊深
        LeftCorridorWidth: Item Access; float
            左廊宽
        RightCorridorWidth: Item Access; float
            右廊宽
        BuildingDepth: Item Access; float
            建筑深
        CentralCol2Junction: Item Access; float
            连接柱到中柱距离
        CentralCol2RearGoldenCol: Item Access; float
            中柱到金柱距离
    Output:
        JianJiaPts: Point3d
            几何点
        JianJiaPtsGrouped: Point3d
            按照间架围合的单元组织的点
        JianJiaRectangles: Polyline
            间架围合单元的矩形区域
        MiddlePillar: Point3d
            中柱
'''

ghenv.Component.Name = "+抱厦"
ghenv.Component.NickName = "+抱厦"
ghenv.Component.Description = "根据衔接柱点增加前(后)抱厦"
ghenv.Component.Message = "0.0.1"
ghenv.Component.Category = "Moths"
ghenv.Component.SubCategory = "BayBeam Layout"
ghenv.Component.PanelSection = 2

import ghpythonlib.treehelpers as th
import rhinoscriptsyntax as rs
import moths.utility as moths_utility
import numpy as np
import copy


def side_extension():
    pts = moths_utility.flatten_lst(th.tree_to_list(JunctionColumnPts))
    pts_copy = pts
    mid_pt = (pts[0] + pts[-1]) / 2
    vector_alongPts = pts[-1] - pts[0]

    if Direction:
        r_angle = 90
    else:
        r_angle = -90
    vector_vertical = rs.VectorRotate(vector_alongPts, r_angle, [0, 0, 1])
    vector_vertical_unit = rs.VectorUnitize(vector_vertical)
    vector_alongPts_unit = rs.VectorUnitize(vector_alongPts)

    if LeftCorridorWidth:
        pts = [
            pts[0] - rs.VectorScale(vector_alongPts_unit, LeftCorridorWidth)
        ] + pts
    if RightCorridorWidth:
        pts = [
            pts[-1] + rs.VectorScale(vector_alongPts_unit, LeftCorridorWidth)
        ] + pts

    offset_dist = []
    if BuildingDepth:
        offset_dist.append(BuildingDepth)
    else:
        offset_dist.append(CentralCol2Junction + CentralCol2RearGoldenCol)

    if FrontCorridorDepth:
        offset_dist.append(FrontCorridorDepth)

    offset_dist = [0] + offset_dist
    total = 0
    offset_dist_accumulated = [total := total + i for i in offset_dist]

    vecs = [
        rs.VectorScale(vector_vertical_unit, dist)
        for dist in offset_dist_accumulated
    ]
    columnGrid = [[pt + vec for pt in pts] for vec in vecs]
    columnGrid_tree = th.list_to_tree(columnGrid)

    # 绘制间架
    grouped_pts = moths_utility.nestedListGrouping4(columnGrid)
    grouped_pts_tree = th.list_to_tree(grouped_pts)
    grouped_pts_array = np.array(grouped_pts)
    grouped_pts_array = np.c_[grouped_pts_array, grouped_pts_array[:, :1]]

    jianjia_rec = list(
        np.apply_along_axis(rs.AddPolyline, 1, grouped_pts_array)
    )

    if CentralCol2Junction:
        central_vec = rs.VectorScale(vector_vertical_unit, CentralCol2Junction)
        central_columns = [
            pt + central_vec for pt in [pts_copy[0], pts_copy[-1]]
        ]

    else:
        central_columns = None

    return columnGrid_tree, grouped_pts_tree, jianjia_rec, central_columns


if __name__ == "__main__":
    if JunctionColumnPts.DataCount > 0:
        JianJiaPts, JianJiaPtsGrouped, JianJiaRectangles, MiddlePillar = (
            side_extension()
        )
  

代码下载(2_7_1_02.gh)

3)基本间架(内柱)

基本间架的组件设计没有内柱,因此参照安佑宫(图 2.7.1-6 )构建一个包含内柱的组件基本间架(内柱)(图 2.7.1-7)。安佑宫大殿平面尺寸:明间面宽1丈8尺5寸,其余各间面宽1丈5尺。通面宽13丈8尺5寸(44.32 米)。进深方向,前廊深8尺,金柱与内柱间深1丈3尺,内柱间距2丈,通进深6丈2尺(19.84 米)。该殿为圆明园中体量最大的建筑。

基本间架(内柱)程序是基于基本间架组件调整完成,增加了内柱相关的参数,如GoldenInnerDepthInnerColumnsDepth等。

pys

图 2.7.1-6 安佑宫复原设计平面图,图片引子参考文献[3]340

pys

图 2.7.1-7 基本间架(内柱)组件构建

基本间架(内柱) (Python Script 组件)

  '''
    指定轴线,绘制多步(跨)的基本间架平面
    Inputs:
        Axis: Item Access; ghdoc Object
            定位中轴线
        MingJianWidth: Item Access; float
            明间宽
        CiJianWidth: Item Access; float
            次间宽
        Num_CiJian: Item Access; int
            次间数
        ShaoJianWidth: Item Access; float
            梢间宽 
        Num_ShaoJian: Item Access; int
            梢间数
        JinJianWidth: Item Access; float
            尽间宽
        Num_JinJian: Item Access; int
            尽间数
        FrontCorridorDepth: Item Access; float
            前廊深
        RearCorridorDepth: Item Access; float
            后廊深
        LeftCorridorWidth: Item Access; float
            左廊宽
        RightCorridorWidth: Item Access; float
            右廊宽
        GoldenInnerDepth: Item Access; float
            檐柱到金柱距离
        InnerColumnsDepth: Item Access; float
            金柱间距离

    Output:
        JianJiaPts: Point3d
            几何点
        JianJiaPtsGrouped: Point3d
            按照间架围合的单元组织的点
        JianJiaRectangles: Polyline
            间架围合单元的矩形区域    
'''

ghenv.Component.Name = "基本间架(内柱)"
ghenv.Component.NickName = "基本间架(内柱)"
ghenv.Component.Description = "单座建筑的间架平面布局,含内柱"
ghenv.Component.Message = "0.0.1"
ghenv.Component.Category = "Moths"
ghenv.Component.SubCategory = "BayBeam Layout"
ghenv.Component.PanelSection = 2

import ghpythonlib.treehelpers as th
import rhinoscriptsyntax as rs

import moths.utility as moths_utility
import numpy as np


def basic_building_framework_innerColumns():
    # 绘制中轴线上檐柱、金柱、内柱位置参考点
    axis_start_pt = rs.CurveStartPoint(Axis)
    axis_end_pts = rs.CurveEndPoint(Axis)

    vector_axis = axis_end_pts - axis_start_pt
    unitizedVec_axis = rs.VectorUnitize(vector_axis)
    unitizedVec_axisreversed = rs.VectorReverse(unitizedVec_axis)
    vec_center2inner = rs.VectorScale(unitizedVec_axis, InnerColumnsDepth / 2)
    vec_center2golden = rs.VectorScale(
        unitizedVec_axis, InnerColumnsDepth / 2 + GoldenInnerDepth
    )
    vecs_front = [vec_center2inner, vec_center2golden]
    vecs_back = [rs.VectorReverse(vec) for vec in vecs_front]

    if FrontCorridorDepth:
        vec_center2eave_f = rs.VectorScale(
            unitizedVec_axis,
            InnerColumnsDepth / 2 + GoldenInnerDepth + FrontCorridorDepth,
        )
        vecs_front.append(vec_center2eave_f)
    if RearCorridorDepth:
        vec_center2eave_b = rs.VectorScale(
            unitizedVec_axisreversed,
            InnerColumnsDepth / 2 + GoldenInnerDepth + RearCorridorDepth,
        )
        vecs_back.append(vec_center2eave_b)

    cols_pts_front = [axis_start_pt + vec for vec in vecs_front]
    cols_pts_back = [axis_start_pt + vec for vec in vecs_back]

    cols_pts_front.reverse()
    cols_pts = cols_pts_front + cols_pts_back

    # 绘制间架柱网
    width_lst = (
        [MingJianWidth / 2]
        + [CiJianWidth] * Num_CiJian
        + [ShaoJianWidth] * Num_ShaoJian
        + [JinJianWidth] * Num_JinJian
    )
    if RightCorridorWidth:
        width_right_lst = width_lst + [RightCorridorWidth]
    else:
        width_right_lst = width_lst
    total = 0
    width_right_lst_accumulated = [total := total + i for i in width_right_lst]

    if LeftCorridorWidth:
        width_left_lst = width_lst + [LeftCorridorWidth]
    else:
        width_left_lst = width_lst
    total = 0
    width_left_lst_accumulated = [total := total + i for i in width_left_lst]

    unitizedVec_axis_vertical_r = rs.VectorRotate(
        unitizedVec_axis, 90, [0, 0, 1]
    )
    unitizedVec_axis_vertical_l = rs.VectorRotate(
        unitizedVec_axis, -90, [0, 0, 1]
    )
    vecs_r = [
        rs.VectorScale(unitizedVec_axis_vertical_r, i)
        for i in width_right_lst_accumulated
    ]
    vecs_l = [
        rs.VectorScale(unitizedVec_axis_vertical_l, i)
        for i in width_left_lst_accumulated
    ]

    columnGrid_r = [[pt + vec for pt in cols_pts] for vec in vecs_r]
    columnGrid_l = [[pt + vec for pt in cols_pts] for vec in vecs_l[::-1]]
    columnGrid = columnGrid_l + columnGrid_r
    columnGrid_tree = th.list_to_tree(columnGrid)

    # 绘制间架
    grouped_pts = moths_utility.nestedListGrouping4(columnGrid)
    grouped_pts_tree = th.list_to_tree(grouped_pts)
    grouped_pts_array = np.array(grouped_pts)
    grouped_pts_array = np.c_[grouped_pts_array, grouped_pts_array[:, :1]]

    jianjia_rec = list(
        np.apply_along_axis(rs.AddPolyline, 1, grouped_pts_array)
    )

    return columnGrid_tree, grouped_pts_tree, jianjia_rec


if __name__ == "__main__":
    if Axis and MingJianWidth:
        JianJiaPts, JianJiaPtsGrouped, JianJiaRectangles = (
            basic_building_framework_innerColumns()
        )
  

代码下载(2_7_1_03.gh)

4)简单间架

圆明园中的一些值房平面布局通常比较简单,为连续的等宽开间排布。为了增加组件简单间架(图 2.7.1-8)的功用,设计了两类可选输入参数,一个是,给定数量值(Num)和一个宽度值(Width)生成连续等开间的平面;另一个是,给定一个包含连续值的字符串用于各个开间宽,例如4.32,3.84,3.84,3.84,生成对应开间宽的连续布局。同时,包含有前后廊的配置。

pys

图 2.7.1-8 简单间架组件构建

简单间架 (Python Script 组件)

  '''
    指定轴线,绘制简单的古建筑间梁布局。给定宽度和数量,或者给定多个宽度绘制开间,包含前后廊
    Inputs:
        Axis:Item Access; ghdoc Object
            定位轴线
        Width: Item Access; float
            开间宽(一个)
        Widths: Item Access; str
            多个开间宽,为字符串形式,例如:4.32,3.84,3.84,3.84
        Num: Item Access; int
            如果指定 Width 参数,则需要指定开间数量
        Direction:  Item Access; bool
            延轴线翻转布局方向
        FrontCorridorDepth: Item Access; float
            前廊深
        RearCorridorDepth: Item Access; float
            后廊深
        BuildingDepth: Item Access; float
            建筑进深
    Output:
        JianJiaPts: Point3d
            几何点
        JianJiaPtsGrouped: Point3d
            按照间架围合的单元组织的点
        JianJiaRectangles: Polyline
            间架围合单元的矩形区域        

'''

ghenv.Component.Name = "简单间架"
ghenv.Component.NickName = "简单间架"
ghenv.Component.Description = "单座建筑的间架平面布局"
ghenv.Component.Message = "0.0.1"
ghenv.Component.Category = "Moths"
ghenv.Component.SubCategory = "BayBeam Layout"
ghenv.Component.PanelSection = 2

import ghpythonlib.treehelpers as th
import rhinoscriptsyntax as rs

import moths.utility as moths_utility
import numpy as np


def simple_building_framework():
    # 绘制参考线及参考线上金柱位置参考点
    axis_start_pt = rs.CurveStartPoint(Axis)
    axis_end_pts = rs.CurveEndPoint(Axis)

    vector_axis = axis_end_pts - axis_start_pt
    unitizedVec_axis = rs.VectorUnitize(vector_axis)
    halfDepthVec_goldenPillar = rs.VectorScale(
        unitizedVec_axis, BuildingDepth / 2
    )
    halfDepthVec_goldenPillar_reversed = rs.VectorReverse(
        halfDepthVec_goldenPillar
    )
    end_pt_goldenPillar = axis_start_pt + halfDepthVec_goldenPillar_reversed
    start_pt_goldenPillar = axis_start_pt + halfDepthVec_goldenPillar

    # 绘制参考线上檐柱位置参考点
    if FrontCorridorDepth:
        halfDepthVec_frontEavePillar = rs.VectorScale(
            unitizedVec_axis, BuildingDepth / 2 + FrontCorridorDepth
        )
        start_pt_eavePillar = axis_start_pt + halfDepthVec_frontEavePillar
    else:
        start_pt_eavePillar = None

    if RearCorridorDepth:
        halfDepthVec_rearEavePillar = rs.VectorScale(
            rs.VectorReverse(unitizedVec_axis),
            BuildingDepth / 2 + RearCorridorDepth,
        )
        end_pt_eavePillar = axis_start_pt + halfDepthVec_rearEavePillar
    else:
        end_pt_eavePillar = None

    # 绘制间架柱网
    pts_onAxis = [
        start_pt_eavePillar,
        start_pt_goldenPillar,
        end_pt_goldenPillar,
        end_pt_eavePillar,
    ]
    pts_onAxis = [x for x in pts_onAxis if x is not None]

    if Widths:
        widths = [float(i) for i in Widths.strip().split(",")]
        total = 0
        widths_accumulated = [0] + [total := total + i for i in widths]

    if Width:
        width_lst = [Width] * Num
        total = 0
        width_lst_accumulated = [0] + [total := total + i for i in width_lst]

    if Direction:
        angle = 90
    else:
        angle = -90

    unitizedVec_axis_vertical = rs.VectorRotate(
        unitizedVec_axis, angle, [0, 0, 1]
    )
    if Widths:
        vecs = [
            rs.VectorScale(unitizedVec_axis_vertical, i)
            for i in widths_accumulated
        ]
    elif Width:
        vecs = [
            rs.VectorScale(unitizedVec_axis_vertical, i)
            for i in width_lst_accumulated
        ]
    else:
        vecs = None

    pts_onAxis = [
        start_pt_eavePillar,
        start_pt_goldenPillar,
        end_pt_goldenPillar,
        end_pt_eavePillar,
    ]
    pts_onAxis = [x for x in pts_onAxis if x is not None]

    if vecs:
        columnGrid = [[pt + vec for pt in pts_onAxis] for vec in vecs]
        columnGrid_tree = th.list_to_tree(columnGrid)

        # 绘制间架
        grouped_pts = moths_utility.nestedListGrouping4(columnGrid)
        grouped_pts_tree = th.list_to_tree(grouped_pts)
        grouped_pts_array = np.array(grouped_pts)
        grouped_pts_array = np.c_[grouped_pts_array, grouped_pts_array[:, :1]]

        jianjia_rec = list(
            np.apply_along_axis(rs.AddPolyline, 1, grouped_pts_array)
        )

        return columnGrid_tree, grouped_pts_tree, jianjia_rec
    else:
        return [None] * 3


if __name__ == "__main__":
    if Axis and BuildingDepth:
        JianJiaPts, JianJiaPtsGrouped, JianJiaRectangles = (
            simple_building_framework()
        )
  

代码下载(2_7_1_04.gh)

5)直角平行折线,廊

古代建筑群中建筑之间往往用廊连接,在功能上提供遮荫、可休憩的步道,减少雨、风、日照的影响;并有助于建筑总体布局的整体和谐平衡,为布局中的动线和空间划分的元素之一。廊可以随意曲直,这里仅选择延给定两个方向的曲直方式(图 2.7.1-9),包括正交曲直。构建廊需要解决两个问题,一是,输入条件为关键控制点时,如何生产延给定两个向量方向的折线,对应组件直角平行折线;二是,构建横梁,对应组件

第一个问题解决的关键是应用正弦法则计算延两个向量边的长度,对应函数为calculate_sides(),如图 2.7.1-9 中的图解(右),给定边 CD,和 ∠CBD、∠DCB、∠CDB 计算边 CB 和 BD 的长度,或边 CA 和 AD 的长度。输入端ReferenceCurve可以实现给定一根参考线,来确定是选择边CBD,还是边CAD。

第二个问题解决的主要思路是,将两条平行折线延转折点打断为直线,对应两两一组,如图 2.7.1-9 中的图解(左下)。因为每段的两条平行直线并不等长,多出有转折部分,如边AB 和 边EF。所以先根据对边的起始点最近点投影打断为多段,如将直线AC,打断为直线AB 和直线BC;将直线DF,打断为直线DE 和直线EF。将平行两条直线打断后,需要提取出直线BC 和直线DE,判断的依据是通过判断两两直线始末点是否成直角的情况为2次,例如判断直线AB 和直线DE,只有∠BDE 为直角,成直角的情况为1次;而直线BD 和直线DE,∠BDE 和∠CED 均为直角,成直角的情况为2次。提取出成对对齐的两条平行线后,等分点连横梁完成廊平面的构建。

pys

图 2.7.1-9 简单间架组件构建

直角平行折线 (Python Script 组件)

  '''
    将多个顺序点连为直角(垂直坐标系)平行(四边形)折线
    Inputs:
        Points: List Access; Point3d
            控制点
        VectorA: Item Access; ghdoc Object
            向量A,用于控制折线方向
        VectorB: Item Access; ghdoc Object
            向量B,用于控制折线方向
        ReferenceCurve: Item Access; ghdoc Object
            参考折线,用于控制折线的位置
        Reset:  Item Access;  bool
            重新计算
    Output:
       Polylines: List[Polyline]
            生产的折线
'''

ghenv.Component.Name = "直角平行折线"
ghenv.Component.NickName = "直角平行折线"
ghenv.Component.Description = (
    "将多个顺序点连为直角(垂直坐标系)平行(四边形)折线"
)
ghenv.Component.Message = "0.0.1"
ghenv.Component.Category = "Moths"
ghenv.Component.SubCategory = "BayBeam Layout"
ghenv.Component.PanelSection = 1

import ghpythonlib.treehelpers as th
import rhinoscriptsyntax as rs
import Rhino.Geometry as rg
import Rhino
import scriptcontext as sc

import moths.utility as moths_utility
import numpy as np
import math
import random


def included_angle(vec1, vec2):
    norm_v0 = np.linalg.norm(vec1)
    norm_v2 = np.linalg.norm(vec2)
    dot_product = np.dot(vec1, vec2)
    cos_theta = dot_product / (norm_v0 * norm_v2)
    angle_radians = np.arccos(cos_theta)
    return angle_radians


def calculate_sides(a, A_deg, B_deg, C_deg):
    # 用正弦法则计算边 b 和边 c 的长度
    b = a * math.sin(B_deg) / math.sin(A_deg)
    c = a * math.sin(C_deg) / math.sin(A_deg)

    return b, c


def regutangularPolyline():
    points_paired = [[Points[i], Points[i + 1]] for i in range(len(Points) - 1)]
    rectangles_dict = {}
    rectangles_lst = []
    corners_pts = []
    for idx, pts in enumerate(points_paired):
        pt0, pt2 = pts
        distCD = rs.Distance(pt0, pt2)
        vector02 = pt2 - pt0

        angleACB = included_angle(VectorA, VectorB)
        angleCBD = np.pi - angleACB

        angleDCB = included_angle(vector02, VectorB)
        angleCDB = included_angle(vector02, VectorA)

        distCB, distBD = calculate_sides(distCD, angleCBD, angleDCB, angleCDB)

        if angleDCB > np.pi / 2:
            vectorB_ = -VectorB
        else:
            vectorB_ = VectorB
        if angleCDB > np.pi / 2:
            vectorA_ = -VectorA
        else:
            vectorA_ = VectorA

        pt1 = pt0 + rs.VectorScale(vectorA_, distCB)
        pt3 = pt0 + rs.VectorScale(vectorB_, distBD)

        edge1 = rg.PolylineCurve(
            [pt0, pt1, pt2]
        )  
        edge2 = rg.PolylineCurve(
            [pt0, pt3, pt2]
        )  

        rectangles_dict[idx] = [edge1, edge2]
        rectangles_lst.append([edge1, edge2])
        corners_pts.append([pt1, pt3])

    rectangles_tree = th.list_to_tree(rectangles_lst)
    
    absTol = Rhino.RhinoDoc.ActiveDoc.ModelAbsoluteTolerance
    selectedPolylines = []
    if ReferenceCurve is None:
        for polylines in rectangles_lst:
            selectedPolyline = random.choice(polylines)
            if selectedPolylines:
                events_lst = [
                    rg.Intersect.Intersection.CurveCurve(
                        line, selectedPolyline, absTol, absTol
                    )
                    for line in selectedPolylines
                ]
                overlap = []
                for events in events_lst:
                    for ccx_event in events:
                        overlap.append(ccx_event.IsOverlap)

                overlap_num = sum(overlap)
                print(overlap_num)
                if overlap_num > 0:
                    polylines.remove(selectedPolyline)
                    selectedPolyline = polylines[0]

            selectedPolylines.append(selectedPolyline)
    else:
        for polylines, cornerPts in zip(rectangles_lst, corners_pts):
            closestPtParam = [
                rs.CurveClosestPoint(ReferenceCurve, pt) for pt in cornerPts
            ]
            closestPts = [
                rs.EvaluateCurve(ReferenceCurve, param)
                for param in closestPtParam
            ]
            closestDist = [
                rs.Distance(cornerpt, closestpt)
                for cornerpt, closestpt in zip(cornerPts, closestPts)
            ]
            idx_maximumValue = closestDist.index(min(closestDist))
            selectedPolylines.append(polylines[idx_maximumValue])

    return selectedPolylines


if __name__ == "__main__":
    if Points and VectorA and VectorB:
        Polylines = regutangularPolyline()
        if Reset:
            Polylines = regutangularPolyline()
  

(Python Script 组件)

  '''
    根据参考折线绘制廊平面
    Inputs:
        Polyline: Item Access; Polyline
            基础折线
        Width:  Item Access; float
            廊宽
        BeamSpacing: Item Access; float
            梁宽
        Domain: Item Access; float
            垂直探测误差区间
    Output:
        SegmentsPaired: Tree[Line]
            成对的平行线
        Beam: Tree[line]
            梁
        OffsetPolyline: Polyline
            偏移折线
        Points: Tree[Point3d]
            点(柱)
'''

ghenv.Component.Name = "廊"
ghenv.Component.NickName = "廊"
ghenv.Component.Description = "根据参考折线绘制廊平面"
ghenv.Component.Message = "0.0.1"
ghenv.Component.Category = "Moths"
ghenv.Component.SubCategory = "BayBeam Layout"
ghenv.Component.PanelSection = 2

import ghpythonlib.treehelpers as th
import rhinoscriptsyntax as rs
import Rhino.Geometry as rg
import ghpythonlib.components as ghc
import scriptcontext as sc

import moths.utility as moths_utility


def polyline2corridor():
    polyline_offset = ghc.OffsetCurve(Polyline, Width, None, 1)
    polyline_id = sc.doc.Objects.AddPolyline(Polyline)
    polylineExplode_origin = rs.ExplodeCurves(polyline_id)
    polylineExplode_offset = rs.ExplodeCurves(polyline_offset)
    polylines_paired = [
        [i, j] for i, j in zip(polylineExplode_origin, polylineExplode_offset)
    ]

    segmentsPaired = []
    for line_paired in polylines_paired:
        endPts = [
            [rs.CurveStartPoint(line), rs.CurveEndPoint(line)]
            for line in line_paired
        ]
        line_paired_reversed = list(reversed(line_paired))
        closestPts_param = [
            [rs.CurveClosestPoint(line, pt) for pt in pts]
            for pts, line in zip(endPts, line_paired_reversed)
        ]
        closestPts = [
            [rs.EvaluateCurve(line, param) for param in params]
            for params, line in zip(closestPts_param, line_paired_reversed)
        ]

        lines_split = [
            rs.SplitCurve(line, params, delete_input=False)
            for params, line in zip(closestPts_param, line_paired_reversed)
        ]
        segments_num = [len(i) for i in lines_split]
        for seg_0 in lines_split[0]:
            seg_0_endPts = [rs.CurveStartPoint(seg_0), rs.CurveEndPoint(seg_0)]
            vector_seg_0 = seg_0_endPts[1] - seg_0_endPts[0]
            for seg_1 in lines_split[1]:
                seg_1_endPts = [
                    rs.CurveStartPoint(seg_1),
                    rs.CurveEndPoint(seg_1),
                ]
                seg_angles = [
                    [
                        rs.VectorAngle(pt0 - pt1, vector_seg_0)
                        for pt1 in seg_1_endPts
                    ]
                    for pt0 in seg_0_endPts
                ]
                vertical_detection = sum(
                    [
                        1
                        for i in moths_utility.flatten_lst(seg_angles)
                        if abs(i - 90) < Domain
                    ]
                )
                if vertical_detection == 2:
                    segmentsPaired.append([seg_0, seg_1])

    beams = []
    beam_columns = []
    for segs in segmentsPaired:
        dividePts = [rs.DivideCurveLength(seg, BeamSpacing) for seg in segs]
        segs0_endPts, segs1_endPts = rs.CurveEndPoint(
            segs[0]
        ), rs.CurveEndPoint(segs[1])
        if None not in dividePts:
            dividePts = [[i, j] for i, j in zip(*dividePts)] + [
                [segs0_endPts, segs1_endPts]
            ]
            beam_lst = [rs.AddLine(i, j) for i, j in dividePts]
            beams.append(beam_lst)

            beam_columns.append(dividePts)

    return (
        polyline_offset,
        th.list_to_tree(segmentsPaired),
        th.list_to_tree(beams),
        th.list_to_tree(beam_columns),
    )


if __name__ == "__main__":
    if Polyline and Width and BeamSpacing:
        OffsetPolyline, SegmentsPaired, Beam, Points = polyline2corridor()
  

代码下载(2_7_1_05.gh)

6)辅助轴线网格

单座建筑并不能构成一个空间,但是由多个单座建筑组合的院落就会形成相对建筑明确的的室外空间。群体的建筑群外墙能够限制视线,构成垂直面,达到外部空间的围合。在各个单座建筑之间的空隙处或外围围有围墙或单面空廊,形成封闭的围合空间,具有最强的围合感。如果不设置院墙或单面空廊,建筑间“空隙”的多少会调整封闭感的强弱,或相对开放感的强弱。除了直接的院墙封闭方式,消除空间空隙的一种方法是围绕空间的各个单座建筑尽量重叠,以阻挡视线出入,或利用自然要素进行视线的隔挡。

潜在的“院落”空间是指非封闭的院落,是力求在设计中使建筑群井然有序,在单座建筑间及其构成的空间之间构建联系的设计原则。要想得到井然有序的布局,最简单和普通的方法之一就是使单座建筑物相互之间的夹角为直角;并可直接利用某一建筑物的形状和线条(例如单座建筑的矩形轮廓边线)与附近建筑物的形状和线条相互结合的方式,须是沿一已知建筑物的边缘向外延长虚线,然后使其与临近建筑物边缘一致对齐,即延建筑界面延申的潜在无形界面。或者将其理解为纵横轴线组成的不等距的网格进行的布局控制。这种方法能在建筑群相邻建筑间创造出令人深思、但又明显清晰的视觉联点;并容许大量视线从任何一座建筑进入到中心开放空间中,而不会受到邻近对立建筑的直接影响,具有空间层次,更显空间的深远。在对圆明园四十景图咏建筑群的分析中可以发现几乎所有的建筑群均遵循上述原则。

为了方便应用纵横轴线组成不等距网格进行布局控制的方法,定义辅助轴线网格组件,参考输入端的直线MainAxis建立纵横轴,及网格。

pys

图 2.7.1-10 辅助轴线网格组件

辅助轴线网格 (Python Script 组件)

  '''
    绘制参考垂直轴线和网格,包括在轴线上提取点
    Inputs:
        MainAxis: Item Access; ghdoc Object
            参考直线
        ExtendMAUpper: Item Access; float
            主轴线一端延申距离
        ExtendMALower: Item Access; float
            主轴线另一端延申距离
        ExtendVerticalMALeft:Item Access; float
            垂直轴线一端延申距离
        ExtendVerticalMARight: Item Access; float
            垂直轴线另一端延申距离
        Distance4ptonMA: Item Access; float
            延主轴线偏移
        Distance2IntersectionPt: Item Access; float
            垂直轴线上取点
        CellWidth: Item Access; float
            单元格宽
        CellHeight: Item Access; float
            单元格高
        ShowGrid:Item Access; bool
            是否显示参考网格
    Output:
        ExtendedAxis: Line
            主轴线
        ExtendedVerticalAxis: Line
            垂直轴线
        Grid: List[Line]
            网格线列表
        IntersectionPt:Point3d
            交叉点
        PtOnSubsidiaryAxis:
            从垂直轴线上提取的点

'''

ghenv.Component.Name = "辅助轴线网格"
ghenv.Component.NickName = "辅助轴线网格"
ghenv.Component.Description = "辅助中国古代建筑群总平设计的轴线网格"
ghenv.Component.Message = "0.0.1"
ghenv.Component.Category = "Moths"
ghenv.Component.SubCategory = "BayBeam Layout"
ghenv.Component.PanelSection = 2

import ghpythonlib.treehelpers as th
import rhinoscriptsyntax as rs

import moths.utility as moths_utility
import numpy as np


def auxiliary_axis_grid():
    # 主轴线的延申(从开始点)
    mainAxis_st_pt = rs.CurveStartPoint(MainAxis)
    mainAxis_end_pt = rs.CurveEndPoint(MainAxis)

    mainAxis_vector = rs.VectorUnitize(mainAxis_end_pt - mainAxis_st_pt)
    mainAxis_vector_reversed = rs.VectorReverse(mainAxis_vector)
    pt4extendMAUpper = mainAxis_st_pt + rs.VectorScale(
        mainAxis_vector, ExtendMAUpper
    )
    pt4extendMALower = mainAxis_st_pt + rs.VectorScale(
        mainAxis_vector_reversed, ExtendMALower
    )
    extendedMA = rs.AddLine(pt4extendMAUpper, pt4extendMALower)

    # 垂直主轴线
    ptOn_mainaxis = mainAxis_st_pt + rs.VectorScale(
        mainAxis_vector, Distance4ptonMA
    )

    vector_vertical_mainaxis = rs.VectorRotate(mainAxis_vector, 90, [0, 0, 1])
    vector_vertical_mainaxis_reversed = rs.VectorReverse(
        vector_vertical_mainaxis
    )
    pt4extendVerticalMALeft = ptOn_mainaxis + rs.VectorScale(
        vector_vertical_mainaxis, ExtendVerticalMALeft
    )
    pt4extendVerticalMARight = ptOn_mainaxis + rs.VectorScale(
        vector_vertical_mainaxis_reversed, ExtendVerticalMARight
    )
    extendedVerticalAxis = rs.AddLine(
        pt4extendVerticalMALeft, pt4extendVerticalMARight
    )

    ptOnSubsidiaryAxis = ptOn_mainaxis + rs.VectorScale(
        vector_vertical_mainaxis, Distance2IntersectionPt
    )

    # 参照网格
    offset_dist_mainAxis_left = []
    moths_utility.recursive_add(
        0, CellWidth, ExtendVerticalMALeft, offset_dist_mainAxis_left
    )
    offset_dist_mainAxis_right = []
    moths_utility.recursive_add(
        0, CellWidth, ExtendVerticalMARight, offset_dist_mainAxis_right
    )

    extendedMA_ptonMA = rs.AddLine(
        ptOn_mainaxis + rs.VectorScale(mainAxis_vector, ExtendMAUpper),
        ptOn_mainaxis + rs.VectorScale(mainAxis_vector_reversed, ExtendMALower),
    )

    right_offset = moths_utility.flatten_lst(
        [
            rs.OffsetCurve(extendedMA_ptonMA, vector_vertical_mainaxis, -dist)
            for dist in offset_dist_mainAxis_left[:-1]
        ]
    )
    left_offset = moths_utility.flatten_lst(
        [
            rs.OffsetCurve(extendedMA_ptonMA, vector_vertical_mainaxis, dist)
            for dist in offset_dist_mainAxis_right[:-1]
        ]
    )
    left_offset.reverse()
    offset_mainAxis = left_offset + [extendedMA_ptonMA] + right_offset

    offset_dist_vertical_mainAxis_upper = []
    moths_utility.recursive_add(
        0, CellHeight, ExtendMAUpper, offset_dist_vertical_mainAxis_upper
    )
    offset_dist_vertical_mainAxis_lower = []
    moths_utility.recursive_add(
        0, CellHeight, ExtendMALower, offset_dist_vertical_mainAxis_lower
    )

    lower_offset = moths_utility.flatten_lst(
        [
            rs.OffsetCurve(extendedVerticalAxis, mainAxis_vector, dist)
            for dist in offset_dist_vertical_mainAxis_upper[:-1]
        ]
    )
    upper_offset = moths_utility.flatten_lst(
        [
            rs.OffsetCurve(extendedVerticalAxis, mainAxis_vector, -dist)
            for dist in offset_dist_vertical_mainAxis_lower[:-1]
        ]
    )
    upper_offset.reverse()
    offset_vertical_mainAxis = lower_offset + upper_offset

    if ShowGrid:
        grid = th.list_to_tree([offset_vertical_mainAxis, offset_mainAxis])
    else:
        grid = None

    return (
        extendedMA,
        extendedVerticalAxis,
        grid,
        ptOn_mainaxis,
        ptOnSubsidiaryAxis,
    )


if __name__ == "__main__":
    if MainAxis and Distance4ptonMA and CellHeight:
        (
            ExtendedAxis,
            ExtendedVerticalAxis,
            Grid,
            IntersectionPt,
            PtOnSubsidiaryAxis,
        ) = auxiliary_axis_grid()
  

代码下载(2_7_1_06.gh)

7)2维轴向移动,轴线镜像

沿着给定参考平面的 X 轴和 Y 轴移动对象,和对象轴向镜像是经常用到的功能,方便调整对象的相对位置。如图 2.7.1-11,定义了组件2维轴向移动轴线镜像。 对于对象B,会按照参考平面 XY 轴线移动,移动结果为对象C;并以红虚线为轴线,镜像为对象C’。

pys

图 2.7.1-11 2维轴向移动和轴线镜像组件构建

2维轴向移动 (Python Script 组件)

  '''
    按给定的参考平面的轴线移动输入对象
    Inputs:
        Object: Item Access; ghdoc Object
            待变换的对象
        Amplitude: Item Access; float
            偏移倍数
        Plane: Item Access; ghdoc Object
            参考平面
        SwitchX: Item Access; bool
            沿 X 轴向翻转
        SwitchY: Item Access; bool
            沿 Y 轴向翻转
        x: Item Access; float
            X 向偏移距离
        y: Item Access; float
            Y 向偏移距离
    Output:
        MovedObject: ghdoc Object
            偏移后的对象
'''

ghenv.Component.Name = "2维轴向移动"
ghenv.Component.NickName = "2维轴向移动"
ghenv.Component.Description = "沿XY轴向移动对象"
ghenv.Component.Message = "0.0.1"
ghenv.Component.Category = "Moths"
ghenv.Component.SubCategory = "BayBeam Layout"
ghenv.Component.PanelSection = 1

import ghpythonlib.treehelpers as th
import rhinoscriptsyntax as rs
import Rhino.Geometry as rg


def moveObjecAlongAxis2D():
    pt_origin = rg.Point3d(0, 0, 0)

    if Plane:
        xaxis = rs.VectorUnitize(Plane.XAxis)
        yaxis = rs.VectorUnitize(Plane.YAxis)
    else:
        xaxis = rs.VectorUnitize(rs.WorldXYPlane().XAxis)
        yaxis = rs.VectorUnitize(rs.WorldXYPlane().YAxis)

    if SwitchX:
        switchx = -1
    else:
        switchx = 1
    if SwitchY:
        switchy = -1
    else:
        switchy = 1

    ptMoved_x = pt_origin - xaxis * x * switchx
    ptMoved = ptMoved_x + yaxis * y * switchy

    MDScaled = rs.VectorScale(ptMoved, Amplitude)
    xform = rs.XformTranslation(MDScaled)
    objectmoved = rs.TransformObject(Object, xform, True)

    return objectmoved


if __name__ == "__main__":
    if Object and x and y:
        MovedObject = moveObjecAlongAxis2D()
  

轴线镜像 (Python Script 组件)

  '''
    沿指定的轴线镜像对象
    Inputs:
        Objects: Item Access; ghdoc Object
            待镜像的对象
        Axis:  Item Access; ghdoc Object
            轴线
    Output:
        MirroredObjects: ghdoc Object
            镜像后的对象
        VerticalPlane: Plane
            垂直参考平面
        Normal: Vector
            垂直向量
'''

ghenv.Component.Name = "轴线镜像"
ghenv.Component.NickName = "轴线镜像"
ghenv.Component.Description = "沿轴线垂直面镜像对象"
ghenv.Component.Message = "0.0.1"
ghenv.Component.Category = "Moths"
ghenv.Component.SubCategory = "BayBeam Layout"
ghenv.Component.PanelSection = 1

import ghpythonlib.treehelpers as th
import rhinoscriptsyntax as rs
import Rhino.Geometry as rg


def mirrorObjects():
    axis_start_pt = rs.CurveStartPoint(Axis)
    axis_end_pts = rs.CurveEndPoint(Axis)

    verticalVec = rs.VectorRotate(axis_end_pts - axis_start_pt, 90, [0, 0, 1])
    plane = rs.PlaneFromNormal(axis_start_pt, verticalVec)
    xform = rg.Transform.Mirror(plane)
    mirror_objects = rs.TransformObjects(Objects, xform, copy=True)

    return mirror_objects, plane, verticalVec


if __name__ == "__main__":
    if Objects and Axis:
        MirroredObjects, VerticalPlane, Normal = mirrorObjects()
  

代码下载(2_7_1_07.gh)

2.7.1.3 绘制镂月云开

镂月云开位于圆明园后湖东南一隅,与九洲清宴和天然图画为邻,如图 2.7.1-12。

pys

图 2.7.1-12 圆明园镂月云开平面图及其圆明园四十景图咏,, 图片引子参考文献[5]128

使用构建的间梁布局工具集,通过辅助轴线网格组件建立垂直轴线对位关系,将定位轴线作为间梁布局各类型的输入条件,绘制镂月云开建筑群平面图,如图 2.7.1-13。

pys

图 2.7.1-13 绘制镂月云开程序和结果

代码下载(2_7_1_08.gh)

参考文献:

[1] 傅熹年, 著. 中国古代城市规划、建筑群布局及建筑设计方法研究[M]. 北京: 中国建筑工业出版社, 2001.

[2] 王溥(宋), 撰. 唐会要[M]. 上海: 上海古籍出版社, 2006.

[3] 郭黛姮,贺艳.圆明园的“记忆遗产”[M].杭州:浙江古籍出版社,2010.

[4] (美)诺曼·K·布思著.曹礼昆,曹德鲲等译.风景园林设计要素[M].北京:北京科学技术出版社,2015.

[5] 何重义,曾昭奋.圆明园园林艺术[M].北京:中国大百科全书出版社,2010.