模板成图
模板成图
以已有 imports 图纸的 DWG 为底图,新建设计图并克隆模板实体。导出 DWG 时后端打开模板 DWG,对 clone_ 实体做 ODA 克隆并只覆盖变化字段,因此模板原有的字体、线型、填充图案都能原样保留——即使前端因未加载字体而显示成回退字形。
适用场景
- 自动成图:剖面图、柱状图、表格等批量出图
- 需要与模板 DWG 中的真实字体 / 自定义线型 / 复杂填充图案完全一致
- 对标 vjmap 的
from+cloneObjectId/cloneFromDb语义
核心概念
| 概念 | 说明 |
|---|---|
| 模板 | 必须是 imports 类型,且服务端仍保留源 DWG(hasDwg === true) |
from | 文档顶层字段,格式 mapid/version,导出时据此打开模板 DWG |
isClearFromDb | true 表示导出时克隆完后清空模板原实体,只留自己画的内容 |
clone_<handle> | 同模板克隆标记 |
clone_<handle>_<mapid>_<ver> | 跨模板(跨库)克隆标记 |
在线示例
| 示例 | 描述 | 链接 |
|---|---|---|
| 从模板新建图纸 | openFromTemplate + designs 导出 | 在线演示{target="_blank"} |
| 克隆模板实体 | cloneEntity 基本用法 | 在线演示{target="_blank"} |
| 克隆模板文字 | 保留模板字体 | 在线演示{target="_blank"} |
| 克隆模板填充 | 只换边界,图案不变 | 在线演示{target="_blank"} |
| 测量文字宽高 | 本地 WASM 与服务端 ODA 两种结果对比 | 在线演示{target="_blank"} |
| 模板成图 | 剖面示意 + 跨模板克隆 | 在线演示{target="_blank"} |
| 数据自动生成剖面图 | 对标 vjmap 03datatodwgmap | 在线演示{target="_blank"} |
命令与 UI
| 命令 | 别名 | 说明 |
|---|---|---|
NEWFROMTEMPLATE | NFT | 对话框选择模板,整图引入或只保留样式 |
CLONEFROMTEMPLATE | CFT | 从模板挑选实体,交互指定插入点后克隆进当前图纸 |
await Engine.editor.executerWithOp('NEWFROMTEMPLATE');API:TemplateService
const { TemplateService } = vjcad;从模板打开图纸(界面编辑)
// keepEntities: false → 只保留图层/线型/字体/填充/块定义,对应 isClearFromDb: true
// keepEntities: true → 整图引入,模板实体一并载入
const doc = await TemplateService.openFromTemplate({
mapid: 'template_sect',
version: 'v1',
keepEntities: false,
name: '我的设计图'
});
console.log(doc.templateFrom); // "template_sect/v1"
console.log(doc.isClearFromDb); // true编程式创建文档(不上屏)
const doc = await TemplateService.createDocFromTemplate({
mapid: 'template_sect',
version: 'v1',
keepEntities: false
});
// 自行构造实体后
const json = doc.toDb(); // 顶层含 from / isClearFromDb加载模板并克隆实体
const template = await TemplateService.loadTemplate('template_sect', 'v1');
// 按 handle 克隆,props 中未列出的属性沿用模板
// 克隆出的实体尚未进图,需要自己 Engine.addEntities
const text = template.cloneEntity('96A0', {
insertionPoint: [100, 200]
});
text.text = '孔口名称'; // MTEXT 用 text.contents
Engine.addEntities(text);cloneEntity 返回的是与模板实体同类型的对象,props 里没写的字高、字体、线型、填充图案都保持模板原样。想平移克隆体时用 move(from, to) 比逐类型改几何属性更通用:
const cloned = template.cloneEntity('96A0');
cloned.move([0, 0], [50, 0]); // 整体右移 50批量克隆同一个 handle 时,模板实体只解析一次,导出阶段后端也只做一次跨库克隆并复用原型,适合花纹填充这类重复上百次的场景:
const hatches = template.cloneEntities([
{ handle: '96BB' },
{ handle: '96BB' },
{ handle: '96BB', props: { patternScale: 1.5 } }
]);
// 填充只换边界,图案与角度沿用模板
hatches.forEach((hatch, i) => hatch.setLoops(loopsList[i]));
Engine.addEntities(hatches);就地修改模板实体
keepEntities: true 时模板实体一并载入,objectId 保持原 handle(没有 clone_ 前缀)。这类实体导出时走的是原地修改而非克隆:
const doc = await TemplateService.openFromTemplate({
mapid: 'template_sect', version: 'v1', keepEntities: true
});
const entity = doc.findByObjectId('96A0');
entity.text = '改写模板中已有的文字';
entity.setModified();其他方法
| 方法 | 说明 |
|---|---|
buildDocDataFromTemplate(options) | 返回带 from 的 vcad JSON,不构造文档 |
openInView(doc) | 把已构造的 CadDocument 打开到界面 |
clearCache(mapid?, version?) | 清除模板缓存;省略参数清空全部,切换工作区后应调用 |
template.from | 模板引用,形如 "template_sect/v1" |
template.handles | 可克隆实体的 handle 列表 |
template.getEntity(handle) | 只读取得模板实体,不要直接修改 |
template.doc / template.rawJson | 模板文档对象 / 模板原始 vcad JSON |
template.buildInsertDoc(handles) | 构造供 SymbolInteractiveInserter 交互插入的迷你文档 |
怎么拿到 handle
实际项目里 handle 一般是事先约定好的(在平台里点选模板实体,读它的 objectId)。要在代码里找,可遍历 template.rawJson.dbBlocks['*Model'].items,这里的坐标是 [x, y] 数组,便于按位置、图层、类型筛选。
API:setCloneSource
编程式成图时若不用 TemplateHandle.cloneEntity,可手动标记克隆来源:
entity.setCloneSource(sourceHandle); // → clone_<handle>
entity.setCloneSource(sourceHandle, mapid, version); // → clone_<handle>_<mapid>_<version>导出时后端据此从源图做 ODA 克隆。
导出 DWG
设计图导出必须用 type: 'designs',JSON 顶层的 from 由 CadDocument.toDb() 自动写出:
const { DrawingManagerService } = vjcad;
const result = await new DrawingManagerService().exportToDwg({
type: 'designs',
webcadJson: JSON.stringify(Engine.currentDoc.toDb()),
designPath: `auto/${Date.now()}`,
branch: 'main',
isZoomExtents: true,
useCache: false
});后端流程概要:
- 按
from打开模板 DWG 作为工作库;打不开直接报错,不会静默产出空图 - 处理
clone_实体:同库deepClone或跨库wblockClone,克隆后只覆盖变化字段 - 没有
clone_前缀但objectId非空的实体,视为对模板实体的原地修改 - 若
isClearFromDb,此时才删除模板原实体(必须晚于克隆,否则克隆源已不存在) objectId为空的实体按普通新建流程创建
keepEntities: true(不清空模板实体)首次导出时,后端会拿模板自身的 base.vcad 作差异基线,因此 DWG 里只体现你的改动。keepEntities: false 则用空基线,全部实体都算新增。
选择可用模板
模板必须仍保留源 DWG。listWebcadDraws() 返回的 imports 项带 hasDwg 字段:
const drawingManager = new DrawingManagerService();
const [res, serverMaps] = await Promise.all([
drawingManager.listWebcadDraws(),
drawingManager.listServerMaps()
]);
const registered = new Set((serverMaps || []).map(m => m.mapid));
const usable = (res.imports || []).filter(d => {
if (d.hasDwg === false) return false;
if (d.hasDwg === true) return true;
return registered.has(d.mapid); // 兼容老服务端
});若服务端清理了原始 DWG,需重新 IMPORTDWG,或配置 noAutoDeleteDwgFile 保留源文件。
文字尺寸测量
自动成图常常要先知道一段文字排版后占多大,才能算出表格行高、块的位置。measureTexts 做这件事:
const { measureTexts } = vjcad;
const results = await measureTexts([
// 带克隆来源,走服务端:用模板 DWG 的真实字体排版
{ text: '粉质粘土:褐黄色,可塑', height: 2.5, width: 30, cloneSource: 'clone_96BA_template_sect_v1' },
// 没有克隆来源,走本地 WASM
{ text: '普通文字', height: 3, type: 'TEXT', styleName: 'Standard' }
]);
const [first] = results;
console.log(first.width, first.height, first.geomHeight, first.source);返回的每项包含:
| 字段 | 说明 |
|---|---|
width / height | 排版后的真实宽高。MTEXT 的 height 是逐行累加的逻辑高度 |
geomWidth / geomHeight | 内容包围盒尺寸。要让图元紧贴文字用这个 |
bounds | [minX, minY, maxX, maxY] |
source | 'server' 或 'local' |
isFallbackFont | 本地测量时字体被回退,结果仅供参考 |
测不了的位置返回 null,不会中断整批。
为什么克隆的文字要走服务端
前端只加载了 MainView 的 fonts 里配置的那几种字体,模板 DWG 用的字体(仿宋、宋体等)通常不在其中。resolveFontForName 会静默回退到默认字体,中文字宽虽接近,但西文与标点的差异会改变换行位置,多行高度就跟着偏。服务端用 ODA 在真实 DWG 上排版(OdDbMText::actualWidth() / actualHeight()),结果与导出的 DWG 完全一致。
measureTexts 默认按来源分流(mode: 'auto'):带 cloneSource 或指定了 templateMapId 的走服务端,其余走本地。也可以用 mode: 'local' / 'server' 强制走某一侧,示例 测量文字宽高{target="_blank"} 就是同一批数据两边各测一次来看差值。
服务端那部分按模板分组,每组合并成一次请求,测几十上百条也只有一两次往返。
单独使用某一侧
const { measureTextLocal, measureTextsOnServer } = vjcad;
// 同步,不入图不等帧
const local = measureTextLocal({ text: 'ABC', height: 3, styleName: 'Standard' });
// 指定模板批量测
const server = await measureTextsOnServer('template_sect', 'v1', [
{ handle: '96BA', text: '粉质粘土', height: 2.5, width: 30 }
]);服务端测量需要后台支持 measureText 命令。
更完整的 API、字段说明与 mode 分流见 文字宽高计算。
与 vjmap 对照
| vjmap | WebCAD |
|---|---|
doc.from = 'mapid/ver' | TemplateService.openFromTemplate → doc.templateFrom |
isClearFromDb: true | keepEntities: false → doc.isClearFromDb |
cloneObjectId: '96A0' | template.cloneEntity('96A0', props) |
cloneObjectId + cloneFromDb | 跨模板时自动写成 clone_<h>_<mapid>_<ver> |
注意事项
- 模板固定读
patchId: 'base',保证前端显示与导出所用 DWG 一致 - 不要改克隆实体的
styleName/patternName等本应继承的字段,否则导出也会被覆盖 - 克隆出的实体还没进图,需要自己
Engine.addEntities - CUSTOM / GROUP 等类型克隆可能退化为普通创建
- 跨库同名块默认忽略冲突(
kDrcIgnore)
常见问题
导出报 Failed to open template xxx/v1
服务端已清理该图的源 DWG,或 mapinfos 里没登记这个版本。重新 IMPORTDWG,或配置 noAutoDeleteDwgFile。
导出后字体 / 填充图案变了
检查是否在 props 里显式写了 styleName、patternName、patternScale 等字段。写了就会覆盖模板;想继承就别传。
克隆实体在画布上显示正常,但选不中
早期版本克隆实体的 id 为空导致进不了空间索引,升级 SDK 即可。
跨模板克隆没生效cloneEntity 会自动判断:目标文档的 templateFrom 与模板一致时写 clone_<handle>,否则写 clone_<handle>_<mapid>_<version>。若手工调 setCloneSource,跨模板时必须传 mapid。