500行JavaScript代码在前端根据数据生成CAD工程剖面图
前言用数据生成CAD图,一般采用的ObjectArx对CAD二次开发完成。ObjectARX是AutoDesk公司针对AutoCAD平台上的二次开发而推出的一个开发软件包,它提供了以C++为基础的面向对象的开发环境及应用程序接口,能访问和创建AutoCAD图形数据库。而由于现在懂C++的人少,很多人对C++有点望而生畏。则JavaScript 是互联网上最流行的脚本语言,用户群体很大。那有没有可能利用JavaScript来进行数据成图?
今天和大家聊聊,怎么用500行JavaScript代码,根据数据在前端创建一个Dwg格式的工程剖面图。
效果
先上效果图
它支持哪些功能?
[*]支持CAD的27种实体类型的创建,如线、文字、填充等
[*]支持对DWG图中实体进行修改、克隆、删除等操作
[*]支持创建CAD图层、线型、块定义、文字样式
[*]支持从外部图形中拷贝实体到当前创建的CAD图中
[*]支持块属性文字的创建和设置
[*]对创建好的CAD图形数据能以GeoJson的格式在前端直接展示,同时能选中移动等操作
[*]对创建好的CAD图形能在前端展示,同时能点击弹出实体类型等属性
[*]能导出成DWG图形
实现原理
(1) 对剖面图中不变的元素如图例做成模板。创建图时直接拷贝这些实体即可。对于图签可以外部图形插入,同时图签中需要修改的文字内容如制图人或日期等字段,可以块属性文字的方式在创建时以属性赋值的方式来进行创建。如上面生成的剖面图的模板来源于下面这两个模板图形。 剖面图模板:
图签模板:(其中单位和日期是块属性文字,支持插入的时候输入属性值进行修改)
(2) 获取要创建的绘图数据,示例中对数据进行了模拟生成。
(3) 根据唯杰地图https://vjmap.com/ SDK中提供创建CAD实体类型的方法创建相关实体。唯杰地图SDK支持的实体类型有DbLine直线、DbCurve曲线、Db2dPolyline二维折线、Db3dPolyline三维多段线、DbPolyline多段线、BlockReference块参照、DbArc圆弧、DbCircle圆、DbEllipse椭圆、DbHatch填充、Text单行文本、DbMText多行文本、RasterImage栅格图片、DbShape型实体、Spline样条曲线、Wipeout遮罩实体、Dimension标注、Db2LineAngularDimension角度标注[两条线]、Db3PointAngularDimension角度标注[三点]、DbAlignedDimension对齐标注、DbArcDimension圆弧标注、DbDiametricDimension直径标注、DbOrdinateDimension坐标标注、DbRadialDimension半径标注、DbRadialDimensionLarge半径折线标注、DbRotatedDimension转角标注、AcDbAttributeDefinition属性注记、AcDbAttribute块属性、DbLayer图层、DbTextStyle文字样式、DbDimStyle标注样式、DbLinetypeStyle线型样式、DbBlock块定义、DbDocument数据库文档。
如何减少代码量可以用如下方法:
[*]技巧一:可以直接拷贝模板中的实体,对实体的属性进行修改。这样能少赋值参数,减少代码量。
[*]技巧二:对于重复的对象,可以创建块,变化的文字,以块属性文字定义。再重复创建块参照,修改属性文字。
(4) 把创建的数据生成一个JSON对象,调用唯杰地图服务,后台创建DWG图形。
(5) 把后台创建的DWG图形数据以GeoJson数据或GIS瓦片的格式返回给前端进行展示。对于图不大的情况,可用GeoJson数据进行展示。如果图大时,GeoJson数据量大,数据返回慢,渲染也会受影响,这时建议用GIS栅格瓦片或矢量瓦片的时候进行绘制。
在线体验地址
https://vjmap.com/demo/#/demo/map/comprehensive/03datatodwgmap
应用场景
能在前端通过JavaScript创建CAD格式的DWG图形,极大的降低了数据生成CAD图的门槛,具有很广泛的应用场景。例如,在建筑和工程领域,DWG文件是广泛使用的标准文件格式,如工程中常用的一些等值线图、剖面图、水位图等;建筑、交通等不同行业中的相关图纸都可以用这个来生成DWG图形。偷个懒,让目前很火的ChatGPT来总结下吧:
全部实现代码
// --数据自动生成CAD工程剖面图--根据数据在前端创建生成CAD格式的工程剖面图形
// 剖面图模板来源地图id和版本
let templateSectId = "template_sect";
let templateSecVersion = "v1";
// 图框模板来源id和版本
const templateTkMapId = "template_tk";
const templateTkVersion = "v1";
// 注:以下所的有objectid来源方法为:
// 在唯杰云端管理平台 https://vjmap.com/app/cloud 里面以内存方式打开模板图,然后点击相应实体,在属性面板中获取object值
// 或者以几何渲染方式打开模板图,点击相应实体,在属性面板中获取object值,如果是块实体(objectid中有多个_),取第一个_前面的字符串
let svc = new vjmap.Service(env.serviceUrl, env.accessToken);
// 获取模板信息
let tplInfo;
// 获取模板中的信息
const getTemplateInfo = async (templateSectId, version) => {
let features = await getTemplateData(templateSectId, version);
// 获取所有填充符号。先获取 填充符号 图层中的所有文字,文字上面的hatch就是填充符号
let hatchInfos = features.filter(f => f.layername == "填充符号" && f.name == "AcDbMText").map(t => {
let hatch = features.filter(f => f.layername == "填充符号" && f.name == "AcDbHatch").find(h =>
// 填充垂直方向位于文字上方,并且距离不能超过文字高度两倍,水平方向包含文字中心点水平方向
h.envelop.min.y > t.envelop.max.y &&
h.envelop.min.y - t.envelop.max.y < t.envelop.height() * 2 &&
h.envelop.min.x <= t.envelop.center().x &&
h.envelop.max.x >= t.envelop.center().x
)
if (!hatch) return;
return {
name: t.text,
hatchObjectId: hatch.objectid
}
})
// 获取绘制开始的位置线
let lineInfo = features.filter(f => f.layername == "线" && f.name == "AcDbLine");
let startLine;
if (lineInfo.length > 0) {
startLine = {
objectId: lineInfo.objectid,
positon: .envelop.min.x, lineInfo.envelop.min.y]
}
}
return {
startLine,
hatchInfos
}
}<br>
// 模拟数据
const mockData = (hatchNames, minCount) => {
// 对填充符号次序先随机排序下,这样每次生成次序就不一样了
hatchNames.sort(() => Math.random() - 0.5);
let data = [];
// 孔口个数
let kongCount = vjmap.randInt(minCount, minCount * 2);
for(let i = 0; i < kongCount; i++) {
let item = {
name: '孔' + (i + 1),
x: 15 * (i + 1) + vjmap.randInt(0, 10) + 1000, // 孔口坐标x 生成随机数x
y: vjmap.randInt(100, 105), // 孔口坐标y 生成随机数y
stratums: [] // 分层数据
}
// 生成每层的信息
let stratumCount = vjmap.randInt(5, hatchNames.length - 1);
let stratumAllThickness = 0;
for(let k = 0; k < stratumCount; k++) {
const thickness = vjmap.randInt(2, 6) // 随机生成一个厚度
item.stratums.push({
hatch: hatchNames,
thickness: thickness
})
stratumAllThickness += thickness;
}
item.stratumsThickness = stratumAllThickness; // 所有的厚度
data.push(item);
}
return data;
}
// 创建剖面图
const createSectDoc = async (sectData) => {
// 获取要绘制的数据
let drawData = sectData;
// 获取最大和最小值
let minX = Math.min(...drawData.map(d => d.x));
let maxX = Math.max(...drawData.map(d => d.x));
let minY = Math.min(...drawData.map(d => d.y));
let maxY = Math.max(...drawData.map(d => d.y + d.stratumsThickness));
minY = Math.floor(minY / 10) * 10; // 往10取整,刻度以10为单位
maxY = Math.ceil(maxY / 10) * 10 + 10; // 往10取整,刻度以10为单位,稍长点
let posMaxX = maxX - minX + 20; //x绘制位置,相对距离从标尺偏移十个像素
let posMinX = 10;//x绘制位置,相对距离从标尺偏移十个像素<br>
const startPoint = tplInfo.startLine.positon;<br>
let doc = new vjmap.DbDocument();
// 数据来源
doc.from = `${templateSectId}/${templateSecVersion}`;<br>
// 把来源图的数据最后都清空,(这里的模板不需要清空,直接用了)
// doc.isClearFromDb = true;
let entitys = [];<br>
// 左边刻度
entitys.push(new vjmap.DbLine({
objectid: "169A2",
start: startPoint,
end: , startPoint + (maxY - minY)]
}))
for(let y = minY; y < maxY; y += 10) {
let pt = , startPoint + maxY - y];
entitys.push(new vjmap.DbLine({
start: pt,
end: - 2, pt]
}))
// 刻度值<br>
entitys.push(new vjmap.DbText({
cloneObjectId: '168C8',
position: - 1, pt + 0.2],
text: y + ''
}))
}
// 右边刻度
entitys.push(new vjmap.DbLine({
cloneObjectId: "169A2", // 不是修改了,是克隆左边的刻度线
start: + posMaxX, startPoint],
end: + posMaxX, startPoint + (maxY - minY)]
}))
for(let y = minY; y < maxY; y += 10) {
let pt = , startPoint + maxY - y];
entitys.push(new vjmap.DbLine({
start: + posMaxX , pt],
end: + posMaxX + 2, pt]
}))
// 刻度值
entitys.push(new vjmap.DbText({
cloneObjectId: '168C8',
position: + posMaxX + 1, pt + 0.2],
text: y + ''
}))
}<br>
// 修改线坐标
entitys.push(new vjmap.DbLine({
cloneObjectId:tplInfo.startLine.objectId,
start: , startPoint],
end: + posMaxX, startPoint]
}))<br><br>
// 演示下块及属性字段的使用,这里用块创建一个孔口名称和x坐标,中间用横线隔开
const blockName = "nameAndx";
let block = new vjmap.DbBlock();
block.name = blockName;
block.origin =
block.entitys = [
new vjmap.DbAttributeDefinition({
position: ,
contents: "名称",
tag: "NAME",
colorIndex: 7, // 自动反色
horizontalMode: vjmap.DbTextHorzMode.kTextCenter,
verticalMode: vjmap.DbTextVertMode.kTextBottom, // kTextBottom,
height: 0.5,
}),
new vjmap.DbLine({
start: [-2, 0],
end:
}),
new vjmap.DbAttributeDefinition({
position: ,
contents: "X坐标",
tag: "POSX",
colorIndex: 7, // 自动反色
horizontalMode: vjmap.DbTextHorzMode.kTextCenter,
verticalMode: vjmap.DbTextVertMode.kTextTop, // kTextBottom,
height: 0.5,
})
];
doc.appendBlock(block);
// 绘制每一个孔
for(let i = 0; i < drawData.length; i++) {
// 开始绘制的位置点
let x = posMinX + drawData.x - minX;
let y = startPoint + maxY - drawData.y;
// 名称和x,用上面的块创建块参照
let blockRef = new vjmap.DbBlockReference();
blockRef.blockname = blockName;
blockRef.position =;
// 修改属性定义值
blockRef.attribute = {
NAME: drawData.name,
POSX: drawData.x
}
entitys.push(blockRef);<br>
// 一层一层绘制
for(let k = 0; k < drawData.stratums.length; k++) {
let y2 = y - drawData.stratums.thickness;
let bounds = vjmap.GeoBounds.fromArray();
let points = bounds.toPointArray(); // 转成点坐标格式
// 闭合
points.push(points);
// 填充
entitys.push(new vjmap.DbHatch({
cloneObjectId: drawData.stratums.hatch.hatchObjectId,
points: points,
patternScale: 1.5
}))
// 边框
entitys.push(new vjmap.Db2dPolyline({
points: points
}))<br>
// 绘制连接下一个孔的线
if (i != drawData.length - 1) {
const nextKongStratums = drawData.stratums;
let nextX = posMinX + drawData.x - minX;
let nextY = startPoint + maxY - drawData.y;
if (k < nextKongStratums.length) {
for(let n = 0; n <= k; n++) {
nextY = nextY - drawData.stratums.thickness;
}
entitys.push(new vjmap.DbLine({
start: ,
end:
}))
}
// 水平间距
entitys.push(new vjmap.DbLine({
start: ],
end: - 2]
}))
entitys.push(new vjmap.DbLine({
start: ],
end: - 2]
}))
entitys.push(new vjmap.DbLine({
start: - 2],
end: - 2]
}))
// 间距值
entitys.push(new vjmap.DbText({
cloneObjectId: '168C8',
position: [(x + nextX) / 2, startPoint - 1],
text: nextX - x,
horizontalMode: vjmap.DbTextHorzMode.kTextCenter, // kTextCenter
verticalMode: vjmap.DbTextVertMode.kTextVertMid // kTextVertMid,
}))
}
y = y2;
}
// 最下面写上累计厚度值
entitys.push(new vjmap.DbText({
cloneObjectId: '168C8',
position: ,
text: drawData.stratumsThickness,
horizontalMode: vjmap.DbTextHorzMode.kTextCenter, // kTextCenter
verticalMode: vjmap.DbTextVertMode.kTextTop // kTextTop,
}))<br><br>
}<br>
entitys.push(new vjmap.DbText({
objectid: '1687C',
position: [(posMinX + posMaxX) / 2.0, startPoint + maxY - minY + 10],
/* 如果是相对位置,可以利用矩阵
matrix: [
{
op: "translation",
vector: [相对偏移x, 相对偏移y]
}
],*/
text: `剖面图${Date.now()}`
}))<br>
// 绘制图框
let bounds = vjmap.GeoBounds.fromArray( + maxY - minY + 15, posMaxX + 10, startPoint - 20]);
let labelPos = ;
let points = bounds.toPointArray(); // 转成点坐标格式
// 闭合
points.push(points);
// 边框
entitys.push(new vjmap.Db2dPolyline({
points: points
}))
bounds = bounds.scale(1.02);
points = bounds.toPointArray(); // 转成点坐标格式
// 闭合
points.push(points);
// 边框
entitys.push(new vjmap.Db2dPolyline({
points: points,
lineWidth: 30 // mm
}))<br>
let date = new Date();
// 图框从其他模板插入,并修改块属性文字
entitys.push(new vjmap.DbBlockReference({
cloneObjectId: '6A1',
cloneFromDb: `${templateTkMapId}/${templateTkVersion}`,
position: labelPos,
attribute: {
// 修改块中的属性字段
DATETIME: `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`,
COMPANY: {
text: "唯杰地图VJMAP",
color: 0x00FFFF
}
}
}))<br>
entitys.push(new vjmap.DbLine({
objectid: "168C8", // 这个模板文字不用了,直接删除了
delete: true
}))
doc.entitys = entitys;
return doc;
}<br><br>
// 先得设置一个要图形的所有范围,这个范围是随便都没有有关系的。最后导出dwg时,会根据实体的所有坐标去自动计算真实的范围。
let mapBounds = '[-10000,-10000,10000,10000]'
let mapExtent = vjmap.GeoBounds.fromString(mapBounds);
mapExtent = mapExtent.square(); // 要转成正方形<br>
svc.setCurrentMapParam({
darkMode: true, // 由于没有打开过图,所以主动设置黑色模式
bounds: mapExtent.toString()
})
// 建立坐标系
let prj = new vjmap.GeoProjection(mapExtent);<br>
// 新建地图对象
let map = new vjmap.Map({
container: 'map', // container ID
style: {
version: svc.styleVersion(),
glyphs: svc.glyphsUrl(),
sources: {},
layers: []
},// 矢量瓦片样式
center: , // 中心点
zoom: 2,
renderWorldCopies: false
});
// 地图关联服务对象和坐标系
map.attach(svc, prj);<br>
// 使地图全部可见
map.fitMapBounds();
await map.onLoad();<br><br>
// 创建一个几何对象
const createGeomData = async (map, doc) => {
let svc = map.getService();
let res = await svc.cmdCreateEntitiesGeomData({
filedoc: doc.toDoc()
});
if (res.error) {
message.error(res.error);
return {
type: "FeatureCollection",
features: []
};
}
if (res.metadata && res.metadata.mapBounds) {
// 如果返回的元数据里面有当前地图的范围,则更新当前地图的坐标范围
map.updateMapExtent(res.metadata.mapBounds);
}<br>
const features = [];
if (res && res.result && res.result.length > 0) {
for (let ent of res.result) {
if (ent.geom && ent.geom.geometries) {
let clr = map.entColorToHtmlColor(ent.color); // 实体颜色转html颜色
let featureAttr = {};
// 因为要组合成一个组合实体,所以线和多边形的颜色得区分
if (ent.isPolygon) {
featureAttr.color = clr; // 填充色,只对多边形有效
featureAttr.noneOutline = true; // 不显示多边形边框,只对多边形有效
} else {
featureAttr.color = clr; // 颜色
featureAttr.line_width = ent.lineWidth; // 线宽
}
let ft = {
id: vjmap.RandomID(10),
type: "Feature",
properties: {
objectid: ent.objectid,
opacity: ent.alpha / 255,
...featureAttr,
}
}
if (ent.geom.geometries.length == 1) {
features.push({
...ft,
geometry: ent.geom.geometries,
});
} else {
features.push({
...ft,
geometry: {
geometries: ent.geom.geometries,
type: "GeometryCollection"
},
});
}<br>
}
}
}
return {
type: "FeatureCollection",
features: features,
};
};<br>
// 清空之前的地图数据
const clearMapData = () => {
svc.setCurrentMapParam({
darkMode: true, // 由于没有打开过图,所以主动设置黑色模式
bounds: mapExtent.toString()
})
map.disableLayerClickHighlight();
map.removeDrawLayer();
let sources = map.getStyle().sources;
for(let source in sources) {
map.removeSourceEx(source);
}
}<br>
// 创建一个有数据的地图
const createDataMap = async (doc) => {
clearMapData();
let geojson = await createGeomData(map, doc);
const opts = vjmap.Draw.defaultOptions();
// 修改默认样式,把点的半径改成1,没有边框,默认为5
let pointIdx = opts.styles.findIndex(s => s.id === "gl-draw-point-point-stroke-inactive");
if (pointIdx >= 0) {
opts.styles['paint']['circle-radius'] = 0
}
pointIdx = opts.styles.findIndex(s => s.id === "gl-draw-point-inactive");
if (pointIdx >= 0) {
opts.styles['paint']['circle-radius'] = 1
}
map.getDrawLayer(opts).set(geojson);
}<br>
// 创建一个dwg的地图
const createDwgMap = async (doc) => {
// 先清空之前绘制的
clearMapData();
// js代码
let res = await svc.updateMap({
// 获取一个临时的图id(临时图形只会用临时查看,过期会自动删除)
mapid: vjmap.getTempMapId(1), // 临时图形不浏览情况下过期自动删除时间,单位分钟。默认30
filedoc: doc.toDoc(),
mapopenway: vjmap.MapOpenWay.Memory,
style: {
backcolor: 0 // 如果div背景色是浅色,则设置为oxFFFFFF
}
})
if (res.error) {
message.error(res.error)
}
await map.switchMap(res);
}<br><br>
let curDoc;
const exportDwgOpen = async () => {
if (!curDoc) return;
const mapid = 'exportdwgmap';
let res = await svc.updateMap({
mapid: mapid,
filedoc: curDoc.toDoc(),
mapopenway: vjmap.MapOpenWay.Memory,
style: {
backcolor: 0 // 如果div背景色是浅色,则设置为oxFFFFFF
}
})
if (res.error) {
message.error(res.error)
} else{
window.open(`https://vjmap.com/app/cloud/#/map/${res.mapid}?version=${res.version}&mapopenway=Memory&vector=false`)
}
}<br>
// 获取模板的所有数据
const getTemplateData = async (mapid, version) => {
let res = await svc.rectQueryFeature({
mapid,
version,
fields: "",
geom: false, // 以内存方式打开,获取真正的objectid
maxGeomBytesSize: 0, // 不需要几何坐标
useCache: true, // 因为是以内存方式打开,后台先把查询的数据保存进缓存,下次直接去缓存查找,提高效率
// x1,y1,x2,y2同时不输的话,表示是查询整个图的范围这范围不输入,表示是全图范围
})
// 把实体的范围字符串转成对象
res.result.map(f => f.envelop = vjmap.GeoBounds.fromString(f.bounds));
console.log(res.result)
return res.result;
}<br><br>
const creatSectDataMap = async () => {
let sectData = mockData(tplInfo.hatchInfos, 5);
const doc = await createSectDoc(sectData);
await createDataMap(doc);
map.fitMapBounds();
curDoc = doc;
}
const creatSectDwgMap = async () => {
let sectData = mockData(tplInfo.hatchInfos, 15);
const doc = await createSectDoc(sectData);
await createDwgMap(doc);
map.fitMapBounds();
// 点击有高亮状态(鼠标点击地图元素上时,会高亮)
map.enableLayerClickHighlight(svc, e => {
if (!e) return;
let msg = {
content: `type: ${e.name}, id: ${e.objectid}, layer: ${e.layerindex}`,
key: "layerclick",
duration: 5
}
e && message.info(msg);
})
curDoc = doc;
}
// 先获取模板信息
tplInfo = await getTemplateInfo(templateSectId, templateSecVersion);
// 随机生成一个剖面图
creatSectDataMap();
// UI界面
const App = () => {
return (
<button className="btn btn-full mr0" onClick={creatSectDataMap}>随机生成一个剖面图[前端直接绘制,适合于生成图不大的情况]</button>
<button className="btn btn-full mr0" onClick={creatSectDwgMap}>随机生成剖面图[后台生成DWG前端展示,适合于生成图大的情况]</button>
<button className="btn btn-full mr0" onClick={exportDwgOpen}>导出成DWG图并打开</button>
);
}
ReactDOM.render(<App />, document.getElementById('ui'));<br><br>
const mousePositionControl = new vjmap.MousePositionControl();
map.addControl(mousePositionControl, "bottom-left");
<br>
来源:https://www.cnblogs.com/vjmap/p/17179837.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!
页:
[1]