芯片制造企业在建设PLM、QMS、ECN这些流程系统的时候,几乎都会遇到同一个抱怨:工程师辛辛苦苦调好的CAD图纸,一到TinyMCE编辑器里就变成一张静止的位图。放大看线条全是锯齿,想选中某个引脚看间距压根不可能,审批人发回的批注只能按图片区域圈个大概。所谓的CAD图纸粘贴到TinyMCE的矢量输出,本质上不是“贴图技巧”,而是把CAD的矢量数据链路完整打通到Web编辑器里。
这事听着小,放在芯片制造环境里却是要命的。封装图、引线框架图、贴装示意图里的信息密度极高,一位工艺工程师写的SOP如果贴上去是一张糊图,下面产线的人照着操作,角度差半度就是一批报废。我在这篇文章里要讲的,是一套在封装厂数字化转型项目里实际落地过的完整思路:从剪贴板原理讲到后台转换中心,再从TinyMCE插件代码讲到芯片场景特有的图层、单位、图框适配。适合正在做QMS/PLM/OA系统集成的工程师,也适合被文控需求反复磨的工艺标准化同事参考。
1. 芯片厂的图纸文档化,卡在最后一步粘贴
1.1 工艺文控的真正需求:不是贴图,而是让图纸进系统
芯片制造企业的文档系统里,图纸不是给人“看一眼”的装饰品。在SOP(标准作业程序)里,封装外形图用来指导产线员工识别引脚方向;在ECN/ECR变更单里,改版后的引线框架图要让所有审批人确认改动位置;在8D报告或NCR异常单里,不良品照片旁边往往要附上对应的设计图纸,才能说清楚“哪个引脚、哪个间距、哪条线偏了”。
我在项目里统计过,一个中等规模的封装测试厂,每天流经文档系统的ECN大概在20到50份,每份变更单至少挂1到3张CAD图纸。如果只是贴成位图,问题会集中在三个地方。
首先是可测量性没了。SOP里写“引脚间距0.65mm”,审阅人想用系统里的图量一下确认,位图做不到。位图的单位是像素,不同屏幕的dpi还不同,想量也量不准。
其次是可追溯性丢了。位图上的每一根线、每一个标注都变成了同一个像素层,图纸编号、版本号、图层名这些信息全部消失。文控想从旧文档里检索“某张图纸被哪些ECN引用过”,只能靠人工看图片,完全不可能自动化。
最后是批注精度不够。审批人看到的是一张整图,想在某个引脚旁边加一条意见,只能用框选把整张图圈出来。等到输出修改单时,生产和质量部门来回扯皮:“你批注的是哪个位置?”。
所以文控系统的真实需求,不是“把图纸变成图片放进页面”,而是“让CAD图纸作为矢量对象进入文档生命周期”。矢量意味着元素可选中、可标注、可按坐标测量,附加的元数据(图号、版本、单位)能跟后台流程打通。TinyMCE在这里只是载体,真正要打通的是CAD数据格式与Web编辑器之间的鸿沟。
1.2 为什么Ctrl+V拿不到矢量:剪贴板链路的真相
很多人第一反应是写个TinyMCE粘贴插件,在粘贴时把剪贴板里的数据拦下来处理。但真正动手后才发现,Web网页对剪贴板的访问有严格限制,浏览器不会把剪贴板里的所有东西都交给JavaScript。
Windows里CAD软件复制图纸时,通常会按多种格式写入剪贴板:OLE对象、EMF/WMF增强图元文件、位图(DIB)、以及一些软件自定义的私有格式。其中OLE对象和EMF/WMF才是真正的矢量载体,但浏览器出于安全考虑,基本不会把这些格式暴露给网页脚本。JS能稳定拿到的只有位图数据和文本内容。
于是TinyMCE的默认行为就是:收到一个位图,然后在光标处插入一张PNG或JPEG。从矢量到位图这一步,损失是彻底的。分辨率被锁定在截屏时的采样值,通常只有96到150dpi;CAD里的颜色模型被压扁成RGB;图层信息、块(Block)引用、线型、文字样式全部丢失。
还有一个非常典型的坑:芯片设计软件和很多CAD插件默认是深色绘图区、白色线条,复制出来的位图如果是透明背景,插到白底文档里就是一行若隐若现的白色虚线。工程师在CAD界面里看着好好的,到了系统里什么都看不清。
所以,想从浏览器端直接“截胡”CAD复制出来的矢量数据,这条路基本走不通。正确做法是绕开浏览器对剪贴板的限制,让CAD图纸以原始文件或中间矢量格式进入服务器,由服务器做转换,再把SVG交还给TinyMCE。这才是整个方案的真正起点。
2. 两条真正能落地的矢量输出路线
2.1 路线A:CAD端导出SVG,编辑器端透明注入
先讲最朴素也最容易实现的一条路线:不在浏览器端做任何转化,而是在CAD软件端就把图纸导出为SVG,然后由工程师或后台脚本把SVG内容注入到TinyMCE里。
具体操作分三步。第一步,在AutoCAD、中望CAD或国产CAD平台里,按照输出规范设置好图层可见性、颜色映射和视口范围。第二步,使用打印或导出功能,把当前视口输出为SVG或PDF。有些CAD版本原生支持导出SVG,不支持的可以先用虚拟打印机输出PDF,再用Inkscape命令行把PDF转成SVG,命令很直接:
inkscape --without-gui --file=drawing.pdf --export-plain-svg=drawing.svg第三步,在TinyMCE里通过按钮或自定义插件,读取这个SVG文件的内容,调用editor.insertContent()把它塞进编辑区。
这条路适合图纸量不大、IT投入有限的团队。它最大的优点是没有额外服务器,不需要维护转换服务,部署成本很低。只要CAD端做好统一模板,导出的SVG质量是稳定可控的。
缺点也很明显:自动化程度低。工程师需要手动执行导出,漏导、错导版本的概率很高。而且每个工程师的CAD环境设置可能不同,有人开了打印样式表,有人没开,转出来的SVG线宽、颜色五花八门。更麻烦的是,如果图纸在CAD里做了块嵌套,导出的SVG可能包含大量重复路径,文件体积会迅速膨胀。
所以我把路线A定位为“兜底方案”。适合部门小、图纸量少、暂时没有专职开发资源的情况。一旦图纸量上来了,后面这堆手工操作会变成文控部门的噩梦。
2.2 路线B:剪贴板EMF/WMF与源文件上传的服务器转换
真正贴近“粘贴即矢量”体验的,是路线B:在服务器上部署一个图纸转换中心,TinyMCE侧写一个自定义插件。用户点一下工具栏的“CAD导入”按钮,选择DWG/DXF/EMF文件,插件把文件上传到内网转换服务,服务端解析并转换成SVG,返回给前端插入编辑器。
为什么EMF在这里很关键?因为Windows下几乎所有专业CAD软件都支持把图纸复制为EMF增强图元文件。EMF保留了矢量信息,没有栅格化。问题是浏览器不能直接读剪贴板里的EMF,所以要么让用户手动选择文件,要么在装有CAD的Windows工作站上放一个轻量托盘工具,监听剪贴板,检测到CAD复制动作时自动把原始文件推送到转换服务。
我实际做的时候没有强求完全自动,因为芯片厂的客户端环境很杂,有老旧的Windows 7工控机,也有Linux工作站。给所有工作站装托盘工具推广成本太高。折中方案是:TinyMCE插件支持拖拽DWG/DXF文件到编辑区自动上传,也支持从剪贴板粘贴位图后弹窗询问“是否尝试转换为矢量”。这样既保留了操作效率,又不会强制改变用户习惯。
服务端转换工具链我是这样搭配的:DWG先用ODA File Converter批量转成DXF;DXF用ezdxf这个Python库解析和渲染成SVG;如果是EMF,则用Inkscape的无头模式直接转SVG。后面会详细展开这部分。
2.3 芯片厂环境下我为什么选路线B为主、A兜底
芯片制造企业跟普通互联网公司有一个根本性区别:网络环境封闭,数据保密要求极高。很多封装厂的办公内网跟生产网是隔离的,文档系统不能调用任何外部云API,图纸文件禁止明文落到业务服务器之外的第三方平台。
在这个前提下,路线B的架构优势就体现出来了。转换服务全部部署在内网,DWG/DXF文件从上传到删除都在自己的服务器上完成;SVG是转换产物,不含原始DWG里的敏感属性数据,可以安全存库。路线A虽然也安全,但标准化执行依赖人的自觉,在几十人规模的工程部门里很难推行。
两条路线的对比,我用一个表格总结过:
| 对比项 | 路线A(CAD端导出SVG) | 路线B(服务器转换中心) |
|---|---|---|
| 自动化程度 | 低,依赖人工导出 | 高,上传即转换 |
| 保真度 | 高,取决于CAD端设置 | 高,取决于转换配置 |
| 实施成本 | 低,无需额外服务 | 中高,需维护转换服务 |
| 数据安全 | 图纸留在内网,安全 | 需严格控制临时文件生命周期 |
| 扩展能力 | 弱,难挂接元数据 | 强,可做图层过滤、单位归一化、图号提取 |
| 适合场景 | 图纸量少、团队小 | 图纸量大、流程系统集成深 |
如果你所在的团队一天只有三张图要处理,我建议直接用路线A,别折腾服务端。等业务量上了量级,再按路线B的思路搭转换中心,你会发现前期的导出模板也并没有白做,可以复用到转换配置里作为缺省规则。
3. 搭建图纸转换中心:从DWG/EMF到SVG的处理栈
3.1 整体数据流:从“粘贴”动作到TinyMCE里的SVG节点
我先描述一下完整的数据流,让大家对这套系统有个全局概念。
用户在TinyMCE里点击“CAD导入”按钮,或者把DWG文件拖进编辑区。插件拿到文件后,以multipart/form-data形式POST到内网转换服务的/api/cad/convert接口。服务端先做权限校验和文件类型检查,然后把文件写入临时目录,调用转换器处理。转换完成后,SVG内容经过后处理(去脚本、压缩路径、统一单位),最终以JSON格式返回给前端:
{ "code": 0, "svg": "<svg xmlns='...'>...</svg>", "meta": { "docNo": "PKG-12345", "rev": "B", "units": "mm", "layers": ["DIE", "WIRE", "PIN"] } }插件拿到svg字段后,用editor.insertContent()插入到一个结构化的容器里,比如:
<div class="cad-figure" contenteditable="false">services: cad-converter: build: . ports: - "8080:80" environment: - MAX_UPLOAD_SIZE=50m - CONVERT_TIMEOUT=90 - STORAGE_BUCKET=cad-svg-cache mem_limit: 2g cpus: 2 security_opt: - no-new-privileges:true有两个容易被忽略的细节。第一,转换进程非常吃CPU,内存限制一定设死,不然一次恶意大文件上传就能拖垮整个容器。第二,容器内运行Inkscape时,最好以非root用户执行,并且给根文件系统设置只读。虽然麻烦一点,但DWG解析漏洞在业内并不罕见,制造企业又特别在意数据安全,这个投入值得。
3.3 图纸安全、保密与版本缓存
芯片厂的图纸保密等级通常很高,文控系统上线时安全审查是绕不开的。
我的做法是三条硬规矩。第一,转换服务只允许内网访问,禁止绑定公网入口,Nginx层按来源IP白名单放行。第二,源文件在转换完成后立即从临时目录删除,SVG结果长期缓存,但DWG/DXF坚决不能进入文档系统数据库。第三,所有上传操作必须有用户Token,日志只记录文件MD5和文件大小,不记录文件路径全名。
版本缓存这个点特别值得一说。同一张图纸,多个ECN文档都引用它,如果每次都重新解析DWG再转SVG,既浪费CPU又容易因为转换器升级导致同一张图在不同时期输出不同的样子。我用文件内容的MD5作为缓存键,把转换配置版本一起拼接进去:
cache_key = hashlib.md5((file_md5 + "_" + config_version).encode()).hexdigest()这样配置不变的情况下,同一张图纸只转换一次,后续所有文档直接复用缓存。上线一个月,转换服务的重复转换比例降到了15%以下,负载轻松很多。
4. TinyMCE端改造:把SVG当作一等公民接住
4.1 自定义插件:拦截粘贴、文件上传与SVG注入
TinyMCE的插件机制很成熟,关键是搞清楚要拦截哪几个事件。
首先是paste_data_images这个配置项。默认开启时,TinyMCE会把剪贴板里的图片直接转成base64插入,这是位图的来源。我们在初始化配置里关掉它:
tinymce.init({ selector: '#ecn-editor', plugins: 'paste link code image table autosave', external_plugins: { cadimport: '/static/tinymce/plugins/cadimport/plugin.min.js' }, paste_data_images: false, toolbar: 'undo redo | blocks | link image table | cadimport | code', content_style: ` svg.cad-vector { max-width: 100%; height: auto; border: 1px solid #ddd; } .cad-figure { position: relative; margin: 12px 0; } .cad-caption { font-size: 12px; color: #666; text-align: center; } ` });插件里增加一个“CAD导入”按钮,核心逻辑是选择文件、上传、拿到SVG后插入:
editor.ui.registry.addButton('cadimport', { text: 'CAD导入', icon: 'upload', onAction: () => { const input = document.createElement('input'); input.type = 'file'; input.accept = '.dwg,.dxf,.emf,.wmf,.pdf'; input.onchange = async (e) => { const file = e.target.files[0]; const token = localStorage.getItem('plm_token'); const form = new FormData(); form.append('file', file); form.append('doc_id', currentDocId); const resp = await fetch('/api/cad/convert', { method: 'POST', headers: { 'Authorization': 'Bearer ' + token }, body: form }); const data = await resp.json(); if (data.code === 0) { const figure = buildCadFigure(data.svg, data.meta); editor.insertContent(figure); } else { editor.notificationManager.open({ type: 'error', text: data.message || '图纸转换失败' }); } }; input.click(); } });这里有个很重要的细节:插入的div.cad-figure要设置contenteditable="false",让整个图纸区域成为一个不可拆分的整体。否则用户不小心点到内部的SVG路径,光标就会跑进SVG文本节点里,文档结构会被改坏。
4.2 让SVG在编辑器里自适应缩放与打印不糊
SVG插进去了,但不代表显示效果就对了。我在实际使用中遇到过三种情况要处理。
第一种是尺寸适配。DWG里的图幅可能是A0,而编辑器内容区只有800像素宽。SVG必须设定viewBox而不是固定width/height。viewBox确定了用户坐标和视口坐标的映射关系,配合CSS的max-width: 100%; height: auto;,图片会等比缩放,任何分辨率下都不失真。
第二种是线宽处理。默认情况下,SVG路径缩放后线宽也会跟着缩放。图纸整体缩小到15%显示时,原本0.3mm的细线几乎看不见。解决办法是给路径加一个CSS属性:
svg.cad-vector path, svg.cad-vector line, svg.cad-vector polyline { vector-effect: non-scaling-stroke; }vector-effect: non-scaling-stroke的意思是路径坐标随SVG缩放,但描边宽度保持像素值不变。这样缩小时线条依然清晰,放大时线条也不会变得过分粗。这个属性在Chrome、Edge、Firefox里都支持,可以放心用。
第三种是打印导出。审批人经常会把文档导出成PDF再签章,这时候要保证SVG在打印介质上也是矢量输出。CSS里加上:
@media print { svg.cad-vector { width: 100% !important; height: auto !important; } }打印时浏览器会把SVG作为矢量图形输出到PDF,文字层也保持可选中。这一步对芯片厂尤其重要,因为很多体系审核要求文档具备全文检索能力,位图PDF是过不了这条的。
4.3 图纸的再加工:批注、测量与图元关联
SVG以DOM元素存在,意味着我们可以做很多位图做不到的事。我把它分成了三层。
第一层是批注。在div.cad-figure上加一个position: relative,内部再放一个绝对定位的透明标注层。审批人点击图纸任意位置,前端在点击坐标上放一个小红点,旁边弹输入框让审批人填写意见。位置数据用SVG用户坐标存储,不直接改SVG内容,这样批注和图纸本身是分离的,后续图纸换版时批注还能做对比。
第二层是测量。我们做了一个“测距模式”,启用后点击图纸上的两个点,前端通过SVG的getScreenCTM().inverse()把屏幕坐标换算成SVG用户坐标,再用根节点上的>const svg = document.querySelector('svg.cad-vector'); const pt = svg.createSVGPoint(); pt.x = e.clientX; pt.y = e.clientY; const loc = pt.matrixTransform(svg.getScreenCTM().inverse()); // 与另一个点计算距离 const distance = Math.sqrt((loc.x - prev.x) ** 2 + (loc.y - prev.y) ** 2);
这个功能上线后,质量部门的人反馈最好用。以前他们核对外形尺寸要单独开CAD软件,现在直接在ECN单里点两下就量出来了。
第三层是图元关联。转换服务在SVG的每个路径上写了>{ "include": ["DIE", "BOND_WIRE", "PIN", "DIMENSION", "DATUM", "PACKAGE_OUTLINE"], "exclude": ["HATCH", "AOI", "RASTER", "DO_NOT_PLOT"], "strokeByLayer": true, "monochrome": true }
转换后每个路径保留>insunits = doc.header.get('$INSUNITS', 4) # 默认按mm处理 units_to_mm = { 1: 25.4, # inch 4: 1.0, # mm 5: 10.0, # cm 8: 1000.0 # micrometer } scale = units_to_mm.get(insunits, 1.0)
还有一个隐蔽的坑是块引用缩放。DXF里一个块被插入到模型空间时,可能带了2倍或5倍的缩放系数。如果只读实体坐标,不乘以块的插入矩阵,转出来的图形位置会完全错位。我用ezdxf的doc.query('INSERT')逐个解析块的insert属性,累积变换矩阵后再计算路径坐标。
最终在SVG根节点上,我会写入两个自定义属性:
<svg xmlns="http://www.w3.org/2000/svg" >for entity in msp.query('TEXT MTEXT'): x, y = entity.dxf.insert.x, entity.dxf.insert.y if title_block_box.contains(x, y): lines.append({'x': x, 'y': y, 'text': entity.dxf.text}) # 按行聚类,再按模板正则提取 for row in sorted_rows(lines): m = re.match(r'(PART_NO|图纸编号)\s*[::]\s*(\S+)', row.text) if m: meta['docNo'] = m.group(2)提取到的元数据会随SVG一起返回,插件把它写入数据属性。文控系统在保存文档时读取这些属性,自动关联到PLM里的对应对象。以前文控同事每天要手工核对图号和ECN号,现在系统自动带出了,只需要点确认。
这个功能投入不大,但对流程效率的提升非常明显,特别是对于那些需要频繁做版本对比的ECN场景,图号一旦能自动关联,后续所有检索和追溯都有了解析入口。
6. 上线实测、性能优化与踩坑记录
6.1 大图纸直接把浏览器拖崩
第一次联调时,我拿了一张真实的键合图(Bond Diagram)做测试。DXF文件40MB,转换服务花了一分钟才输出SVG,等我把SVG字符串插进TinyMCE,页面直接白屏。
问题出在SVG的元素数量上。那一张图有超过30万个节点和路径,浏览器解析DOM时根本扛不住。后来我做了三层优化。
第一层是从源头瘦身。转换时把圆和圆弧的逼近段数从默认的32段降为16段,肉眼几乎看不出差别,但元素量直接减半。第二层是用SVGO做后处理,删除无用的元数据、合并相邻路径、压缩坐标精度:
npx svgo input.svg -o output.svg --precision=2第三层是区分“编辑器预览版”和“完整版”。编辑器内插入的是轻量SVG,只保留主体轮廓和标注,文件控制在2MB以内。用户如果想看完整图纸或做高精度测量,点击图面上的“查看完整图纸”按钮,打开一个新页面加载不带降采样的原始SVG。这个策略极大缓解了编辑器的渲染压力。
6.2 字体、线型、填充的兼容性处理
CAD图纸里大量使用SHX形文件定义的专用字体,这些字体在Web环境根本不存在。如果直接转SVG,文字会变成乱码或者显示成方框。
我的处理方案是把文字强制转换成路径。在ezdxf渲染阶段,对每个TEXT/MTEXT实体调用text_to_paths,把文字轮廓变成贝塞尔曲线。代价是SVG体积增大,但换来的是在任何电脑上都显示一致。为了不丢失检索能力,转换服务会把提取到的文字内容单独存在meta.texts字段里,文控系统可以拿它做全文索引。
线型也一样。DWG里的虚线、点划线依赖线型文件定义,转成SVG后经常变成连续实线。我的做法是在转换器里识别实体的线型属性,如果是虚线,就在SVG路径上添加stroke-dasharray,通过计算线型比例系数来模拟原线型节奏。虽然不是100%还原,但审批人足够看清是虚线还是实线了。
填充的兼容性我在图层白名单里提过,这里再补一句:大面积渐变填充一定不要直接保留为路径。我的实测经验是,把填充区域转成低分辨率PNG再以背景图形式嵌入SVG,体积能缩小90%以上,视觉上几乎没区别。
6.3 用一套验收清单守住“能上线”的底线
项目收尾时,我整理了一份验收清单,发给测试和文控部门对照执行。里面最关键的五项是这样的:
| 验收项 | 标准 | 实测备注 |
|---|---|---|
| 转换成功率 | 对50张真实图库抽样,成功率≥98% | 失败集中在超老版本DWG |
| P95转换耗时 | 单张图纸从上传到返回SVG≤15秒 | 异步任务可接受30秒 |
| 编辑器打开首屏 | 含SVG的文档在TinyMCE编辑态≤2秒 | 超过3秒就要检查是否误用完整SVG |
| 打印效果 | 导出PDF后放大200%无锯齿,文字可搜索 | 文字转path后可搜索靠meta辅助 |
| 失败回退 | 转换失败时自动贴位图并标记“需复核” | 不让用户任务中断 |
这套清单的价值在于,它把“能不能上线”变成了可量化的指标,而不是靠感觉。最后真正上线后,最让我意外的不是性能数据,而是使用习惯的变化。质量部门开始主动在ECN单里做图面标注,工艺部门提交的SOP图面规范了很多,因为SVG里那些图层信息让文控审图时能直接定位问题。
如果你也准备在芯片厂的系统里做类似的事,我的建议是先把图框、图层、单位这三件事的规范定下来,再谈转换工具。工具只是手段,数据规则才是这套方案能不能长期运转的地基。那些在CAD端坚持自己绘图规范的工程师,他们的图纸转到系统里永远比别人顺畅,这不是偶然。