HarmonyOS鸿蒙Next中(WEB CAD online)在线CAD图纸表格提取到excel

HarmonyOS鸿蒙Next中(WEB CAD online)在线CAD图纸表格提取到excel

前言

CAD图纸上的表格信息承载着大量关键数据,生产过程中会导出表格数据到excel,本文将介绍如何通过自定义 MxCAD 插件,在web端实现对CAD图纸中表格的智能识别、自动合并与高效导出,大幅提升数据提取效率与准确性,效果如下:

一、功能概述

本次图纸表格提取主要实现以下核心功能:

  1. 交互式区域选择:用户通过鼠标框选目标表格区域。
  2. 图形元素识别:自动识别范围内的直线、文字、多段线等实体。
  3. 表格结构重建:基于交点分析重建表格网格。
  4. 智能单元格合并:支持横向与纵向跨单元格合并识别。
  5. 内容提取与导出:提取单元格文本内容并导出为 Excel 文件。

二、技术实现原理

2.1 实体获取与预处理

首先让用户指定一个提取范围(矩形框),然后利用mxcad中的[MxCADSelectionSet]选择集跨区域选择所有相关实体:

const ss = new MxCADSelectionSet();
await ss.crossingSelect(corner1.x, corner1.y, corner2.x, corner2.y);

为确保嵌套块(BlockReference)中的实体也能被识别,程序递归遍历块定义,并应用变换矩阵(blockTransform)还原其真实坐标位置。

const needTransformEntity: { handle: string, mart: McGeMatrix3d }[] = [];
const Mx_getBlokEntity = (blkRec: McDbBlockTableRecord, mart: McGeMatrix3d) => {
    blkRec.getAllEntityId().forEach(id => {
        let ent = id.getMcDbEntity();
        if (ent instanceof McDbBlockReference) {
            let blkref = ent as McDbBlockReference;
            let mat = blkref.blockTransform.clone();
            mat.preMultBy(mart);
            Mx_getBlokEntity(blkref.blockTableRecordId.getMcDbBlockTableRecord(), mat);
        } else {
            needTransformEntity.push({ handle: ent.getHandle(), mart });
            ...
        }
    })
}

此外,多段线(Polyline)会被打散为独立的直线或圆弧段,便于后续交点计算。

const explodePl = (ent: McDbPolyline, mart?: McGeMatrix3d): McDbEntity[] => {
    // 如果是多段线,需要打散成线段
    const numVert = ent.numVerts();
    const entsArr: McDbEntity[] = [];
    for (let i = 0; i < numVert; i++) {
        if (i < numVert - 1) {
            const convexity = ent.getBulgeAt(i);
            const pt1 = ent.getPointAt(i).val;
            const pt2 = ent.getPointAt(i + 1).val;
            if (mart) {
                pt1.transformBy(mart);
                pt2.transformBy(mart);
            }
            if (!convexity) {
                const line = new McDbLine(pt1, pt2);
                entsArr.push(line)
            } else {
                const d = (ent.getDistAtPoint(pt1).val + ent.getDistAtPoint(pt2).val) / 2;
                const midPt = ent.getPointAtDist(d).val;
                const arc = new McDbArc();
                arc.computeArc(pt1.x, pt1.y, midPt.x, midPt.y, pt2.x, pt2.y);
                entsArr.push(arc)
            }
        } else {
            if (ent.isClosed) entsArr.push(new McDbLine(ent.getPointAt(0).val, ent.getPointAt(numVert - 1).val))
        }
    }
    return entsArr;
}

2.2 表格线段分类

在上述步骤中,我们提取到了图纸框选范围内的所有实体并对部分实体做了初步处理,接下来我们需要通过提取出框选范围内的所有直线,并将这些直线分为两类:

  • 水平线:方向接近 X 轴
  • 垂直线:方向接近 Y 轴

直线的分类通过线向量与X轴、Y轴的单位向量之间的夹角来判断:

const horizontalLineArr: McDbLine[] = [];//横向
const verticalLineArr: McDbLine[] = [];//纵向
lineArr.forEach(item => {
    const vec_x = McGeVector3d.kXAxis;
    const vec_y = McGeVector3d.kYAxis;
    const line = item.clone() as McDbLine;
    //判断直线是块内实体,如果是则需要使用变换矩阵还原真是坐标位置
    const res = needTransformEntity.find(i => i.handle === item.getHandle());
    if (res) {
        line.startPoint = line.startPoint.clone().transformBy(res.mart);
        line.endPoint = line.endPoint.transformBy(res.mart);
    }
    const _vec = line.startPoint.sub(line.endPoint).normalize().mult(precision);
    if (vec_x.angleTo1(_vec) < precision || Math.abs((vec_x.angleTo1(_vec) - Math.PI)) < precision) {
        horizontalLineArr.push(new McDbLine(line.startPoint.addvec(_vec), line.endPoint.subvec(_vec)))
    }
    if (vec_y.angleTo1(_vec) < precision || Math.abs((vec_y.angleTo1(_vec) - Math.PI)) < precision) {
        verticalLineArr.push(new McDbLine(line.startPoint.addvec(_vec), line.endPoint.subvec(_vec)))
    };
});

2.3 表格交点提取与去重

在上一步中,我们以及获取到了所有的横纵直线。接下来,我们将利用水平线与垂直线之间的交点构建表格节点矩阵。所有交点经过坐标四舍五入(精度控制)和去重处理,形成唯一的网格点集合。

// 点数组去重
const deduplicatePoints = (points: McGePoint3d[]): McGePoint3d[]=> {
    const allPoints: McGePoint3d[] = [];
    points.forEach((item, index) => {
        const res = points.filter((j, ind) => {
            return ind > index && item.distanceTo(j) < 0.00001
        });
        if (!res.length) allPoints.push(item)
    });
    return allPoints;
}
// 根据线拿到所有的点
const roundToPrecision = (num, precision = 0.0001): number => {
    const decimals = Math.abs(Math.floor(Math.log10(precision))); // 计算精度对应的小数位数
    const factor = Math.pow(10, decimals);
    return Math.round(num * factor) / factor;
}
let allPoints: McGePoint3d[] = [];
horizontalLineArr.forEach(line1 => {
    verticalLineArr.forEach(line2 => {
        const res = line1.IntersectWith(line2, McDb.Intersect.kOnBothOperands);
        if (res.length()) res.forEach(pt => {
            pt.x = roundToPrecision(pt.x, precision);
            pt.y = roundToPrecision(pt.y, precision);
            if (arePointsInRectangle([new_corner1, new McGePoint3d(new_corner1.x, new_corner2.y), new_corner2, new McGePoint3d(new_corner2.x, new_corner1.y)], [pt])) {
                allPoints.push(pt)
            }
        })
    })
});
allPoints = deduplicatePoints(allPoints);//点数组去重;

2.4 构建初始单元格矩阵

根据交点的 X 和 Y 坐标排序,生成二维网格结构 cellPointsArr,每个元素为交点或 null(表示缺失的角点),例如:

[
  [A1, B1, null, D1],
  [A2, B2, C2, D2],
  [null, B3, C3, D3]
]
const _x = Array.from(new Set(allPoints.map(item => item.x))).sort((a, b) => a - b);
const _y = Array.from(new Set(allPoints.map(item => item.y))).sort((a, b) => b - a);
const cellPointsArr: (McGePoint3d | null)[][] = [];
_y.forEach((y, row) => {
    const arr: (McGePoint3d | null)[] = [];
    const pts = allPoints.filter(item => item.y === y);
    if (pts.length) {
        _x.forEach((x, col) => {
            const index = pts.findIndex(item => item.x === x);
            // 若表格四个角点缺失,则手动补充数据使表格完整
            if (index === -1) {
                if ((row === 0 || row === _y.length - 1) && (col === 0 || row === _x.length - 1)) {
                    arr.push(new McGePoint3d(x, y));
                } else {
                    arr.push(null)
                }
            } else {
                arr.push(pts[index])
            }
        });
        cellPointsArr.push(arr)
    } else {
        cellPointsArr.push(null);
    }
});

三、智能单元格合并机制

3.1 合并策略总览

接下来我们将采用两阶段合并策略:

  1. 横向合并优先
  2. 纵向合并补充

纵向合并仅在横向合并后形成的 2×2 子矩阵仍包含 null 元素 时触发。

3.2 横向合并逻辑

系统将整个表格划分为多个 2×2 子矩阵块,每个块以左上角单元格命名(如 B2 表示第2行第2列开始的块)。

对于每一个 2×2 块,若其四个角点中有 null,则判定为“不完整”,需要参与合并。

合并规则(横向扩展)

条件 查找方向 判断依据
第一个元素为 null 左侧块 当前块的左邻块(如 A2)第二个元素是否为 null
第二个元素为 null 右侧块 当前块的右邻块(如 C2)第一个元素是否为 null
第三个元素为 null 左侧块 当前块的左邻块第四个元素是否为 null
第四个元素为 null 右侧块 当前块的右邻块第三个元素是否为 null

示例:B2:[[null,a],[c,b]] → 检查 A2 的第二个元素是否为 null

通过广度优先搜索(BFS),收集所有可横向连接的“不完整”块,形成一个合并组。

3.3 纵向合并触发条件

当横向合并完成后,若新生成的 2×2 外围矩阵仍含有 null,则启动纵向合并流程。

纵向合并规则

条件 查找方向 判断依据
第一个元素为 null 上方块 上方块(如 B1)第三个元素是否为 null
第二个元素为 null 上方块 上方块第四个元素是否为 null
第三个元素为 null 下方块 下方块(如 B3)第一个元素是否为 null
第四个元素为 null 下方块 下方块第二个元素是否为 null

示例:B2:[[a,null],[c,b]] → 检查 B1 的第四个元素是否为 null

程序继续扩展合并组,直到包围盒内所有 2×2 块都被纳入,最终形成一个完整的矩形区域。

3.4 合并结果生成

合并完成后,系统计算最小行/列与最大行/列,生成新的 2×2 矩阵代表合并区域的四个角点,并记录其原始单元格范围(如 "A1+B1+A2+B2")。

// 合并表格
function solveWithMerging(input: MatrixValue[][]): MergeResult[] {
    const rows = input.length;
    const cols = input[0].length;
    if (rows < 2 || cols < 2) {
        return;
    }

    // 1. 提取所有 2x2 子矩阵
    const blocks: Record<string, MatrixValue[][]> = {};
    const positions: Record<string, Position> = {};

    for (let r = 0; r <= rows - 2; r++) {
        for (let c = 0; c <= cols - 2; c++) {
            const key = `${String.fromCharCode(65 + c)}${r + 1}`;
            blocks[key] = [
                [input[r][c], input[r][c + 1]],
                [input[r + 1][c], input[r + 1][c + 1]]
            ];
            positions[key] = { row: r, col: c };
        }
    }

    // 工具:判断是否含 null
    const hasNull = (mat: MatrixValue[][]): boolean =>
        mat.some(row => row.some(cell => cell === null));

    const processed = new Set<string>(); // 已参与合并的块
    const results: MergeResult[] = [];

    // 筛选出所有块
    const getAllBlockNames = (visited: Set<string>): { fullRangeKeys: string[], newMatrix: MatrixValue[][] } => {
        // 获取包围盒(原始合并区域)
        let minRow = Infinity, maxRow = -Infinity;
        let minCol = Infinity, maxCol = -Infinity;

        Array.from(visited).forEach(key => {
            const { row, col } = positions[key];
            minRow = Math.min(minRow, row);
            maxRow = Math.max(maxRow, row);
            minCol = Math.min(minCol, col);
            maxCol = Math.max(maxCol, col);
        });

        // ===== 拓展:生成包围盒内所有 2×2 块名(完整矩形区域)=====
        const fullRangeKeys: string[] = [];
        for (let r = minRow; r <= maxRow; r++) {
            for (let c = minCol; c <= maxCol; c++) {
                const key = `${String.fromCharCode(65 + c)}${r + 1}`;
                fullRangeKeys.push(key);
                // 标记这些块为已处理(防止在独立块中重复)
                processed.add(key);
            }
        };

        // 提取新 2x2 矩阵(四个角)
        const safeGet = (r: number, c: number): MatrixValue =>
            r < rows && c < cols ? input[r][c] : null;

        const newMatrix: MatrixValue[][] = [
            [safeGet(minRow, minCol), safeGet(minRow, maxCol + 1)],
            [safeGet(maxRow + 1, minCol), safeGet(maxRow + 1, maxCol + 1)]
        ];
        return { fullRangeKeys, newMatrix }
    }

    // ===== 第一阶段:处理含 null 的合并组 =====
    for (const startKey in blocks) {
        if (processed.has(startKey) || !hasNull(blocks[startKey])) continue;

        const visited = new Set<string>();
        const queue: string[] = [startKey];
        visited.add(startKey);
        processed.add(startKey);

        while (queue.length > 0) {
            const key = queue.shift()!;
            const { row, col } = positions[key];
            const block = blocks[key];
            const [a, b] = block[0];
            const [c, d] = block[1];

            const leftKey = col > 0 ? `${String.fromCharCode(64 + col)}${row + 1}` : null;
            const rightKey = col < cols - 2 ? `${String.fromCharCode(66 + col)}${row + 1}` : null;

            // 先横向合并,如果符合要求就跳出循环

            // 规则1: 第一个元素 null → 上方第三个 或 左边第二个
            if (a === null) {
                if (leftKey && blocks[leftKey] && !visited.has(leftKey) && blocks[leftKey][0][1] === null) {
                    visited.add(leftKey);
                    queue.push(leftKey);
                    processed.add(leftKey);
                }
            }

            // 规则2: 第二个元素 null → 上方第四个 或 右边第一个
            if (b === null) {
                if (rightKey && blocks[rightKey] && !visited.has(rightKey) && blocks[rightKey][0][0] === null) {
                    visited.add(rightKey);
                    queue.push(rightKey);
                    processed.add(rightKey);
                }
            }

            // 规则3: 第三个元素 null → 下方第一个 或 左边第四个
            if (c === null) {
                if (leftKey && blocks[leftKey] && !visited.has(leftKey) && blocks[leftKey][1][1] === null) {
                    visited.add(leftKey);
                    queue.push(leftKey);
                    processed.add(leftKey);
                }
            }

            // 规则4: 第四个元素 null → 下方第二个 或 右边第三个
            if (d === null) {
                if (rightKey && blocks[rightKey] && !visited.has(rightKey) && blocks[rightKey][1][0] === null) {
                    visited.add(rightKey);
                    queue.push(rightKey);
                    processed.add(rightKey);
                };
            }
        }
        if (visited.size === 1) queue.push(startKey);
        if (!getAllBlockNames(visited).newMatrix.flat().every(item => item !== null)) {
            while (queue.length > 0) {
                const key = queue.shift()!;
                const { row, col } = positions[key];
                const block = blocks[key];
                const [a, b] = block[0];
                const [c, d] = block[1];

                const up

更多关于HarmonyOS鸿蒙Next中(WEB CAD online)在线CAD图纸表格提取到excel的实战教程也可以访问 https://www.itying.com/category-93-b0.html

2 回复

在HarmonyOS鸿蒙Next中,通过WEB CAD online实现在线CAD图纸表格提取到Excel,需使用鸿蒙ArkTS/ArkUI开发。利用Canvas组件解析CAD图纸,通过Web组件加载在线CAD服务,结合鸿蒙文件管理API读取图纸数据。表格提取需借助鸿蒙的图形识别能力或集成第三方解析库,将识别数据转换为结构化格式,最后使用Sheet组件或调用系统服务导出为Excel文件。整个过程需在鸿蒙分布式框架下实现跨端协同。

更多关于HarmonyOS鸿蒙Next中(WEB CAD online)在线CAD图纸表格提取到excel的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


这篇帖子详细介绍了在HarmonyOS Next环境下,基于MxCAD库实现Web端CAD图纸表格智能识别与Excel导出的完整技术方案。作者通过交互式区域选择、图形元素识别、表格结构重建、智能单元格合并及内容提取导出五大核心功能,构建了一套高效的数据提取流程。

技术实现上,利用MxCADSelectionSet进行实体获取与预处理,通过递归遍历块定义确保嵌套实体坐标还原。表格结构重建阶段,通过水平/垂直线分类、交点提取与去重,构建初始单元格矩阵。智能合并机制采用横向优先、纵向补充的两阶段策略,基于2×2子矩阵的广度优先搜索实现跨单元格识别。

内容提取阶段,通过几何中心匹配将文本实体与单元格关联,最终借助ExcelJS库实现带样式导出的Excel文件生成。该方案在复杂表格结构(如合并单元格)处理上表现稳健,为HarmonyOS Next生态下的CAD数据处理提供了可复用的工程实践参考。

回到顶部