CAD实体对象智能识别
概述
实体对象智能识别能够在CAD图纸中智能识别和匹配相似的实体对象。该系统采用模式匹配算法,支持几何变换(缩放、旋转),并提供了丰富的配置选项和可视化界面。
系统提供两种主要的识别方式:
1. 块参照实体识别
针对CAD图纸中以块(Block)形式存在的对象,系统能够:
- 批量获取所有块参照:自动检索图纸中的所有块参照实体,支持从元数据或表达式查询获取
- 块信息管理:显示块名称、对象ID、图层名称、边界范围、位置坐标、旋转角度、缩放比例等详细信息
- 可视化展示:通过动画线框和高亮显示所有块的位置和边界
- 交互式操作:支持点击表格行定位到对应块,支持显示/隐藏所有块的可视化效果
- 悬浮提示:鼠标悬停时显示块的详细属性信息,包括属性数据
2. 智能对象识别
针对不是块形式的普通CAD实体对象,系统提供:
- 模式匹配识别:基于用户选择的参考实体,在图纸中智能匹配相似的对象
- 几何变换支持:支持缩放、旋转等几何变换的容差匹配
- 多条件筛选:支持颜色、图层、文本内容等多种匹配条件
- 配置化管理:支持保存和加载识别配置,提高识别效率
核心功能特性
1. 智能实体选择
- 多种选择模式:支持框选、点选、多边形选择三种实体选择方式
- 实时预览:选择后立即显示所选实体数量
- 边界计算:自动计算所选实体的边界范围
2. 几何变换支持
- 缩放变换:支持设置缩放比例范围(0.1-10倍)
- 旋转变换:支持设置旋转角度范围(0-360度)
- 变换容差:可配置变换匹配的容差值
3. 匹配条件配置
- 颜色匹配:可设置是否要求实体颜色相同
- 图层匹配:可设置是否要求实体在同一图层
- 文本内容匹配:可设置文本类实体是否要求内容相同
- 自定义匹配规则:支持针对不同实体类型配置匹配属性
4. 配置管理系统
- 配置保存:支持将识别配置保存到服务器
- 配置加载:支持从服务器加载已保存的配置
- 配置覆盖:支持覆盖同名配置
- 配置删除:支持删除不需要的配置
5. 缩略图生成
- 自动生成:选择实体后自动生成120x80像素的缩略图
- 配置关联:缩略图与配置一起保存,便于快速识别
- 实时更新:选择实体变化时自动更新缩略图
使用场景
1. 块参照实体管理
- 块实体统计:快速统计图纸中所有块参照的数量和类型分布
- 块位置定位:通过表格快速定位到指定块的位置,便于查看和编辑
- 块属性查看:查看块的详细属性信息,包括位置、旋转角度、缩放比例等
- 块重复性分析:识别相同块名的多个实例,分析设计重复性
2. CAD图纸标准化检查
- 检查图纸中相似元素的一致性
- 发现不规范的设计元素
3. 重复元素检测
- 识别图纸中的重复设计
- 优化图纸存储和传输
4. 设计模式分析
- 分析图纸中的设计模式
- 提取可复用的设计元素
5. 智能对象匹配
- 基于参考实体智能匹配相似对象
- 支持几何变换的模糊匹配
- 提高CAD图纸处理的自动化程度
技术实现架构
1. 核心数据结构
模式配置数据
let patternConfig: any = {"name": "","sameColor": true,"sameLayer": false,"global": {"match": {"all": {"equalMatchProperty": ["color"]},"AcDbCircle": {"scaleMatchProperty": ["radius"]},"AcDbArc": {"scaleMatchProperty": ["radius"]},"AcDbLine": {"scaleMatchProperty": ["length"]},"AcDbText": {"scaleMatchProperty": ["height"]}}},"rules": [{"tolerance": 0.00001,"translateTolerance": 0.015,"transform": {"scale": {"min": 0.5,"max": 2.0},"rotation": {"min": 0,"max": 360}},"referenceObjectIds": []}]
}
2. 关键算法实现
块参照实体检索算法
const detectAllBlockRefsObjects = async () => {try {removeAntPathAnimateLine();let svc = map.getService();let metadata = await svc.metadata();let blockReferences = []if("blockReferences" in metadata) {// 从元数据中获取块参照信息(新版本)let blockRefs = metadata.blockReferences;if (blockRefs) {blockReferences = JSON.parse(blockRefs);}} else {// 通过表达式查询获取块参照信息(旧版本兼容)if (map.logInfo) map.logInfo("正在获取所有块数据,请稍候...", "success", 2000);let query = await svc.exprQueryFeature({expr: `gOutReturn := if((gInFeatureType == 'AcDbBlockReference'), 1, 0);`,fields: "", // 查询所有字段geom: false, // 内存模式useCache: true,limit: 1000000 // 查找所有块参照});if (query.error) {ElMessage({type: 'error',message: query.error,});return;}blockReferences = query.result.map((r: any) => {return {objectId: r.objectid,blockName: r.blockname,layerName: r.layername,bounds: r.bounds,position: r.positon,...r};});}// 保存数据并创建可视化效果blockRefsData.value = blockReferences;let data = getData();createAntPathAnimateLine(data);createHighlightBlocks(data);ElMessage({type: 'success',message: `共找到 ${blockReferences.length} 个块参照实体。`,});} catch (error) {showError("获取块参照实体失败:" + (error as any).message);}
}
块可视化高亮算法
const createHighlightBlocks = (data: any) => {let polygons = datalet polygon = new vjmap.Polygon({data: polygons,fillColor: ['case', ['to-boolean', ['feature-state', 'hover']], '#0ff', '#f00'],// 默认透明,鼠标悬停时显示高亮fillOpacity: ['case', ['to-boolean', ['feature-state', 'hover']], 0.1, 0.0],fillOutlineColor: ['get', 'color'],isHoverPointer: true,isHoverFeatureState: true});polygon.addTo(map);// 悬浮提示功能polygon.hoverPopup((f: any, popup: any) => {let bounds = vjmap.GeoBounds.fromDataExtent(f);popup.setLngLat([bounds.center().x, bounds.max.y]);return `<h3>块名称: ${f.properties.blockName}</h3>图层名称: ${f.properties.layerName}<br>对象ID: ${f.properties.objectId}${f.properties.attributeDef ? '<br>属性数据: ' + f.properties.attributeDef : ''}`}, { anchor: 'bottom' });polygonOverlay = polygon;
}
实体选择算法
const select = async () => {removeAntPathAnimateLine()let result = await selectFeatures(map, null, btnCtx, false)if (!result || !result.features) return;currentFeatures.value = result.featurespatternConfig.rules[patternConfig.rules.length - 1].referenceObjectIds = result.features// 选择后生成缩略图await generateThumbnail()
}
缩略图生成算法
const generateThumbnail = async () => {if (currentFeatures.value.length === 0) {thumbnailImage.value = ''return}try {isGeneratingThumbnail.value = true// 获取所有选中对象的边界let allBounds = nullfor (const feature of currentFeatures.value) {const bounds = vjmap.GeoBounds.fromString(feature.properties.bounds)if (!allBounds) {allBounds = bounds} else {allBounds.updateByBounds(bounds)}}if (allBounds) {// 生成 120x80 的缩略图let objectIds = currentFeatures.value.map((item: any) => item.properties.objectid)const base64Image = await getObjectsThumbnail(map, objectIds, allBounds, 120, 80)thumbnailImage.value = base64Image}} catch (error) {console.error('Generate thumbnail failed:', error)thumbnailImage.value = ''} finally {isGeneratingThumbnail.value = false}
}
智能识别算法
const detectAllObjects = async () => {try {if (!formData.value.keepLastResult) {removeAntPathAnimateLine();}if (currentFeatures.value.length == 0) {ElMessage({type: 'error',message: '请先选择要识别的实体',})return;}let svc = map.getService();ElMessage({type: 'success',message: '正在执行智能识别,请稍候...',})// 调用后端识别服务let res = await svc.execCommand("objectDetection", {mapid: svc.currentMapParam()?.mapid,version: svc.currentMapParam()?.version,layer: svc.currentMapParam()?.layer,pattern: JSON.stringify(patternConfig),detailedLog: formData.value.detailedLog,bounds: formData.value.bounds}, "_null", "v1");// 处理识别结果if (res && res.result && res.result.length > 0) {// 在地图上显示识别结果let geoDatas = [];for (let i = 0; i < res.result.length; i++) {const bounds = vjmap.GeoBounds.fromString(res.result[i].bounds)const pts = bounds.toPointArray();pts.push(pts[0])geoDatas.push({points: map.toLngLat(pts.map(p => vjmap.geoPoint(p))),properties: {color: "#ff0000"}})}// 创建动画线图层let antPathAnimateLine = vjmap.createAntPathAnimateLineLayer(map, geoDatas, {fillColor1: "#f00",fillColor2: formData.value.keepLastResult ? vjmap.randomColor() : "#0ff",canvasWidth: 128,canvasHeight: 32,frameCount: 4,lineWidth: 2,lineOpacity: 0.8});map._dectionAntPathAnimateLines = map._dectionAntPathAnimateLines || [];map._dectionAntPathAnimateLines.push(antPathAnimateLine);ElMessage({type: 'success',message: `共找到 ${geoDatas.length} 个匹配的对象。`,})}} catch (error) {showError("识别失败:" + (error as any).message);}
}
4. 配置管理实现
配置保存
const saveConfig = async () => {try {// 确保配置列表已加载,用于检查重复名称if (configList.value.length === 0) {await loadConfigList()}// 获取配置名称const name = await getInput('保存配置', '请输入配置名称', patternConfig.name || '')if (!name || typeof name !== 'string') {ElMessage.warning('配置名称不能为空')return}// 检查名称是否重复const exists = checkConfigExists(name)let shouldOverwrite = falseif (exists) {try {const action = await ElMessageBox.confirm(`配置名称 "${name}" 已存在,请选择操作`,'名称重复',{confirmButtonText: '覆盖',cancelButtonText: '新增',distinguishCancelAndClose: true,type: 'warning'})shouldOverwrite = truepatternConfig.name = name} catch (error: any) {if (error === 'cancel') {patternConfig.name = name} else {return}}} else {patternConfig.name = name}// 如果是覆盖操作,先删除旧配置if (shouldOverwrite) {const existingConfig = configList.value.find((c: any) => c.name === name)if (existingConfig) {const svc = map.getService()const deleteUrl = svc.serviceUrl(`recognizer/delete?filename=${existingConfig.filePrefix}`)await svc.httpDel(deleteUrl)}}// 添加缩略图到配置中if (thumbnailImage.value) {patternConfig.thumbnail = thumbnailImage.value}const svc = map.getService()const url = svc.serviceUrl("recognizer/add")const response = await svc.httpPost(url, patternConfig)const result = response.dataif (result.code === 0) {ElMessage.success(shouldOverwrite ? '配置覆盖成功' : '配置保存成功')await loadConfigList()} else {ElMessage.error('配置保存失败')}} catch (error) {console.error('Save config failed:', error)ElMessage.error('配置保存失败')}
}
配置加载
const selectConfig = async (config: any) => {try {selectedConfigId.value = config.filePrefix// 获取配置详细数据const svc = map.getService()const url = svc.serviceUrl(`recognizer/detail?filenames=${config.filePrefix}`)const response = await svc.httpGet(url)const result = response.dataif (result.code === 0 && result.data && result.data.length > 0) {const configData = result.data[0]// 加载配置数据patternConfig = configData// 同步表单数据if (configData.rules && configData.rules.length > 0) {const rule = configData.rules[0]if (rule.transform) {if (rule.transform.scale) {formData.value.allowScale = trueformData.value.scaleMin = rule.transform.scale.min || 0.5formData.value.scaleMax = rule.transform.scale.max || 2} else {formData.value.allowScale = false}if (rule.transform.rotation) {formData.value.allowRotation = trueformData.value.rotationMin = rule.transform.rotation.min || 0formData.value.rotationMax = rule.transform.rotation.max || 360} else {formData.value.allowRotation = false}}}// 同步其他配置项formData.value.sameColor = configData.sameColor || falseformData.value.sameLayer = configData.sameLayer || false// 更新缩略图if (configData.thumbnail) {thumbnailImage.value = configData.thumbnail} else {thumbnailImage.value = ''}currentFeatures.value = patternConfig.rules[patternConfig.rules.length - 1].referenceObjectIdsElMessage.success(`已加载配置: ${config.name}`)}} catch (error) {console.error('Load config failed:', error)ElMessage.error('选择配置失败')}
}
在线体验地址 https://vjmap.com/app/cloud/#/