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
(完)