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 = data let 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.features patternConfig.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 = null for (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 = false if (exists) { try { const action = await ElMessageBox.confirm( `配置名称 "${name}" 已存在,请选择操作`, '名称重复', { confirmButtonText: '覆盖', cancelButtonText: '新增', distinguishCancelAndClose: true, type: 'warning' } ) shouldOverwrite = true patternConfig.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.data if (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.data if (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 = true formData.value.scaleMin = rule.transform.scale.min || 0.5 formData.value.scaleMax = rule.transform.scale.max || 2 } else { formData.value.allowScale = false } if (rule.transform.rotation) { formData.value.allowRotation = true formData.value.rotationMin = rule.transform.rotation.min || 0 formData.value.rotationMax = rule.transform.rotation.max || 360 } else { formData.value.allowRotation = false } } } // 同步其他配置项 formData.value.sameColor = configData.sameColor || false formData.value.sameLayer = configData.sameLayer || false // 更新缩略图 if (configData.thumbnail) { thumbnailImage.value = configData.thumbnail } else { thumbnailImage.value = '' } currentFeatures.value = patternConfig.rules[patternConfig.rules.length - 1].referenceObjectIds ElMessage.success(`已加载配置: ${config.name}`) } } catch (error) { console.error('Load config failed:', error) ElMessage.error('选择配置失败') } }
在线体验地址