Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .agents/features/cad-exchange.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,13 @@ covers: src/Import/CadExchange.*, src/Import/AcisReader.*, src/Serialization/Dwg
- 3DSOLID / REGION / BODY / 各类曲面(`Dwg::IsAcisEntity`):`Importer::ConvertAcis` 经 `Dwg::DecodeAcisData` 取出 ACIS(R2013 起查 `Dwg::ReadAcisDataStore`),`Import::ReadAcis` 读成 B-rep,用 `SetCadImportSolidKernel` 设的内核 `FromBrep` 建成 `Solid3DEntity`;没有内核或建不出时只显示边线框,结束时按类型 `LOG_WARN`。格式与限制见 `docs/Solid/PLAN.md` 第 16 节。导出:`Solid3DEntity` 写成多面网格(只有边线、没有网格的跳过并提示);原图未改动的实体在同格式同版本另存时原样写回
- 不支持的类型跳过并按类型统计,导入结束时 `LOG_WARN`(读入 `m_skipped`;导出时不能写的实体经 `Skip` 同样统计)。改映射前先用 skill `drawing-debug` 看原图里有哪些类型
- OLE2FRAME(嵌入的 Word / Excel 等)只导入显示:MiniDWG 原样保留,`Dwg::DecodeOle2Frame` 解出数据,`src/Core/Ole/OleObject` 解析 AutoCAD 头与 OLE 复合文档里的展示图(WMF / EMF / DIB),显示为 `OleFrameEntity`;图元文件经 `AppPlatform::RasterizeOle` 光栅化(桌面版 GDI,网页版只画边框)
- 其他软件的自定义实体(`SWR_*`)没有专门适配:带代理图形的经 `Tch::DecodeProxyGraphics` 按代理图形显示(图层 / 线型序号与文字字体经 `Importer::ProxyContext` 映射,格式与支持的记录见 `docs/evidence/Tch-proxy-graphics.md`),结束时按类型 `LOG_WARN` "显示为普通几何";`SWR_BLOCK_INSERT` 的代理为空,按块参照前缀解(`Tch::DecodeBlockInsert`)。代理图形里不支持或数据不合理的记录只跳过那一条(`TchProxyGraphics::Skipped`),记录头损坏、有裁剪 / 变换矩阵记录或一个图元都没有时整个对象不显示,看 `m_tchFailures` 打出的原因;`MINICAD_PROXYEXT=SWR_*` 列出各对象用到的记录类型
- 其他软件的自定义实体(`SWR_*`)没有专门适配:带代理图形的经 `Proxy::Decode`(`src/Import/ProxyGraphics.h`,命名空间 `MiniCAD::Proxy`)按代理图形显示(图层 / 线型序号与文字字体经 `Proxy::ProxyGraphicsContext` 映射,格式与支持的记录见 `docs/evidence/Tch-proxy-graphics.md`),结束时按类型 `LOG_WARN` "显示为普通几何";`SWR_BLOCK_INSERT` 的代理为空,按块参照前缀解(`Tch::DecodeBlockInsert`)。`MINICAD_PROXYEXT=SWR_*` 列出各对象用到的记录类型
- 代理图形解码器是**厂商无关**的:只解记录流,不看对象类名。天正、斯维尔、浩辰、建筑结构设计工具(`ASDT_*`)等写进来的都是同一套记录格式,因此走同一个函数
- **容错按「能解多少算多少」**:不认识的记录、数值不合理的记录只**跳过该条**(`ProxyGraphics::Skipped` 计数),其它已识别的记录照常输出,不再整份放弃;坐标超出 `|v| < 1e12` 的图元直接丢弃(`Discarded` 计数),防止少量脏数据把包围盒撑到 1e70 量级、导致"全部显示"时整张图缩成一个点。一条都解不出时才返回 false,看 `m_tchFailures` 打出的原因(含跳过 / 丢弃条数)
- 支持记录类型:`1`(包围盒)`2/3`(圆)`4/5`(弧)`6/7/32`(折线 / 多边形 / 带法向)`9`(shell)`10/11/36/38`(文字)`14/16/18/19/20/22/23/24/25`(颜色 / 图层 / 线型 / 标记 / 填充 / 线宽 / 线型比例 / 厚度)`33`(内嵌 LWPOLYLINE,位字段编码,payload 前 4 字节是长度前缀)
- **位流的位序是 MSB-first**(先取字节最高位);用 LSB-first 解 `33` 会得到 `flags=0x1FE4` 这类垃圾值,进而算出天文数字坐标。`DD`(带默认值的 double)是**字节差分编码**(`00` 用默认值、`01` 覆盖低 4 字节、`10` 覆盖偏移 4-5 加低 4 字节、`11` 完整 8 字节),不是"沿用旧值"
- `22` 颜色记录的 4 字节按 **[method, r, g, b]** 存放,不是小端 u32:实测句柄 `368F5` 的 `c2 00 00 00` = 真彩 RGB(0,0,0) 黑框、`c3 00 00 07` = ACI 7 白字。按 `value >> 24` 取 method 会得到 0,两条都落进 ByLayer 兜底,黑框就画成了背景色(浅色主题下是白块)盖住文字
- `fill`(记录 `20`,`1` = 总是填充、`2` = 从不)作用在闭合图元上时,**既出实心填充、也出轮廓线**。只出填充不加轮廓,会让文字的背景框渲染成一个没有边框的纯色块
- 天正对象见 [tianzheng](tianzheng.md),外部参照见 [xref](xref.md),图纸空间视口见 [layout-viewport](layout-viewport.md)

## 以原图为底另存(`CadSource`)
Expand Down
10 changes: 8 additions & 2 deletions .agents/features/tch.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ covers: src/Import/Tch*, tests/Core/Tch*, tests/Core/TianzhengWallTests.cpp
| `TchText.h` | TCH文字的样式规则(文字样式上 `_TCH` 扩展数据给出西文字高与宽度系数) |
| `TchShape.h`、`TchPathArray.h` | TCH矩形;线图案(沿路径重复排列图案块) |
| `TchPipe.h`、`TchElec.h`、`TchHvac.h` | 给排水管线;电气桥架与配件、折断线;暖通风管、管件(法兰、变径、弯头、三通、四通、乙字弯)与风管标注 |
| `TchBlockInsert.h`、`TchProxyGraphics.h` | TCH块参照的标准几何前缀(`SWR_BLOCK_INSERT` 布局相同);代理图形记录(也用于其他带代理图形的自定义实体,见 `docs/evidence/Tch-proxy-graphics.md`;TCH另存为代理实体的图纸里图层、线型序号多为 -1,按对象自身的处理;不支持或数据不合理的记录只跳过那一条,裁剪 / 变换矩阵记录整个放弃) |
| `TchBlockInsert.h` | TCH块参照的标准几何前缀(`SWR_BLOCK_INSERT` 布局相同) |
| `TchProxyGraphics.h` | 天正这一侧的代理图形入口:`Tch::DecodeProxyGraphics` 是通用解码器 `Proxy::Decode`(`src/Import/ProxyGraphics.h`)的薄封装,只做类型别名与转发。代理图形本身与厂商无关(也用于其他带代理图形的自定义实体,见 `docs/evidence/Tch-proxy-graphics.md`;TCH另存为代理实体的图纸里图层、线型序号多为 -1,按对象自身的处理;不支持或数据不合理的记录只跳过那一条,裁剪 / 变换矩阵记录整个放弃) |

## 约束

Expand All @@ -36,9 +37,14 @@ covers: src/Import/Tch*, tests/Core/Tch*, tests/Core/TianzhengWallTests.cpp
- **新版(T30,TCH基类版本 9~14)**:各类的版本号与明文版本的对应写在 `Read*Fields` / `Decode*Cipher` 上方的注释里。自带 RC 版本与密钥、第二个密钥为 0 的子层用 `Tch::Detail::CipherLayer`(表格子对象、屋顶基类、管线接口、标注样式、管件基类的列表)。TCH基类里的属性包值与文字方向记在 `Tch::Detail::CipherReader::FieldStrings` / `Tch::Detail::CipherReader::FieldNumbers` / `Tch::Detail::CipherReader::Angle`(立管、多管标注要用)。机电:管线节点基类没有加密层(`Tch::Detail::ReadCipherPipeNode`);属性表移进了TCH基类(`Tch::Detail::CipherReader::XData`),管线基类版本 4 在末尾另存 Start、End(`Tch::Detail::ReadCipherPipeBase`)
- **DWG2004**:没有单独的文字流,GUID、属性名与密文都是主数据流里的 GBK 文字;密文字符在 GBK 里有的存为双字节,没有的写成 `\U+XXXX`,`Tch::Detail::CipherReader` 取各字符的低字节解密,与 2007 起 UTF-16 的做法相同。实物图纸(中望看图另存)里坐标标注读出的格式 48、方向 3 超出已核对的范围,仍跳过。明文机电对象里只有管线、管件、管线文字(`Tch::DecodePipe`、`Tch::DecodePipeFitting`、`Tch::DecodePipeText`,`Tch::Detail::CheckPayload` 的 `inlineText`)按内嵌字符串读 2004 格式,其余给排水类仍要求 2007 起
- **暖通**:风管与管件的几何都在属性表里(`Tch::ReadCableTrayMap` / `Tch::ReadCableFittingMap`,明文管件的属性表后是边界列表(每条带若干段)与连接数,与风管相同)。管件按属性表的键分类型(`Tch::DecodeHvacFitting`):三通 I4 为支管样式(1 直、3 弧形、5 Y 形、7 Y 形而对侧为直边),四通有 Pt4 / Pt5,乙字弯有 R28、R34 而无 Vt6(R2 为偏移,竖向偏移画直管加 Z 形符号),分节弯头的节数 I5 ≥ 2 时取 I5、否则 I4 − 1。风管标注 `TCH_HVACDIMENSION` 是TCH基类 + BS 版本 1 + 属性表(`Tch::DecodeHvacDimension`):S1 为“|”分隔的显示文字,I4 = 0 时文字居中在 Pt6,I4 = 4 时引线到 Pt7 再画两行两列的表格;天正按字形实际宽度定列宽,这里是估算(误差约 1 个字宽)。以上对照 2004 格式图纸的天正分解核对;分解结果会被重叠对象裁掉一部分,比对时只看“参照有而 MiniCAD 没有”的部分
- **TCH基类版本7**(ZW看图另存为2018格式):`ReadTchBase` 在版本 5 的字段后读属性表(键在文字流、值在主数据流,`Tch::Detail::SkipXDataMap`),两个句柄个数分别多存了 5、3;之后接旧版明文的各类字段(柱 6、门窗 5 等)。属性表要从头读文字流,所以 `ReadTchBase` 要传 `data`,只适用于对象自身的基类(组合门窗的子门窗不传,版本 7 按不支持)。墙版本 10(`Tch::Detail::DecodeWallFields`):受保护几何同标记 7 先互换半字节再异或密钥,墙高在左右宽之前,结尾标记后多 13 个 0 位(含义未核对,只接受不超过 16 位且全为 0)。坐标标注版本 6(格式 48、方向 3)仍跳过
- **TCH基类版本6~8(天正 T20,t20V2~V7 的图纸)**:`ReadTchBase` 在版本 5 的字段后读属性表(键在文字流、值在主数据流,`Tch::Detail::SkipXDataMap`),之后接旧版明文的各类字段;要传 `data`。三者的主数据流除首字节版本号外布局相同,差别是:版本 **6 的句柄个数不减偏量**、7/8 各减 5/3;版本 **8 在属性表后多一个内联 GUID 变长字符串**(`ReadVariableText`),跳过它各类字段才对得上。组合门窗的子门窗、表格 `DecodeSheet` 等也要给 `ReadTchBase` 传 `data`。墙受保护几何的类版本随基类变:6→9、7→10、8→11。三者解密 / 字段顺序已按 t20V2~V7 的实物图纸核对:**版本 9 只异或密钥、不换半字节,墙高在左右宽之后,且部分墙收尾标记后多 ≤16 个全零位**;**10/11 先互换半字节再异或,墙高在左右宽之前**(10 的尾部 13 个 0 位照旧)。版本 11(base 8)在受保护几何之后、门窗段之前多 4 个字节标记(实测 76 36 76 76 一类),跳过它门窗前缀才读得到 0。墙 `DecodeWallFields` 的 `t20Base` 按 `Main[0]` 在 6~8 判断。各类的字段版本随 T20 升级:`TCH_COORD` 版本 6 在 `LineLength` 后追加「字节 + 二维点 + 内联文字」(与 `DecodeCoordCipher` 尾部一致);`TCH_RECTSTAIR` 版本 5/6 末尾多一个 `BitShort`(与 `DecodeRectStairCipher` 的 `f.Short()` 一致);`TCH_NORTHTHUMB` 部分 T20 版本主数据流末尾补 ≤34 个全零位,按全零尾部接受。`TCH_TEXT` / `TCH_MTEXT` 加密版(`DecodeTextCipher`)的类版本 3(T20 V8)句柄流只有 TextStyle 一个、版本 4 起末尾另有两个句柄,按类版本区分读取;V8 的文字因此能全部解析。索引符号 `TCH_INDEXPOINTER`:AC1018 的文字内联在主数据流里,字段顺序与加密版 `DecodeIndexPointerCipher` 一致——4 段文字(编号 / 图号 / 上旁注 / 下旁注)后按类版本分支:**版本 1/2 即结束**(版本 2 多 2 字节),**版本 3/4** 再接两张字符串表、2 个 BD + 字节、多段线、收尾 BD 与内联文字;以前把两段文字当列表计数、又把余下文字放到单独文字流里,主数据流少读约 330 位。坐标标注 `TCH_COORD` 的类字段按版本递增:1 只有点 + 值 4 个 RD,2 补引线点 2 个 RD,3 再补一个字节,4 起才有 BD `TextGap`,5 补 `LineLength`,6 再追加「字节 + 二维点 + 内联文字」;旧版(基类 2~4)因此能全部解析。**版本 1 没有引线点字段**,`BuildCoord` 按天正的默认规则补出:从标注点沿 `(-4.2894*字高缩放, +4.5069*字高缩放)`(`Side` 决定左右镜像)引到水平线起点——这组倍率取自 t8/t10/t20V5 里存了引线点的同位置标注(三者值一致),补出的斜引线与文字位置与那些版本逐项相同。旧版(基类 2~4)的箭头 `TCH_ARROW` 与矩形楼梯 `TCH_RECTSTAIR` 也已按类版本核对:箭头类版本 2 在 `ArrowLength` 后即结束、3 多一个 Head 字节、4/5 才接完整尾部;楼梯类版本 1 尾部是「1 字节 + 1 个 BD + 2 位」且句柄流更短、3 读到「版本 ≥ 3」的 2 BD + 字节为止、4 起才读完整字段。旧版墙(基类 2/3/4,t5/t6/t7)没有受保护的 84 字节块,几何是主数据流里的**明文位级 BD 字段**:`ReadTchBase` 后 +8 位读 7 个 BD(起点 XYZ、终点 XYZ、曲线参数),此后相对 +12 / +78 / +144 位是三个明文 RD(左宽、右宽、墙高,实测 120 / 120 / 3000),三个版本的相对偏移一致;`DecodeOldWall` 按这些偏移读并用 `CheckWall` 数值校验兜底。旧版标注 `TCH_DIMENSION`(区别于 `TCH_DIMENSION2`)与后者字段序列相同、只是类版本号为 3,`DecodeDimension` 放行该 DxfName 与版本 1~3 即可复用(`Exact()` 校验通过,实测位置 / 角度 / 跨数对齐)
- **老虎窗 `TCH_DORMER`**(`TchDormer.h`):代理图形是空的(只有 8 字节头、0 条记录),几何必须从私有数据解;两个家族字段语义一致但编码不同。明文(TCH基类 3~8,t6~t20V7):`ReadTchBase` + version(2) + `BS`(1) + 10 个 BD 参数 + 6 个 BD(插入点 X/Y/Z,然后**屋面法向** X/Y/Z)。加密(基类 9~14,原始图 / t20V8 / V9):`CipherReader::ReadBase` + version(3) + `ReadKey` + 11 个 `Double`(首值 1,其余 10 个参数)+ `P3`(插入点,按 Key 重排为 X/Y/Z)+ `P3`(屋面法向)。各版本这 10 个参数实测完全相同(200, 200, 0.5, 450, 200, 1500, 180, 1800, 1300, 1400),插入点 X=61689.23 / Y=29421.88 / Z=744.23、法向 (-0.7656, 0, 0.6433)(模长 1)也一致。**平面朝向取法向在 XY 的方位角 `atan2(NY, NX)`**:实测该值(180°)让老虎窗背靠最近的竖直墙(靠墙角距墙 66、背离侧 1466),与图纸布局自洽;早前把法向的 X 分量(-0.7656)当旋转角用,方向差约 136°,已修正。`BuildDormer` 按「+u 坡向 / +v 墙面方向」画矩形窗洞 + 中脊线 + 两条坡屋面线(伸出量按坡度 `Params[2]`),参数几何含义仍未与 CAD 逐项核对
- **房间 `TCH_SPACE`**(`TchSpace.h`):代理图形为空(8 字节头、0 条记录),几何是私有数据里的轮廓。加密对象(基类 9~14):`ReadBase` + `version` + `ReadKey` 之后有一个 `BS` 顶点数,然后是顶点数组——每个顶点是两个明文 RD(X、Y),组内共 130 位(两个 RD 加 2 位)。顶点数组结束位恰好落在主数据流末尾,据此在 `afterKey` 之后扫描定位数组起点(要求数组末尾对齐 `MainBits`),8 个顶点的房间轮廓(实测 `33559.67/24669.33` 起)画成闭合多段线。其余字段(房间名等)未核对,只画轮廓
- **曲面网格 `TCH_CURVEMESH2`**(`TchCurveMesh.h`):代理图形为空。两个家族——加密(基类 9~14,原始图)与明文(基类 2~8,t7);顶点都是明文 BD 三点组(X、Y、Z)。实现用「找最长的连续合理三点组」定位顶点数组(扫描窗口 640 位),再按顺序连成折线显示;X 或 Y 允许为 0(网格边界顶点)
- **多版本核对**:`tools/tch_versions_check.ps1` 用 `MiniCADDwgInspect` 批量扫描一组不同天正版本另存的同一张图(`-Dir` 传目录,本机图纸不入库),统计各文件「显示为普通几何 / 轮廓」(解析成功)与「无法恢复显示」的类型数,低于阈值报错。测试 exe 里直接 `ImportCad` 这些图会因栈溢出崩溃,用独立的 `MiniCADDwgInspect` 进程核对
- **新版未核对**(只有合成样例,没有实物图纸对照):屋顶、曲线楼梯、剖切线、双跑楼梯、阳台;自动扶梯(第三方查看器画两道、MiniCAD 一道);设备 / 井 / 水泵的图块、多管标注、版本 4 的桥架与风管、桥架配件与风管管件(井标注能显示井的标高,说明井按新版布局读对了)。未支持:带形窗(与转角窗同为 `TCH_CORNER_WINDOW`);修订标记另存的多段线不画
- **剖切符号**:旧样式 `Tch::BuildSection`,新国标样式 `Tch::BuildSectionNewStyle`;样式开关在 `Tch::Detail::CipherReader::XData` 里,旧版明文对象一律旧样式
- **半径 / 直径标注 `TCH_RADIUSDIM`**(`Tch::BuildRadiusDim`):天正用同一个 `TDbRadiusDim` 类承载半径与直径两种标注,靠 `Flags` 的 `0x10` 位区分。**直径**(置位)按天正默认样式画:一条直径线贯穿圆心、两端各一个箭头指向圆周内壁、文字(`⌀` + 两倍半径,如 `⌀1500`)水平放在圆心上方的线中央;**半径**(未置位)则是箭头尖在圆周、沿半径方向引线、文字写 `R` + 半径。直径线的方向取自存盘的 `Center→Tip` 方向(实测与水平差 0.41°,属数据本身,不强行拉平)。以前一律按半径画并写 `R`,直径标注会被画成半径
- **柱、幕墙**:柱 `Tch::Detail::DecodeColumnCipher`(版本 7~10,版本 ≥ 9 的出图比例取对象自己的);矩形 `Tch::Detail::DecodeRectCipher`(版本 3);幕墙在 `Tch::Detail::DecodeWallCipher` 的墙字段之后读幕墙版本 3,分格列表是否存在看墙标志 0x100 / 0x10000(旧版明文同样,样例两位都有,没有实物核对)
- **影响显示的开关**(新旧版同读):折断线按 `TchRupture::Count` 等分、0 不画;扶手 `TchHandRail::Align` 为 1 / 2 时路径是一侧边线,`TchHandRail::Draw2d` 为假不画;阳台 `TchBalcony::NoRailEdges` 置位的段(靠墙)不画,内缘在该段不偏移(`Tch::Detail::OffsetPathEdges`);转角窗两段各按 `TchCornerWindow::Thickness` / `TchCornerWindow::Thickness2` 向自身方向右侧偏移;自动扶梯与多跑梯标志位 10 清零时不画箭头(`TchAutoStair::Flags`);设备 Y 比例、阀门 Y 比例与象限(1 X 向镜像、2 两向、3 Y 向镜像),Y 比例近 0 时取 X 比例。读了仍没用的:修订版次文字、柱标注文字与保温层、双线管法兰、弧形平台与外侧栏杆、幕墙分格线、设备系统图块、转角窗文字转角
- **对称轴**:`Tch::BuildSymmetry`,两端各两条横线,距端点 Gap、Gap + Spacing(乘比例)
Expand Down
5 changes: 3 additions & 2 deletions src/Core/Entity/EntityColor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,9 @@ namespace MiniCAD
if (Method != ColorMethod::ByRgb) return Rgba;
auto pure = [](const Math::Color4& c, double v)
{
auto near = [v](double x) { return x > v - 0.004 && x < v + 0.004; };
return near(c.r) && near(c.g) && near(c.b);
// 变量不能叫 near:Windows 头文件里有同名宏,包含它之后会被展开成语法错误
auto isClose = [v](double x) { return x > v - 0.004 && x < v + 0.004; };
return isClose(c.r) && isClose(c.g) && isClose(c.b);
};
if (pure(Rgba, 1.0)) return { 1.0 - kTrueColorInset, 1.0 - kTrueColorInset, 1.0 - kTrueColorInset, Rgba.a };
if (pure(Rgba, 0.0)) return { kTrueColorInset, kTrueColorInset, kTrueColorInset, Rgba.a };
Expand Down
Loading
Loading