cad.net 获取cad和dwg的版本号

版本号工具

发现不少人组合了"大版本.小版本"然后判断字符串,
虽然效率上面不差多少,但是不太考究细节是不对的.(因为我变强了,要节约内存了注重效率了)
1,有序数组比起hashmap来说占用内存少,
并且序列化和反序列化也是有序的,能够直接载入即用.
2,数据太少会是全量缓存,二分法此时运行是很快的.
3,遇到了小数点问题,通过位移拆开它们到int32的高16bit和低16bit,
高位越大,值也是越大的,所以仍然是原有顺序的(需要考虑符号位),这不就是所谓的定点数嘛.
4,三个数组可以序列化成config,交给外部来维护,不过我不太建议.

工具类

public static class VersionTool {

// 年号.
// 1,不用存年号数组,索引+2000就是年号了,
// 2,官方是Acad2000i,用2001年顶替
// 3,官方没有Acad2003,但要满足1的规则,全部数组用上年数据补齐03年.
// static readonly int[] _years =>[
// 2000, 2001, 2002, 2003/*虚拟03年*/,
// 2004, 2005, 2006, 
// 2007, 2008, 2009, 
// 2010, 2011, 2012, 
// 2013, 2014, 2015, 2016, 2017, 
// 2018, 2019, 2020, 2021, 2022, 
// 2023, 2024, 2025];
// 字段:public static readonly int[] Years;
// 构造:Years = _years;
// 异或判断数值是否一致
// Debug.Assert((_years.Length ^ DwgVers.Length ^ AcadVers.Length) == AcadVers.Length, "怎么长度不一样了捏?");

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int IndexToYear(int index){
    int year = index + 2000;
    if (year < 2000) return -1;
    if (year > 2000 + _acadVers.Length) return -1;
    return year;
}
// 和二分法一样,
// 没有命中时将第一个大于搜索值的索引作为返回值并取反,
// 以提供没有命中的信息(负数)和最近值取反后再插入.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int YearToIndex(int year){
    int index = year - 2000;
    if (index < 0) return ~0;
    if (index > _acadVers.Length - 1)
        return ~(_acadVers.Length - 1);
    return index;
}

// https://www.autodesk.com.cn/support/technical/article/caas/sfdcarticles/sfdcarticles/CHS/drawing-version-codes-for-autocad.html
// DWG签名的AC号.
static readonly int[] _dwgVers =>[
1015, 1015, 1015, 1015/*补齐03年*/,
1018, 1018, 1018, 
1021, 1021, 1021,
1024, 1024, 1024, 
1027, 1027, 1027, 1027, 1027, 
1032, 1032, 1032, 1032, 1032, 
1032, 1032, 1032];

// CAD版本号,它与注册表和COM相关.
static readonly string[] _acadVers =>[
"R15.0", "R15.1", "R15.2", "R15.2"/*补齐03年*/,
"R16.0", "R16.1", "R16.2", 
"R17.0", "R17.1", "R17.2", 
"R18.0", "R18.1", "R18.2", 
"R19.0", "R19.1", "R20.0", "R20.1", "R21.0", 
"R22.0", "R23.0", "R23.1", "R24.0", "R24.1", 
"R24.2", "R24.3", "R25.0"];


// 运行中的CAD版本号,位移+位或.
public static readonly int AcadVerOR;
// 这几个只读数组,有序等长,通过二分法共同索引.
public static readonly int[] AcadVers;
public static readonly int[] DwgVers;

// 静态构造函数
static VersionTool() { 
    int major = Acap.Version.Major;
    int minor = Acap.Version.Minor;
    // Q16.16定点数:0.5*2^16来得到小数部分
    var fp = minor * 0.1f * (1 << 16);
    AcadVerOR = (int)( ((uint)major << 16) | (short)fp );
   

    // 消除字符串从而不需要构造hashmap.
    // 位移+位或,使得它们从小数映射到整数,
    // 并且保持大小关系,仍然有序可以能够二分.
    // 编译时:C#没有循环消除,反编译源码还能看见,
    // 运行时:避免求一次循环,可以把结果再重新粘贴回代码.
    // --就像快速平方根倒数,夹逼求浮点数,再转16进制,然后粘贴到代码上,成为魔法数
    int[] v = new int[_acadVers.Length];
    for(int i = 0; i < _acadVers.Length; i++){
        var s = _acadVers[i];
        int m = (s[1] - '0')*10 + (s[2] - '0');
        int n = (s[4] - '0'); //0是R,3是小数点,所以跳到4
        var f = n * 0.1f * (1 << 16);
        v[i] = (int)( ((uint)m << 16) | (short)f );
    }
    AcadVers = v;
    _acadVers = null!; //积极释放
    DwgVers = _dwgVers;

#if DEBUG
    // 防止开发者忘记更新数组,提供一个断言来进行维护
    int index = Array.BinarySearch(AcadVers, AcadVerOR);
    Debug.Assert(index > 0, "运行中的CAD版本未记录,请维护DWG版本号/CAD版本号");
    Debug.Assert(DwgVers.Length == AcadVers.Length, "怎么长度不一样了捏");
    // 解码版本号
    int major2 = AcadVerOR >> 16;
    int minor2 = AcadVerOR & 0xFFFF;//位与掩码,获取低16bit
#endif
}

}

二分法找共同索引例子

根据微软介绍,没有命中的话,返回值负数是有意义的,
取反之后就是最近值(第一个比它大,所以可以直接insert).
Array.BinarySearch

int yearToFind = 2015;
int index = VersionTool.YearToIndex(yearToFind);
if (index < 0) {
    Console.WriteLine($"最近CAD版本:{VersionTool.AcadVers[~index]}");
    return;
}
Console.WriteLine($"索引:{index}.");
Console.WriteLine($"年份:{VersionTool.IndexToYear(index)}.");
Console.WriteLine($"CAD版本:{VersionTool.AcadVers[index]}");
Console.WriteLine($"DWG版本:{VersionTool.DwgVers[index]}");

获取DWG文件版本

模拟打开DWG文件.
文件类型不要根据文件后缀名判断,那是给用户看的,要根据文件签名判断
调用时候都要try一层,因为win有磁盘权限/管理员权限/只读文件属性/局域网共享中断等各种意想不到的情况

如何知道文件已经被其他程序打开?
根据Acad官方博客,是用Append模式打开来触发

// 调用要加using和try
public static FileStream ReadWriteDWG(string dwgFile,
    FileMode fm = FileMode.Open,
    FileAccess fa = FileAccess.Read,
    FileShare fs = FileShare.ReadWrite | FileShare.Delete) {

// 文件流
var fileStream = new FileStream(dwgFile, fm, fa, fs);

// 通过文件签名判断是否DWG,位于前7个字节:
// https://blog.csdn.net/jiangyb999/article/details/124497625
int signature = 7;
if(fileStream.Length < signature)
    throw new FileFormatException($"惊!文件签名损坏:{dwgFile}");

// 栈内分配
Span<byte> bomBuffer = stackalloc byte[signature];
// 堆上分配,如果Span用不了就用它
// byte[] bomBuffer = new byte[signature];

signature = fileStream.Read(bomBuffer, 0, signature);
if(signature < bomBuffer.Length)
    throw new FileFormatException($"惊!文件签名损坏:{dwgFile}");
if(!(bomBuffer[0]=='A' && bomBuffer[1]=='C' && bomBuffer[6]=='\0')) 
    throw new FileFormatException($"惊!文件类型不是DWG:{dwgFile}"); 

// "AC1015\0",提取数字部分,不想把类型转去string再转回int,直接展开写.
int dn = (bomBuffer[2] - '0') * 1000
          + (bomBuffer[3] - '0') * 100
          + (bomBuffer[4] - '0') * 10
          + (bomBuffer[5] - '0');
int index = Array.BinarySearch(VersionTool.DwgVers, dn);
if (index < 0) {
    string ver = Encoding.ASCII.GetString(bomBuffer, 0, bomBuffer.Length - 1);
    throw new FileFormatException($"惊!插件未记录版本:{ver},快联系作者");
}

// 插件已经记录,但是此时运行在低版本CAD中,
// DWG版本会高于当前运行的CAD
// 坏主意:打不开就去注册表,找找有没有高版本CAD,然后让它来打开,嘻嘻...
if (VersionTool.AcadVers[index] > VersionTool.AcadVerOR) {
    throw new FileFormatException("惊!此DWG高于当前CAD版本");
}
return fileStream;

}

相关链接

e大博客收集的CAD版本号
https://www.cnblogs.com/edata/p/10802746.html

我在COM文章收集了注册表中全部的CAD(我之前的代码好像惨不忍睹)
https://www.cnblogs.com/JJBox/p/11381254.html

枚举类型,按位运算的原理
https://www.cnblogs.com/JJBox/p/12033641.html
(完)

posted @ 2024-10-28 22:26  惊惊  阅读(181)  评论(0编辑  收藏  举报