ObjectARX 2025(C++)实现固化标注文字和标注恢复原值

最近更新于 2024-09-02 22:55

Table of Contents

需求

在制作机加工工艺的时候,完成绘图和标注后需要装进作业指导书(图框中),可能需要对图形进行缩放操作。由于 CAD 默认的标注是实体尺寸驱动的,在缩放后尺寸会发生变化,导致标注也跟随实际尺寸变化。因此需要将标注固化下来,让标注内容不再跟随实体尺寸变化。
我个人习惯在做加工工艺的时候,不去改动图形,而是缩放图框去适配图形,这个需求对我的影响就不是很大(不过有些同事是缩放图形适配图框的)。主要还是后期我计划做一个尺寸提取的,在尺寸提取之前可以先固化标注,这样就可以直接读取尺寸标注了。

技术层面实现:
CAD 里的默认标注对象显示的尺寸是由两部分属性决定的,一个是测量单位,这个值就是标注对象的实体尺寸值,跟随标注对象图形变化而变化,另一个是文字替代,文字替代空着就显示测量单位的值,当手动添加了直径、角度符号以及公差等后,就会呈现在文字替代里,此时标注显示的就是文字替代里的内容,默认测量单位会以<>符号呈现在文字替代中,标注显示的时候文字替代中的<>符号会自动赋值为测量单位的值。
可以参考获取尺寸标注内容,有图例更好理解尺寸标注的内容组成。
固化标注其实就是把文字替代里的<>直接改为测量单位的数值(测量单位的数值要先按照标注对象的主单位精度取小数位数),这样文字替代中不存在<>符号,标注内容就不会跟随实体尺寸变化了。而恢复原值,就是把文字替代的内容删掉,这样标注就会显示测量单位的值。不过如果标注存在自行添加的符号、公差等内容也会一并删掉,这个暂时没有可行思路实现保留文字替代其它内容的同时恢复标注的实体尺寸驱动性。

可能存在的 BUG

实体尺寸精度和标注精度不一致,且标注精度低于实体精度时的四舍五入处理可能存在问题。
比如绘制了一条 10.445 的直线,10.445 在计算机中以二进制存储,而 10.445 转为二进制是无限小数,但是计算机保存的位数是有限的,则超出存储的位数就丢失了,这样实际存储的二进制值只是十进制值的近似值,再转回十进制并不是 10.445,而可能是 10.4449999999…
如果我对这个 10.445 的实体尺寸取两位小数,由于内部存储的是 10.444999…,两位小数参考第三位是4,所以得到的结果就会是 10.44,而不是 10.45。
我考虑到造成精度损失的情况是十进制转二进制是无限小数,而实际存储有限位,所以发生精度损失就是计算机存储的值比设置的值小,所以四舍五入的时候我是人为在保留小数的两位后位置人为加 1 补偿。这个方法是没有数学证明是否正确的,只是我测试了有限个数据可行的情况下采用的。
这里的四舍五入其实也是为了应对意外情况,其实正常绘图,绘制的精度和标注的精度应该是一致的,并不需要再次精确小数。

演示

file

file

file

file

file

环境

AutoCAD 机械版 2025
ObjectARX 2025
VS 2022

环境配置参考:https://blog.iyatt.com/?p=16480
自定义类使用方法参考:https://blog.iyatt.com/?p=16635
本文描述的功能将集成在 DimensionTools 工具中:https://github.com/IYATT-yx/DimensionTools ,并更新改进

实现

DimControl.hpp

#pragma once

class DimControl
{
public:
    static void init();
    static void unload();

private:
    typedef void (*CallbackFun)(AcDbDimension*); // 定义回调函数类型

    /**
     * @brief 【回调函数】将数值标注转换为文字标注
     * @param pDimension 标注对象
     */
    static void cbDimNumToText(AcDbDimension* pDimension);

    /**
     * @brief 【回调函数】将文字标注转换为数值标注
     * @param pDimension 标注对象
     */
    static void cbResetDim(AcDbDimension* pDimension);

    /**
     * @brief 遍历选择集修改标注
     * @param fun 【回调函数】对标注修改的实现
     */
    static void selectSetForeach(CallbackFun pFun);

    /**
     * @brief 设置精度,并转为字符串
     * @param sMeasureValue 输出转换精度的字符串
     * @param value 输入数据
     * @param precision 设置精度
     * @param isAugular 是否为角度值
     */
    static void setPrecision(std::wstring& sMeasureValue, double& value, int& precision, bool isAugular = false);

    /**
     * @brief 将测量值插入文字替代中
     * @param sMeasureValue 测量值
     * @param sText 文字替代
     * @return 是否处理(纯文字标注不做处理)
     */
    static bool insertValue(std::wstring& sMeasureValue, std::wstring& sText);

    /**
     * @brief 锁定标注
     */
    static void lockDim();

    /**
     * @brief 恢复标注
     */
    static void resetDim();
};

DimControl.cpp

#include "StdAfx.h"
#include "DimControl.hpp"

constexpr double PI = 3.14159265358979323846;

void DimControl::init()
{
    acedRegCmds->addCommand(L"IYATTyxDimControl", L"LD", L"LD", ACRX_CMD_MODAL, DimControl::lockDim);
    acedRegCmds->addCommand(L"IYATTyxDimControl", L"RD", L"RD", ACRX_CMD_MODAL, DimControl::resetDim);
}

void DimControl::unload()
{
    acedRegCmds->removeGroup(L"IYATTyxDimControl");
}

void DimControl::setPrecision(std::wstring& sMeasureValue, double& value, int& precision, bool isAugular)
{
    double factor = pow(10, precision);
    if (isAugular)
    {
        value = value / PI * 180; // 弧度转角度
    }
    sMeasureValue = std::to_wstring(static_cast<int>(value * factor + 0.51) / factor); // 【算法待验证】【算法待验证】【算法待验证】【算法待验证】【算法待验证】【算法待验证】【算法待验证】
    sMeasureValue.erase(sMeasureValue.find_last_not_of(L'0') + 1, std::wstring::npos); // 去除末尾多余的 0
    if (sMeasureValue.back() == L'.') // 如果最后一个字符是小数点,则删除
    {
        sMeasureValue.pop_back();
    }
}

bool DimControl::insertValue(std::wstring& sMeasureValue, std::wstring& sText)
{
    static const ACHAR* psPlaceholder = L"<>";
    static const size_t placeholderLen = wcslen(psPlaceholder);

    size_t pos = sText.find(psPlaceholder); // 查找占位符
    if (pos != std::wstring::npos)
    {
        std::wstring sNewText = sText.substr(0, pos);
        sNewText += sMeasureValue;
        sNewText += sText.substr(pos + placeholderLen);
        sText = sNewText;
        return true;
    }
    else
    {
        return false;
    }
}

void DimControl::cbDimNumToText(AcDbDimension* pDimension)
{
    static int precision; // 精度
    static double measureValue; // 测量值
    static std::wstring sMeasureValue; // 处理后的字符串测量值
    static std::wstring sText;

    sText = pDimension->dimensionText(); // 获取文字替代

    pDimension->measurement(measureValue); // 获取测量值

    if (pDimension->isA() == AcDb2LineAngularDimension::desc()) // 两线角度标注
    {
        precision = pDimension->dimadec(); // 获取角度精度
        DimControl::setPrecision(sMeasureValue, measureValue, precision, true); // 设置精度
        sMeasureValue = sMeasureValue + L"%%d";
    }
    else
    {
        precision = pDimension->dimdec(); // 获取线性精度
        DimControl::setPrecision(sMeasureValue, measureValue, precision); // 设置精度
        if (pDimension->isA() == AcDbDiametricDimension::desc()) // 直径标注
        {
            sMeasureValue = L"%%c" + sMeasureValue;
        }
        else if (pDimension->isA() == AcDbRadialDimension::desc()) // 半径标注
        {
            sMeasureValue = L"R" + sMeasureValue;
        }
        // 其它标注不做处理
    }

    if (sText.length() == 0)
    {
        pDimension->setDimensionText(sMeasureValue.c_str()); // 设置给标注对象
        acutPrintf(L"测量值:%lf,设置值:%s\n", measureValue, sMeasureValue.c_str());
    }
    else
    {
        bool status = DimControl::insertValue(sMeasureValue, sText);
        if (status)
        {
            pDimension->setDimensionText(sText.c_str()); // 设置给标注对象
            acutPrintf(L"测量值:%lf,设置值:%s\n", measureValue, sText.c_str());
        }
        else
        {
            acutPrintf(L"纯文本标注:%s,不做任何处理。\n", sText.c_str());
            return;
        }
    }
}

void DimControl::cbResetDim(AcDbDimension* pDimension)
{
    pDimension->setDimensionText(L"");
}

void DimControl::selectSetForeach(CallbackFun pFun)
{
    ads_name ss = { 0, 0 };
    if (acedSSGet(nullptr, nullptr, nullptr, nullptr, ss) != RTNORM)
    {
        acutPrintf(L"获取选择集失败。\n");
        return;
    }

    Adesk::Int32 len = 0;
    if (acedSSLength(ss, &len) != RTNORM)
    {
        acutPrintf(L"获取选择集长度失败。\n");
        return;
    }

    for (Adesk::Int32 i = 0; i < len; ++i)
    {
        ads_name name = { 0, 0 };
        acedSSName(ss, i, name);

        AcDbObjectId objId;
        acdbGetObjectId(objId, name);

        AcDbEntity* pEntity = nullptr;
        if (acdbOpenObject(pEntity, objId, AcDb::kForWrite) != Acad::eOk) // 可写方式打开
        {
            continue;
        }

        if (pEntity->isKindOf(AcDbDimension::desc()))
        {
            AcDbDimension* pDimension = AcDbDimension::cast(pEntity);
            pFun(pDimension);
        }

        pEntity->close();
    }
    acedSSFree(ss);
}

void DimControl::lockDim()
{
    DimControl::selectSetForeach(DimControl::cbDimNumToText);
}

void DimControl::resetDim()
{
    DimControl::selectSetForeach(DimControl::cbResetDim);
}
ObjectARX 2025(C++)实现固化标注文字和标注恢复原值
Scroll to top