news 2026/9/26 19:14:37

CityEngine规则库实战:从CGA写法到城市规划参数化生成

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CityEngine规则库实战:从CGA写法到城市规划参数化生成

简介:一套面向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 -50

head -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 需要先 geometry

5.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导出一份指标明细,和规划条件表逐条比对,第二天再针对偏差调参。这样规则库才真正变成了方案计算器,而不是一个“看起来像城市”的黑盒子。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/26 19:13:16

WPF MediaElement视频播放实战:路径、编码、硬件加速全解析

1. 项目概述:WPF里“播视频”远不止拖个控件那么简单WPF实现播放视频——这七个字看着简单,但真动手时,90%的人卡在第一步:MediaElement一放上去,黑屏、无声、报错、卡顿、路径不认、格式崩溃……我带过十几期WPF开发培…

作者头像 李华
网站建设 2026/9/26 19:12:29

顾客抱怨处理手册:从纸面文档到服务执行契约

简介:本资源是业之峰公司面向加盟商及总部服务人员编制的《顾客抱怨处理手册》,聚焦营销服务场景中的客户投诉应对,解决特许经营体系内服务标准不一、响应滞后、处置失当等现实问题。手册以标准化作业流程为核心,覆盖抱怨接收、记…

作者头像 李华
网站建设 2026/9/26 19:11:51

Atlas 300V 24G上部署YOLO全攻略:从环境配置到推理调优

1. 先搞清楚 Atlas 300V 24G 到底是个什么卡先说结论:Atlas 300V 24G 不叫“运算加速卡”还能叫什么?它就是一款不折不扣的 AI 推理加速卡。很多人一听到“Atlas”第一反应是地图软件,但在 AI 圈子里,Atlas 是华为昇腾计算产品线的…

作者头像 李华
网站建设 2026/9/26 19:10:53

安卓职工考勤APP开发实战:定位、围栏与防作弊实现

简介:这是一份基于Android Studio开发的职工考勤APP完整项目源码包,面向具备一定Android基础、希望将综合技能落地为真实考勤场景的移动开发学习者。项目覆盖员工信息管理、上下班考勤录入、异常记录、出勤统计、通知提醒与权限分级等核心模块&#xff0…

作者头像 李华
网站建设 2026/9/26 19:09:44

MCP 协议实战:用 TaoToken 统一 Key 打通 Cline 与 CC Switch 配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/26 19:06:17

Django高校后勤报修系统:从数据模型到部署详解

1. 高校后勤报修系统到底在修什么:需求拆解与定位做这个项目的起因很现实。之前帮一所高校的信息中心做过一套后勤报修系统,当时学生报修还停留在"打电话给宿管、在楼下登记本上写名字"的阶段,维修进度全靠人工催,后勤处…

作者头像 李华