简介:围绕 WinCC 画面图层隐藏与显示的项目案例包,面向已具备一定 WinCC 组态基础、希望掌握画面动态交互与 VBS 脚本控制的自动化工程师及学习者,可用于理解分层画面设计、按钮触发与状态切换等常见需求。内容聚焦图层可见性管理,借助 Layer.Visible 等脚本思路,把按钮点击、数据加载、错误提示等事件与图层显示状态关联,帮助读者掌握事件驱动逻辑,并体会分层组织画面、简化复杂逻辑和提升项目可维护性的做法。压缩包共 298 个文件、约 10.1MB,以 262 个 rpl 项目相关文件及 pdl 画面、sav、dum 等为主,辅以 xml、ini 配置文件和 mdf/ldf 数据库文件,并含 LayerVisible 相关脚本线索,便于对照案例梳理工程结构与脚本调用关系。已有 2288 人学习,可作为 WinCC 图层控制与事件驱动脚本的实操参考,也适合在既有项目中借鉴可见性切换思路进行画面优化。
1. 操作工在一屏四百个对象里找泵:WinCC 画面图层隐藏显示要解决的事
某水处理车间的 WinCC 项目案例里,一张总览画面上四百多个对象全堆在一起:罐体、管线、阀门、仪表读数、报警闪烁块、检修挂牌、调试注释。WinCC 画面图层的隐藏显示功能本来能把这些按层拆开看,图形编辑器里勾一下就能只留下某一层,可一进运行系统,设计期勾好的层可见性就固定死了,操作工还是得在一屏里找那台泵。
这套方案要解决的就是这件事:把「哪一层、给谁看、什么工况下看」从画面组态里抽出来,做成一张可配置的层表和一段脚本,用一个位掩码变量驱动整屏显隐。它适合正在做组态交付、需要按角色分视图的工程,也适合把「组态软件高级应用」当作毕设或课设题目的读者——这套结构能直接搬进你的方案章节里。
2. 图层能力的边界与四条替代路线:图形编辑器里的层开关为什么管不到运行系统
2.1 WinCC 的层是设计期工具,不是运行期开关
图形编辑器默认提供 32 个层(0 到 31),你把对象分配给某一层,编辑时可以选择「只显示某几层」,避免在同屏几百个对象里误选。这个可见性是编辑视图的属性,作用对象是设计人员,不是操作工。运行系统加载画面文件时,所有对象都在,层级只是挂在对象上的一个标签。
很多方案一开始就翻车在这里:以为给对象改个层号就能藏起来。VBS 里ScreenItem确实有Layer属性,也能写,但改了之后只是把这个对象挪到另一个层去归属,画面上纹丝不动。
' 这行看着合理,实际不产生任何隐藏效果:只是改了对象的归属层 HMIRuntime.Screens("Main").ScreenItems("L03_Pipe_01").Layer = 6真正在运行期起作用的是两个属性:Visible(是否绘制)和Enabled(是否响应操作)。Visible = False的对象不参与绘制、不接收鼠标事件;Enabled = False的对象还在画面上,看得见,但点不动。这个区别在「检修模式」里很关键——某台泵要留在图上让操作工知道位置,只是不允许启停,那就该动Enabled而不是Visible。再往上还有两个可用的容器手段:组(Group)的Visible会连带所有子对象一起生效;画面窗口(Picture Window)的「画面名称」和「显示」属性可以整块挂载或卸载一张子画面。
2.2 四条路线摆在一张表里比:遍历、编组、画面窗口、动态对话框
知道能改什么之后,剩下的是「怎么改代价最小」。实际项目里能走通的有四条路,差别不在能不能实现,而在对象数量上去之后的表现和维护成本。
| 路线 | 实现方式 | 切换耗时(400 对象量级) | 适用场景 | 后期维护 |
|---|---|---|---|---|
| A 对象遍历 | 脚本遍历画面对象逐个设Visible | 300~800 ms,有明显停顿 | 对象少、层数多、画面结构零散 | 改组态不用改脚本 |
| B 组 + Visible | 每层对象编成一个组,脚本只动几个组 | 50 ms 以内 | 中型画面、组态时就能按层规划 | 新增对象容易漏进组 |
| C 画面窗口叠加 | 每层一张子画面,挂在主画面的画面窗口上 | 50 ms 以内 | 层数固定、需要多人分工编辑 | 子画面可独立复用 |
| D 动态对话框 | 每个对象的Visible直接绑变量位 | 中等,取决于变量刷新周期 | 只有几十个对象的演示画面 | 改一层要动几十个对话框,最贵 |
选型上我的习惯是:主画面结构稳定、层与层之间坐标能提前对齐的,走路线 C;画面已经画完、不想返工重画的,走路线 B,让脚本去认前缀;只有二三十个对象的临时演示,路线 A 最省事。路线 D 我只在补丁场景用,比如客户临时要求「这块报警牌只在工程师登录时出现」,单点加一个动态对话框比改脚本快。
这里有个血泪经验:不少人在路线 A 上花了大力气做优化,结果发现真正拖慢刷新的不是脚本本身,而是Visible频繁切换引起的整屏重绘。与其抠脚本,不如把对象收敛成组或者子画面,从结构上减少被操作的对象数量。
2.3 Z 序和点击区域:隐藏了不等于让出位置
对象设成Visible = False之后,它在 Z 序里占的位置不会自动腾出来,画面窗口之间的叠放顺序(图形编辑器里置顶、置底那一组操作)决定了谁盖住谁。这一点在路线 C 里尤其要注意:三层子画面用的都是同一个画面窗口位置,切换时如果窗口大小和坐标不一致,操作工看到的就是「切一下就错位」。
另一个常被误解的点是点击。Visible = False的对象不接收鼠标事件,这点很多人以为会,实际不会。真正会吃掉点击的是那些「透明但可见」的对象——用来做背景分隔的矩形填充块、只有边框的框线、贴在设备上做热区的透明按钮。排查「点不到设备」的问题时,先按 Ctrl 逐个选中看有没有透明覆盖物,比翻脚本快得多。
2.4 层和状态是两个概念,别揉进一个变量
层是静态分类:设备层、仪表层、检修层、调试层。状态是动态量:当前是正常运行、停车检修还是培训演练。层掩码回答「哪些层可见」,工况回答「现在该用哪个掩码」。把两者揉成一个变量的后果是,每加一种工况就得回头改脚本里的 if 分支,改到第四五个工况时脚本基本没法读了。
分开存的好处是扩展成本低:新增工况只是往预设表里加一行掩码值,脚本一行不动。我在方案评审时经常问一句「加一个新工况要改几个地方」,答案超过两个就说明这层抽象没做对。
3. 层表、掩码与命名前缀:把「哪层给谁看」抽成配置
3.1 层定义表:六个字段把业务规则固化下来
层表是整个方案的地基,它把「业务上分几层、每层默认开不开、谁能看」写成数据,脚本只负责执行。小项目我一般直接写成脚本里的常量数组,稍大的项目放 SQL Server 用 ADO 读,字段结构是同一套。
CREATE TABLE hmi_layer_def ( layer_id TINYINT NOT NULL, -- 层号 0-31,与对象名前缀里的两位数字对应 layer_name NVARCHAR(32) NOT NULL, -- 层名称,配置界面和文档里用 bit_value INT NOT NULL, -- 该层在掩码中的权重,等于 2 的 layer_id 次方 default_on BIT NOT NULL, -- 上电或复位后默认是否可见 min_user_level TINYINT NOT NULL, -- 可见所需的最低权限等级 remark NVARCHAR(128) NULL -- 用途说明,给接手的人看 );bit_value看起来是冗余字段,但它让层表可以直接被人读——运维翻表时看到 8 就知道是第 3 层,不用心算。真正参与运算的只有layer_id和min_user_level。
填好的数据大概长这样:
| layer_id | layer_name | bit_value | default_on | min_user_level | remark |
|---|---|---|---|---|---|
| 0 | 基础设备 | 1 | 是 | 0 | 罐体、泵、管道骨架 |
| 1 | 仪表数值 | 2 | 是 | 0 | 温度、压力、流量读数 |
| 2 | 报警联锁 | 4 | 是 | 0 | 报警闪烁块、联锁标记 |
| 3 | 检修标注 | 8 | 否 | 2 | 挂牌、盲板、接地线 |
| 4 | 调试信息 | 16 | 否 | 3 | 变量地址、内部调试标签 |
| 5 | 辅助管线 | 32 | 否 | 0 | 备用线、跨线、旁路 |
min_user_level这一列是权限闸门。层掩码算出来之后还要跟当前用户等级做一次与运算,等级不够的层强制置 0,避免有人用调试账号登录后手动勾开检修标记去改现场。
3.2 位掩码:一个字节装下八层可见性
八个层用一个字节就够:层 0 占最低位,权重 1;层 1 权重 2;层 2 权重 4,依次翻倍。可见性判断就一句位与。
' 判断第 n 层当前是否应该显示:掩码对应位为 1 即可见 Function IsLayerOn(ByVal n) IsLayerOn = ((SmartTags("g_LayerMask") And (2 ^ n)) <> 0) End FunctionWinCC 内部变量g_LayerMask建「无符号 8 位」,范围 0 到 255,正好覆盖八层。超过八层就换 32 位无符号,或者在脚本里拆两个字节处理,但十六层以上的画面通常说明该拆画面了,不是靠掩码能救的。
常见的工况预设值可以直接写在层表旁边的说明文档里,也可以做成配置界面的下拉项:
| 工况 | 掩码值 | 说明 |
|---|---|---|
| 正常运行 | 7 | 层 0、1、2 可见 |
| 停车检修 | 15 | 加上层 3 检修标注 |
| 工程师调试 | 63 | 层 0 到 5 全开 |
| 培训演示 | 3 | 只留设备和仪表,关掉报警闪烁避免干扰 |
| 夜间巡检 | 11 | 设备 + 检修 + 报警,关掉数值减少视觉噪音 |
掩码值的计算不要手写十进制,容易错。判断第 0 层和第 1 层可见,写1 + 2 = 3,写清楚比写 3 更好维护。
3.3 对象命名前缀:让脚本能自动认领对象
路线 A 和路线 B 都依赖脚本按名字找对象,所以命名规范必须提前定死。我用的是L{两位层号}_{类型简写}_{序号},比如L00_TANK_01、L02_ALM_07、L03_TAG_12。层号固定占两位,脚本取第 2、3 个字符就能还原层号,不用查表。
' 从对象名还原层号,例如 L03_TAG_12 返回 3 Function LayerOf(ByVal objName) If Left(objName, 1) = "L" Then LayerOf = CInt(Mid(objName, 2, 2)) Else LayerOf = -1 ' 不符合规范的对象不参与显隐控制 End If End Function不要用对象的「注释」属性来存层信息。注释在图形编辑器里看得见,但运行期读取要走GetPropChar之类的接口,代价高,而且改注释的人往往不知道它承担着控制逻辑。名字是唯一一个改错了立刻会暴露的字段,让它承担这个责任最稳。
类型简写这一层主要是给人看的,脚本不解析,但它有个实际好处:接手的人翻对象列表时能一眼看出这是设备、文字、报警还是标注,排查「为什么这个对象没跟着隐藏」时省很多时间。
3.4 变量放在哪、谁写谁读
三个内部变量足够撑起整个机制:g_LayerMask(当前生效的层掩码)、g_UserLevel(当前登录用户的权限等级)、g_WorkMode(当前工况编号,用于界面回显)。前两个参与显隐计算,第三个只是给人看的。
g_LayerMask要设成掉电保持。不设的后果是,现场断电重启后所有层回到默认值,检修班组一上班发现昨天挂的牌全没了,只能重新勾一遍。保持属性在变量属性里勾选即可,也可以在主画面Open事件里按层表的default_on重新算一次,两条路都行,后者更可控——我一般两个都做,保持属性防意外,启动脚本兜底。
多客户端场景要提前想清楚。全局变量在服务器上是共享的,一个客户端把检修层关掉,另一个客户端跟着变。如果现场是「中控室统一视图」的模式,这正好是想要的效果;如果是几个岗位各看各的,就得把掩码放到客户端的本地脚本变量里,用HMIRuntime.Tags的本地作用域,或者干脆每个客户端登录时各算各的。这件事在方案评审阶段就要问清楚,事后改的成本不小。
4. 脚本实现:从一口气遍历到只动三个对象
4.1 VBS 批量遍历:带组递归的基础版本
路线 A 的核心就是遍历画面对象、按名字解析层号、按掩码设置可见性。难点不在逻辑,在于组对象:ScreenItems只遍历顶层,组里面的对象要递归进去,否则编了组的设备永远不受控。
' 递归处理一个容器内所有带层前缀的对象 Sub ApplyMaskToContainer(ByVal container, ByVal mask) Dim obj, lNo For Each obj In container.ScreenItems lNo = LayerOf(obj.ObjectName) ' 复用 3.3 的解析函数 If lNo >= 0 Then obj.Visible = ((mask And (2 ^ lNo)) <> 0) End If ' 组对象要继续往下走:试探它有没有子集合,有就递归 On Error Resume Next Dim n n = obj.ScreenItems.Count If Err.Number = 0 Then ApplyMaskToContainer obj, mask If lNo >= 0 Then obj.Visible = ((mask And (2 ^ lNo)) <> 0) End If Err.Clear On Error GoTo 0 Next End Sub ' 画面打开时或掩码变化时调用 Sub Main() ApplyMaskToContainer HMIRuntime.Screens("MainOverview"), SmartTags("g_LayerMask") End Sub几个细节值得说明。For Each比按下标取对象安全,不用纠结集合索引从 0 还是 1 开始。递归判断用On Error Resume Next试探ScreenItems.Count是否可读,而不是靠Type属性比对字符串——不同版本对组类型的字符串表示不完全一致,试探法更耐版本。组自身的Visible要在递归之后重设一次,因为子对象设Visible = True在某些情况下会把组的可见状态带回来,重设一遍省心。
这个版本在两百个对象以内切换基本感觉不到停顿,超过四百个就能看到明显的一次白屏,这就是路线 A 的天花板。
4.2 C 脚本版本:对象过千时把切换压进 100 ms
对象数量上去之后,VBS 的解释执行和逐个属性写入就顶不住了。换成 C 脚本,跳过遍历、直接对着预生成的名单批量写属性,速度能差一个数量级。
#include "apdefap.h" /* 名单由层表工具生成,两个数组一一对应,长度固定在 g_LayerObjCount */ extern char g_LayerObjName[][32]; extern unsigned char g_LayerObjBit[]; extern int g_LayerObjCount; void ApplyLayerMask(char* lpszPictureName, unsigned char mask) { int i; BOOL bWant; for (i = 0; i < g_LayerObjCount; i++) { bWant = (mask & g_LayerObjBit[i]) ? TRUE : FALSE; /* 先读后写:状态没变就不写,省掉一次无谓的重绘 */ if (GetPropBoolean(lpszPictureName, g_LayerObjName[i], "Visible") != bWant) { SetPropBoolean(lpszPictureName, g_LayerObjName[i], "Visible", bWant); } } }lpszPictureName是画面名,由触发脚本传进来;mask是当前掩码,从内部变量读。名单数组不用手写,写个小工具按层表扫一遍 PDL 文件生成头文件,改了组态重新生成一次,比手工维护靠谱。GetPropBoolean那层判断是性能关键——四百个对象里一次切换真正变状态的通常只有几十个,省下的属性写入直接反映在切换手感上。
C 脚本的代价是可读性,接手的人得能读 C。我的判断标准是:对象总数稳定超过六百,或者切换频率高到操作工一天要切几十次,才值得上 C;否则 VBS 加路线 B 的组合更省事。
4.3 画面窗口方案:工程上更稳的主线做法
如果画面还没开始画,或者愿意返工,我优先推路线 C。每层做成一张独立子画面(Layer0_Base.PDL、Layer3_Maint.PDL…),主画面上按位置摆一排画面窗口,窗口大小和坐标完全一致,叠在一起。切换时只动这几个窗口的属性,跟画面里有多少对象完全无关。
' 按掩码切换一组画面窗口的可见性:窗口名与层号绑定,比如 winLayer3 对应层 3 Sub SwitchLayerWindows(ByVal mask) Dim scr, i, win, want Set scr = HMIRuntime.Screens("MainOverview") For i = 0 To 5 ' 层数由层表决定,这里假设用了 6 层 Set win = scr.ScreenItems("winLayer" & i) want = ((mask And (2 ^ i)) <> 0) If win.Visible <> want Then win.Visible = want End If Next End Sub这个方案的好处不止快。子画面可以分给不同的人编辑,设备专业画设备层,仪表专业画仪表层,互不打架;改一层不用动主画面;层与层之间的对齐靠统一的画面尺寸和坐标原点保证,一次性对齐好,后面加内容不会错位。代价是层与层之间需要「跨层」的元素会比较别扭——比如一根管道从设备层出发、穿过检修层,那就得在每个相关子画面里各画一段,或者把这根管道单独提到一个「公共层」常显。这个取舍在方案设计阶段要跟业主确认,别等交付前才发现。
还有一点:画面窗口的Visible = False之后,窗口位置不会响应鼠标,也不会盖住下层,这对操作安全性是好事。但如果用的是「切换画面名称」而不是切换可见性,空画面窗口在某些版本下仍然是个透明覆盖物,会吃掉点击。我一般直接用Visible,不用切画面名。
4.4 触发链与权限联动:从按钮到整屏刷新只走三步
整个机制的执行链要短,长了就容易出现「按钮点了没反应,也不知道卡在哪」的黑匣子。
第一步,按钮或工况选择写入g_WorkMode;第二步,一段脚本根据工况查预设表算出掩码和权限闸门,写g_LayerMask;第三步,g_LayerMask配一个变量触发,值变化时执行显隐函数。三步各管一件事,出问题时看中间变量就知道断在哪。
权限闸门放在第二步里算,不要放到第三步的显示函数里,否则每次刷新都要重算一遍权限,而且调试时容易忘记当前是什么权限等级。
' 用户登录成功后调用:算权限,再算掩码 Sub OnUserLogin(ByVal userName) Dim lvl, baseMask lvl = QueryUserLevel(userName) ' 查用户表,返回 0-3 SmartTags("g_UserLevel") = lvl baseMask = SmartTags("g_LayerMask") ' 权限不足的层强制关掉:层 3 需要等级 2,层 4 需要等级 3 If lvl < 2 Then baseMask = baseMask And (255 - 8) If lvl < 3 Then baseMask = baseMask And (255 - 16) SmartTags("g_LayerMask") = baseMask ' 写回触发刷新 End Sub255 - 8这种写法比直接写 247 好读,意思是「把第 3 位清零」。写回g_LayerMask会触发第三段的变量触发脚本,整屏刷新一次,操作工看到的就是「换了个人登录,视图跟着变」。用户切换、退出登录都要走这条链,退出时按层表默认值复位,别留着上一个人的视图给下一个人。
5. 避坑与排查:图层显隐最容易翻车的五个点
5.1 改完 Visible 画面一刷新又弹回来
现象:脚本执行了,对象也确实隐藏了一瞬间,但画面下一次刷新时全部恢复原样,日志里看不出报错。
原因通常有三个。一是对象上还挂着动态对话框,Visible被变量直接驱动,脚本写进去的值下一周期就被覆盖;二是同一个属性有两个脚本在写,比如画面Open事件写一次、全局周期脚本又写一次;三是改的其实是组内对象,但组本身的Visible仍是True,看起来像没生效。
解决:先按 Ctrl 选中对象看属性面板里Visible右边有没有小三角(有就是挂了动态)。有的话把动态去掉,统一交给脚本。然后在全局脚本编辑器里搜一遍对象名,确认没有第二个写入点。组的情况直接把脚本改成先算子对象再设组,或者干脆只设组不设子对象。
5.2 隐藏的对象还能被点到
现象:检修层已经关掉了,操作工点原来挂牌的位置,居然弹出了挂牌详情画面。
原因不是Visible失效,而是上面盖着一个透明热区。做画面时经常在设备上贴一个「透明按钮」用于点击跳转,这个按钮如果没编进层组、名字也不带层前缀,脚本就管不到它,它就一直躺在那里吃点击。
解决:把所有透明热区纳入命名规范,跟着所属设备一起编层。排查时按 Ctrl 全选,把填充设为无、边框设为无的对象挨个看一遍,或者用对象的「Tab 顺序」列表逐个点,能点的都会亮。这件事在画面评审阶段做一遍,比交付后现场救火便宜得多。
5.3 切一次层画面卡两秒
现象:点工况切换按钮,画面明显停顿,操作工以为死机,连点几下之后状态彻底乱了。
原因基本是路线 A 在对象过量的画面上硬扛,每个Visible写入都触发一次局部重绘,四百个对象就是四百次。另外如果脚本挂在周期触发上(比如 1 秒一次),那就不只是切换时卡,平时也一直在偷偷跑。
解决:短期把触发方式从周期改成变量变化触发,只在掩码真变的时候跑;中期把对象收敛成组,脚本操作的对象数从四百降到六个;长期换路线 C,动的是画面窗口,跟对象数彻底解耦。另外切换期间可以把主画面的更新时间临时调大,切完再恢复,属于应急手段,别当常规方案用。
5.4 两个客户端互相把对方的层关掉了
现象:中控室的操作员关掉调试层,隔了两个工位的另一个客户端上调试层也跟着消失,两边互相以为对方在乱点。
原因就是g_LayerMask用了全局变量,服务器上一份,所有客户端读同一份。这不是 bug,是全局变量的语义,但如果业务上本来就是「各岗位各看各的」,那就是设计没对上需求。
解决:先确认业务模式。要统一视图就保留现状,但要在界面上加个提示,说明当前视图是全局同步的,避免误会。要各自独立就把掩码改成客户端本地变量,登录时各算各的,服务器只存用户等级这类不随视图变化的数据。改的成本主要在回归测试,所有跟掩码联动的按钮都要重测一遍。
5.5 中文对象名和大小写在脚本里匹配不上
现象:脚本里写的是L03_TAG_12,画面上对象名也是这个,但就是不生效,换成另一个对象又能用。
原因通常是复制粘贴时带了尾随空格,或者对象名里的下划线是全角字符,肉眼几乎看不出来。另一种情况是接手的人改了对象名,脚本里的字符串没跟着改,这种在 VBS 里不会报错,只是静默不生效。
解决:命名规范里明确规定只用半角字符、不加空格;脚本里做前缀匹配而不是全等匹配,Left(objName, 1) = "L"比objName = "L03_TAG_12"宽容得多;交付前写一段自检脚本,把所有带L前缀的对象名打印出来人工扫一遍,十秒的事,能省掉半天的现场排查。
6. 验收与答辩:用一张表证明显隐逻辑真的可靠
这套东西最怕的是「演示时好好的,交付后说不清」。验收不能只靠点几下看看,得有能写进文档、能复现的条目。我在项目里通常用下面这张表,每一项都对应一个具体操作和一个通过标准,验收时逐条打勾。
| 验收项 | 操作方法 | 通过标准 |
|---|---|---|
| 层切换正确性 | 依次切五种工况,逐层核对 | 每层可见性与层表default_on一致,无残留 |
| 切换响应时间 | 秒表计时,重复十次取平均 | 单次切换 ≤ 300 ms(路线 C 应 ≤ 100 ms) |
| 权限闸门 | 用三个不同等级账号分别登录 | 低等级账号看不到检修层和调试层 |
| 掉电恢复 | 切到检修工况后断电重启 | 重启后掩码与断电前一致 |
| 组内对象 | 单独切到某层,检查编组设备 | 组内对象与组外对象同步显隐 |
| 点击安全 | 关掉检修层后点原挂牌位置 | 无弹窗、无跳转、无操作响应 |
| 多客户端一致性 | 两个客户端同时登录,一个切层 | 按业务模式验证:同步或独立,符合设计说明 |
| 与报警联动 | 触发一条报警,切到培训工况 | 报警层按掩码规则显示,不误闪 |
答辩时最容易被追问的是「你为什么不用图层自带的隐藏」。这个问题答不好会显得方案是绕路走的,答好了反而是加分项。我的答法是先讲清楚图层是设计期属性、运行期改层号不改变可见性这个事实,再把手上的四条路线摆出来对比,说明为什么在对象数量、维护成本、多人协作这几个约束下选了当前这条。评委通常不介意你选了哪条,介意的是你知不知道别的路存在、知不知道为什么没走。
第二个高频问题是「加一个新工况要改哪里」。这个问题直指设计质量。答案是只往预设表加一行掩码值,脚本和画面都不动——能当场把表翻出来指给对方看,比讲十分钟架构都有说服力。
演示环节有个技巧:不要只演示「正常情况」。故意切到一个权限不足的账号,让对方看到检修层确实出不来;故意在切换后点一下原来对象的位置,让对方看到点不到。把边界情况主动演出来,比藏着掖着强,现场提问也多半围绕这些点,提前演过就不慌。
从那以后我每次做这类显隐方案,都会在动手画第一根管道之前先把层表和命名规范定死,哪怕只有六层也要写成文档让业主签字确认。返工改名字的成本,比前期多开一次评审会高太多。希望帮到你。
本文还有配套的精品资源,点击获取