简介:一套面向Cityengine用户的城市规划规则库,适合城乡规划、数字城市、三维建模方向的设计师与学习者,用于解决从零搭建复杂城市模型费时费力的问题。压缩包内共8个文件,以.cga和.cgb规则脚本为核心,配合.xml项目配置、工程文件及.txt说明文档,完整展现了规则库从地形、道路到建筑生成的组织逻辑;整体仅36KB,体积小巧,便于快速解析与复用。用户可将规则库直接导入Cityengine,通过调整建筑高度、街道走向、地块密度等参数实时预览不同方案,也可参考CGA脚本自行修改或扩展规则,实现个性化城市设计。已有1108人学习该资源,对希望系统理解CGA规则语法、掌握批量建模流程的读者而言,是一份轻量而实用的参考工具。
1. Cityengine 城市规划02规则库:为什么我劝你别再手动画城市模型
做了几年城市更新项目,我慢慢发现一个规律:凡是靠手工一点点挤出体块来推敲方案的日子,基本都浪费在改版上了。 Cityengine 城市规划规则库,正是把地块、道路、建筑、退线这些规划要素封装成一套可复用的规则资产。它的价值不是你点一个按钮就生成一座城,而是让你在改容积率、换业态、调控高的时候,像改参数表一样改模型。这篇文章不是规则库说明书,而是从文件结构、CGA 规则写法到参数陷阱的一线拆解,适合正在做城市设计和国土空间方案的规划师、建筑师,也适合刚接触 CityEngine 规则库的开发者。我把最值得投入的部分讲透,把最容易翻车的坑提前摆出来。
2. 拆开规则库的“黑匣子”:从CGA规则到城市生成的完整链路
很多新手拿到一个“城市规划02规则库”,第一件事就是把整个文件夹拖进 CityEngine,然后等着看效果。结果通常是一堆建筑从地块里长出来,有的叠在一起,有的干脆消失。这不是规则库错了,而是你没有理解它的运行方式。规则库本质是一组带输入输出的规则程序,不是贴图素材包。要让它听话,先得看清它装了什么、按什么顺序执行。
2.1 规则库到底装了什么:文件、图层与坐标系
一个面向城市规划的规则库,文件结构大致是这样:
rules/ 00_base.cga # 常量、纹理路径、通用材质 01_zoning.cga # 区划分类、地块属性入口 02_building.cga # 建筑体量、立面生成 streets/ street.cga # 道路和步行道规则 assets/ textures/ # 立面、屋面、地面纹理 models/ # 树、路灯、公交站 data/ parcels.shp # 地块矢量面 roads.shp # 道路中心线 zoning.csv # 区划代码和指标表不要只看文件名,先用命令行把规则库完整列一遍:
find . -type f | sort | head -50head -50只是限制输出量。真正该看的是.cga文件之间有没有依赖顺序,比如00_base.cga里定义了BASE_COLOR,后面的02_building.cga引用了它。如果你只把02_building.cga单独拖进场景,引擎会直接报“属性未定义”。这时不是规则坏了,而是文件加载顺序不对。CityEngine 的规则编辑器按文件夹整体加载,所以规则库的目录层级就是在定义依赖关系。
比文件顺序更隐蔽的是坐标系。GIS 数据经常混用投影:有的地块面来自 Web Mercator,有的来自 CGCS2000,如果你把不同投影的数据丢进同一个规则库,生成的地块会偏出几公里。我拿到一个规则库的第一件事,是看场景的 World 坐标系和data/parcels.shp的源投影是否一致。不一致时先转坐标准系再跑生成,否则后面对容积率的统计全是错的。
2.2 城市生成的核心流程:地块、递归与属性
CityEngine 生成模型的逻辑,一句话概括:从一个矢量面开始,把面一步步切割、拉伸、细分,最终生成带材质的模型。CGA 规则是一个递归系统,不是一次性画完就结束的脚本。
下面是最小的城市体量规则链:
Lot --> setback(3) { all: Building | border: Garden } Building --> extrude(world.y, groundFloorHeight) { all: Footprint } Footprint --> split(y) { floorHeight: Floor }* Floor --> comp(f) { front: Facade | top: Roof }这四个规则做了四件事:
Lot是初始形状,setback(3)表示向内退 3 米,内圈交给Building,外圈变成Garden;Building沿世界坐标 Y 轴拉伸groundFloorHeight的高度,得到Footprint;Footprint使用split(y)沿 Y 轴按层高切分,每一层进入Floor;Floor用comp(f)把前面和顶部分离。
setback对应规划里的退线,extrude对应建筑高度,split对应楼层划分。很多新手只写了extrude,没有继续细分,于是所有建筑都是方盒子。那不是城市,是纪念碑。
规则库里的执行顺序一般固定为Block -> Lot -> Building -> Floor -> Wall/Window。在严格的城市设计规则库里,还会追加Roof、Balcony、Shading等分支,保证生成立面风格一致。理解这个递归链,你才能知道生成出错时该去查哪一层。
2.3 为什么“02”不只是版本号:分层的工程习惯
“城市规划02规则库”里的“02”,我通常读作“第二层规则:建筑体量”,而不是第二个版本。一个可维护的规则库,不会把所有逻辑写进一个巨型文件。常见做法是分层编号:
00_base.cga:定义通用材质、颜色、常量;01_zoning.cga:把地块按区划分成住宅、商业、工业;02_building.cga:针对不同区划生成不同体量和立面;03_civic.cga:学校、医院等公共建筑。
这样分层的好处很直接:规划师想改商业地块的容积率,只需打开02_building.cga,把commercialFAR从 4.5 改成 5.0,不用碰道路规则,更不会影响住宅立面。
我还会在每个规则文件头部花一分钟写块注释,记录参数来源和修改日期:
/* commercial building rule updated: 2026-02-14 by planner group parameters: commercialFAR, maxBuildingHeight */ version "2026.02" @Group("招商参数", "地块") @Parameter("容积率上限") commercialFAR = 4.5 @Group("招商参数", "地块") @Parameter("建筑控高") maxBuildingHeight = 60这里的version字符串不是给 CityEngine 读的,是给人读的。当规划师和程序员来回调整时,没有版本注释,你根本不知道4.5是哪次会议的决策。规则库越到后期,注释越比代码值钱。
3. 从0搭一套城市规划规则库:核心CGA代码与参数设置
这一章我会从零搭一套能用于实际规划推演的规则库,不追求花哨,把入口、区划、建筑、街道四层逻辑串起来。每个代码块你都可以直接复制到自己的工程里跑。
3.1 先建工程:导入地块并绑定入口规则
准备工作:新建 CityEngine 工程,把parcels.shp和roads.shp放到项目的data目录。在场景中选中地块图层,准备绑规则文件。
入口规则我一般固定命名为Lot,因为 CityEngine 里默认的初始 shape 就叫 Lot。先写一个最外层分流:
@Hidden @StartRule Lot --> case zoneType == "COM": CommercialBlock() case zoneType == "RES": ResidentialBlock() else: PublicSpace()@StartRule标记入口。zoneType是地块属性,从parcels.shp里的字段映射而来。这里用了case分支,等于把地块按规划用途分流。这样后期你想加一种“商住混合”类型,只需要再加一个case分支,不会影响其他类型。
绑规则的步骤是:选中地块图层,在 Inspector 窗口的 Rule File 栏目选择这个.cga文件,然后点 Generate。如果看不到生成结果,先检查地块属性面板里有没有zoneType这个字段,没有的话基础数据就没接上。
3.2 定义区划属性:把规划指标写进 CGA
为了让规则库脱离 GIS 图层也能演示,我会在规则文件里直接定义一套指标参数。这一步很关键,因为规则库的价值就是把指标变成可变参数。
@Group("区划", "用地属性") @Parameter("用地代码") zoneType = "COM" @Group("区划", "用地属性") @Parameter("容积率上限") FAR = 3.5 @Group("区划", "用地属性") @Parameter("建筑控高") maxHeight = 60 @Group("区划", "用地属性") @Parameter("首层高度") groundFloorHeight = 6这些参数会出现在 CityEngine 的属性面板里,可以直接用滑杆调整。规划师不需要懂代码,拖动滑块就能看方案变化。
接下来写商业地块的体量规则:
CommercialBlock --> setback(5) { all: BuildingFootprint | border: StreetLine } BuildingFootprint --> extrude(world.y, groundFloorHeight) { all: MainVolume } MainVolume --> split(y) { floorHeight: Floor }* Floor --> color("#aab8c4") comp(f) { front: Facade | top: Roof }逻辑说明:
setback(5)先向内退 5 米,退出来的边界做成StreetLine,用于之后铺路;BuildingFootprint用extrude(world.y, groundFloorHeight)拉出首层体量;MainVolume再按floorHeight沿 Y 轴重复切出楼层;- 每层
Floor上色、分离立面与屋面。
你可以把floorHeight设为 4 米、FAR设为 3.5 试试。这时建筑总高度等于groundFloorHeight + floorHeight * 楼层数,但这里的楼层数并没有和容积率挂钩,下一章我会讲怎么用面积参数倒推层数。现在先让模型“长起来”。
3.3 街道与公共空间:用道路中心线生成路网
街道不要混在建筑规则里生成。我习惯单独写一个street.cga,作用在道路中心线图层上。
@StartRule Street --> s(roadWidth, 0, roadWidth) t(0, 0, 0) i("assets/road_texture.jpg")三行规则的含义:
s(roadWidth, 0, roadWidth)把当前 scope 的宽度设为roadWidth,Y 方向为 0;t(0,0,0)把 scope 平移回原点,避免偏位;i()实例化一张路面纹理。
这里必须注意,s()只改变当前 scope 的尺寸,不改变道路长度,长度由道路中心线本身的 extent 决定。所以用一个roadWidth参数控制机动车道宽度,是很常见的做法。
如果你想做步行道和路缘石,就在Street里再切分:
Street --> split(x) { 0.5: Sidewalk | roadWidth: Roadway | 0.5: Sidewalk } Sidewalk --> color("#c8c8c8") Roadway --> color("#404040")split(x)沿当前 scope 的 X 轴切分,两侧各 0.5 米是步行道,中间是机动车道。这种写法的好处是,路网总宽度变化时,只要你改roadWidth,步行道宽度保持不变。
4. 规则库里的关键参数:容积率、退线、楼层与朝向的数学关系
参数是规则库的命门。很多项目最后卡在“生成结果和规划指标对不上”,问题都出在这几组数学关系上。
4.1 容积率和建筑面积:为什么不能直接写整数
容积率是地上总建筑面积除以用地面积。就算你在规则里写了FAR = 3.5,CityEngine 不会自己去算层数。你需要把地块面积和标准层面积的关系写出来。
常见做法是在Lot入口处报告面积参数:
Lot --> report("plotArea", geometry.area) ...然后在体量规则里计算楼层数:
BuildingFootprint --> report("footprintArea", geometry.area) floors = FAR * geometry.area / floorPlateArea extrude(world.y, floors * floorHeight)可这里有个陷阱:geometry.area是当前形状的二维面积,不是用地面积。如果你在已经setback之后的形状上算面积,得到的是建筑投影面积,用它算容积率就会偏小。正确做法是在Lot阶段就把用地面积存成一个属性:
Lot --> plotArea = geometry.area report("plotArea", plotArea) setback(5) { all: BuildingFootprint | border: StreetLine } BuildingFootprint --> report("footprintArea", geometry.area) floors = FAR * plotArea / geometry.area extrude(world.y, floors * floorHeight)参数说明:
plotArea在Lot阶段记录,不随 setback 改变;floors = FAR * plotArea / geometry.area用容积率倒推层数;- 如果地块形状不规则,
geometry.area是投影面积,不考虑退台和阳台。
这种写法下,容积率变成硬约束。改FAR,建筑层数会自动跟着变;改地块面积,建筑高度也会被重新计算。
4.2 退线与日照:距离函数不只是美化
退线除了满足消防间距,还会影响建筑实际可建范围。CGA 里用setback处理,但它可以带多个参数,控制不同方向:
Lot --> setback(3, 10, 6, 10) { left: FrontYard | front: BackYard | all: MainZone }四个数字分别对应四边,顺序是 south, west, north, east。注意这里没有自动判断日照方向,你需要根据项目地理位置确定南向在哪边。如果不做方向区分,只写setback(5),所有退线距离一样,遇到东北向道路就会和规划条件冲突。
日照模拟不是 CGA 的主业,但你可以用太阳角度做一个简单的阴影范围判断:
Building --> shadow = tan(sunAngle) * buildingHeight setback(shadow) { all: ShadowArea }sunAngle需要手动输入,不能从 CityEngine 场景直接读取。所以这个公式更适合做方案对比,不适合做精确日照计算,精确日照我建议导出到专业软件里跑。
4.3 随机种子与城市肌理:让结果可复现
规则库里最怕的是“这次生成一个样,下次生成另一个样”。这不是城市有性格,是你没有控制随机种子。CGA 里任何随机函数都依赖当前形状的 seed,只有用srand()手动固定:
Lot --> srand(20260214) ...固定 seed 后,同一地块每次生成结果完全一致。如果你希望不同地块有不同形态,但整体可复现,可以根据地块 ID 生成种子:
Lot --> srand(20260214 + objectId) ...objectId是每个图层要素自带的主键。这样每个地块有不同的随机形态,但整条街道的生成结果可以稳定复现。对规划方案来说,这一点特别重要,不然汇报时动一下鼠标,所有高层位置都变了,甲方会以为你在变魔术。
5. 避坑:规则库运行翻车的典型场景
规则库用得多了,必然会翻车。我把最常见的几个坑按现象、原因、解决三层写出来,方便你在现场照着排查。
5.1 现象:建筑之间横七竖八互相穿插,甚至出现立体交叉
原因:多半是extrude用了局部坐标系而不是世界坐标系。局部坐标会随地块旋转,导致建筑斜着长。
解决:统一用world.y,不要用裸的y:
Building --> extrude(world.y, height)同时检查入口形状的 scope 方向,如果地块朝向混乱,在入口规则前加一句:
Lot --> alignScopeToGeometry(x, y, z) // 这里只是示意,实际用 alignScopeToGeometry 需要先 geometry5.2 现象:纹理整体发黑或者被拉成马赛克
原因:UV 坐标没有设置,CityEngine 使用默认投影时,纹理把一个 shape 整体铺满,物体形状稍微复杂就拉伸。
解决:在立面规则前手动设置贴图坐标:
Facade --> setupProjection(0, scope.xy, 1, 1) set(material.colormap, "assets/facade.jpg") projectUV(0)setupProjection(0, scope.xy, 1, 1)把纹理坐标按当前立面 scope 的 XY 方向对齐,第二个参数换成scope.xy意思是忽略厚度;projectUV(0)让纹理从 scope 的原点开始。调整后面的1,1可以控制纹理重复次数,立面高度大的话,建议改大这个数,否则图案会被拉伸。
5.3 现象:导出 FBX 后材质全部丢失
原因:规则库里的纹理用了绝对路径,模型导入其他软件后找不到原路径。
解决:把所有资产引用改为相对路径,并在工程里统一放到assets/下:
set(material.colormap, "/assets/facade.jpg")注意斜杠开头指的是工程根目录,不是系统盘。移动规则库文件夹时,保持assets和rules的相对位置不变,导出就大概率正常。
5.4 现象:容积率统计总差一截
原因:很多规则分支最后没有汇总到同一个report字段,或者split(y)切出的楼层没有被计算到面积里。
解决:只用一个字段累计,并且确认所有分支都执行了 report:
Footprint --> totalArea = 0 split(y) { floorHeight: AddFloor }* AddFloor --> totalArea = totalArea + geometry.area report("grossFloorArea", totalArea)这里要特别注意,CGA 的变量在递归分支间不是全局可写的。上面这种写法在实际运行中并不严谨,因为每层递归都会产生新作用域。更稳妥的做法是在最终楼层属性里报告单层面积,再在外部汇总,或者直接用reports里的累加工具。最保险的是每个Floor都单独report("grossFloorArea", geometry.area),最后导出报表时再相加。
5.5 现象:换了一台电脑,同样的规则生成结果完全不同
原因:默认 seed 基于形状的创建顺序,不同的导入顺序会产生不同随机序列。
解决:在入口规则第一行固定 seed,保证跨机器可复现:
@StartRule Lot --> srand(20260214) ...如果你希望每个地块不同形态,用objectId计算 seed,而不是完全依赖运行环境。
6. 验证与进阶:用 reports 把规则库变成“规划计算器”
规则库不是生成完模型就结束了,真正的价值是把模型和指标连起来。我在日常项目中,把reports当成了最常用的验证工具。在每个地块规则里写上报指标,生成完点开报表,容积率、建筑密度、建筑面积一目了然。
Lot --> report("landArea", geometry.area) report("plotArea", geometry.area)在体量规则里补上建筑面积:
BuildingFootprint --> report("footprintArea", geometry.area) report("grossFloorArea", FAR * geometry.area)这样生成后,选择所有地块,导出表格,直接就能和规划条件比对。不需要重新建模,只需要调整参数再跑一轮。
进阶用法是分层控制细节。我习惯在预览阶段关闭复杂纹理和模型,用lod简化远处体量:
Floor --> case lod == 0: Massing() else: DetailedFacade()lod是 CityEngine 的细节等级参数,你可以在场景属性里设置。方案推敲阶段用lod=0,渲染表现阶段切回lod=1,生成速度和视觉质量两条路都走通。
我现在的习惯是,每天下班前把当天所有生成结果用reports导出一份指标明细,和规划条件表逐条比对,第二天再针对偏差调参。这样规则库才真正变成了方案计算器,而不是一个“看起来像城市”的黑盒子。希望帮到你。
本文还有配套的精品资源,点击获取