diff --git a/SourceCode/Code2026/CoreCommand/EarthWorm.cpp b/SourceCode/Code2026/CoreCommand/EarthWorm.cpp index 2a8b3e0..7ec11ad 100644 --- a/SourceCode/Code2026/CoreCommand/EarthWorm.cpp +++ b/SourceCode/Code2026/CoreCommand/EarthWorm.cpp @@ -727,6 +727,10 @@ void InitApplication() //ISO图: 自动分图(按管号/连通性/图幅把一个管道系统拆成多张 PCF) ADD_CMD(EW_PCFSPLIT, EW_PCFSPLITCmdFnc); + //ISO图: PCF 转 ISO 图(DWG) + ADD_CMD(EW_PCFISO, EW_PCFISOCmdFnc); + //ISO图: PCF 按连通性分图(P3D 规则) + ADD_CMD(EW_PCFISOSPLIT, EW_PCFISOSPLITCmdFnc); //管廊漫游 ADD_CMD(EW_GLMY, EW_GLMYCmdFnc); diff --git a/SourceCode/Code2026/CoreCommand/PCF-ISO分图规则说明.md b/SourceCode/Code2026/CoreCommand/PCF-ISO分图规则说明.md new file mode 100644 index 0000000..50fcdda --- /dev/null +++ b/SourceCode/Code2026/CoreCommand/PCF-ISO分图规则说明.md @@ -0,0 +1,119 @@ +# PCF → ISO 分图规则说明(对齐 AutoCAD Plant 3D) + +> 结论先行:Plant 3D(下称 P3D)把 PCF 出 ISO 时的“分图”,**主规则是连通性(模型)拆分**—— +> 把 PCF 里的管道元件按“连接点是否重合”连成一张网,凡是不连通的部分各自成为一张 ISO 图。 +> 我们按同一规则实现了 `EW_PCFISOSPLIT`,对同一份 PCF 得到了与 P3D **完全一致**的分图结果(6 张)。 + +--- + +## 1. P3D 的分图体系(由粗到细,共 5 级) + +| 级别 | 名称 | 触发条件 | 本次是否触发 | +| --- | --- | --- | --- | +| 1 | 模型级 Model | 连通性断开 / 属性变化 | **是**(拆成 6 个模型) | +| 2 | 断点级 Break point | 用户或默认“打断点”(法兰/现场焊口/焊口/任意件) | 否(“未指定打断点”) | +| 3 | 管段级 Spool | 尺寸/重量超限 | 否(`SpoolSplit=false`) | +| 4 | 图幅级 Sheet | 单张图纸放不下 | 否 | +| 5 | 拥塞级 Congestion | 图面过密 | 否 | + +P3D 处理顺序:**先按“模型”分组 → 每个模型尝试按断点/管段/图幅再拆 → 最后输出**。 +日志里逐条出现“正在处理:『污水』/ 未指定打断点 / 已跳过模型拆分”,说明模型已经由第 1 级确定,第 2 级被跳过。 + +--- + +## 2. `IsoConfig.xml` 关键参数(本机 Check_ANSI-C 样式实测) + +``` + FlangeLike > WeldFieldItem > Weld > AnyItem (程序首选打断点) + FlangeLike, WeldFieldItem, Weld (用户首选打断点) + Connector=FieldWeld (图幅断点,不改焊口类型) + + + + + +``` + +- `DefaultSplitPoints`:**程序**倾向在这些连接处断开(法兰类 → 现场焊口 → 焊口 → 任意件)。 +- `SplitPoints`:**用户**倾向断开处;若未在界面里指定,则模型拆分被跳过。 +- `SizeLimits="480 120 96"`:管段尺寸上限(英寸),`OverflowLength="6"` 为余量合并阈值,`MaxDirectionChanges="3"` 为允许的转向次数。 +- `Auto/SplitOnCongestion` + `CongestionLevel=2`:图面过密时按拥塞度再拆。 + +--- + +## 3. 实测:一份 PCF → 6 张图 + +- 输入:`test1.pcf`(= 我们导出的 `ISO-024_污水.pcf`,管线号 **污水**,**60 个元件 + 43 道焊口**,单一管线号)。 +- 因 P3D 默认工程是英制,先把 PCF 由 mm 换算成 inch(`test1_inch.pcf`)才能通过单位校验。 +- P3D 日志关键行: + - `警告 - 在模型中找到了无效的元件(缺少中心点) - TEE ...`(共 5 个 TEE 缺中心点) + - `警告 - 在线号“污水”的模型中发现连接已断开。` + - `信息 - 加载了“6”个模型。` → 每个模型“未指定打断点 / 已跳过模型拆分” → 生成 **6** 张图 `污水-1..6.dwg` + +产物内容(读图确认): + +| 图号 | 内容 | 管径 | +| --- | --- | --- | +| 污水-1/2/3 | 三根**立管**(各带 3 个阀门、上下异径管) | 300 mm | +| 污水-4 | 一段 200 mm 斜管 | 200 mm | +| 污水-5 | 600 mm 主管折线(含弯头/三通) | 600 mm | +| 污水-6 | 一段 600 mm 直管 | 600 mm | + +--- + +## 4. 规则提炼(我们实现所依据的规则) + +1. **规则 A(连通)**:两个元件只要有**重合的连接点**(端点 END-POINT / 中心点 CENTRE-POINT / 支承点 CO-ORDS),就属于同一模型。 +2. **规则 B(无效支管断开)**:三通等支管元件若**缺少 CENTRE-POINT**,P3D 判为“无效元件”,其**支管(BRANCH-POINT)不参与连通** → 主管在支管处断开。 + > 本案例正是这条规则把 3 根立管从主管上“摘”了下来。 +3. **规则 C(模型 = 一张图)**:每个连通子网输出一张 ISO。 +4. 规则 D/E/F(断点、管段尺寸、拥塞)为可选层,本次未触发。 + +> 用规则 A+B 对 `test1.pcf` 做并查集,得到 **6 个连通子网**,与 P3D 的 6 张图**一一对应**。 + +--- + +## 5. 我们的实现(`EW_PCFISOSPLIT`) + +代码:`SourceCode/Code2026/CoreCommand/PcfIso.cpp` / `PcfIso.h` + +- `pointKey()`:把 3D 点量化成字符串键(容差 0.01),用于判定连接点是否重合。 +- `collectConnPoints()`:收集一个元件参与连通的点;**缺少 CENTRE-POINT 时不含支管点**(规则 B)。 +- `groupComponents()`:并查集求连通子网,按“最小元件下标”排序,保证编号稳定。 +- `makeIsoDwgSplit()`:对每个子网调用 `renderToDwg()` 生成一张图,命名 `<文件名>-N.dwg`;只有一个子网时输出 `<文件名>.dwg`。 +- `makeIsoDwgSplitFromPath()` / `EW_PCFISOSPLITCmdFnc()`:命令入口,输入 PCF 文件或目录 + 输出目录。 + +命令:`EW_PCFISOSPLIT` → 输入 PCF(或目录)→ 输入输出目录(回车=同目录)。 + +--- + +## 6. 结果对比 + +实跑 `EW_PCFISOSPLIT test1.pcf`,输出 **6** 张图,与 P3D 内容一一对应(仅编号顺序不同): + +| Plant 3D | 我们 | 内容 | +| --- | --- | --- | +| 污水-1 | test1-1.dwg | 立管 A(300) | +| 污水-2 | test1-3.dwg | 立管 B(300) | +| 污水-3 | test1-4.dwg | 立管 C(300) | +| 污水-4 | test1-6.dwg | 200 mm 斜管 | +| 污水-5 | test1-2.dwg | 600 mm 主管折线 | +| 污水-6 | test1-5.dwg | 600 mm 直管 | + +对比图见:`%TEMP%\compare_split_real.png`(左 P3D / 右我们)。 + +--- + +## 7. 尚未实现 / 后续可做 + +- **断点级**:按 `SplitPoints`(法兰/现场焊口/焊口)在连通网内部再拆。 +- **管段级**:按 `SizeLimits`(尺寸上限、转向次数、余量合并)拆 spool。 +- **图幅级 / 拥塞级**:图面放不下或过密时再拆。 +- **图面样式**:P3D 的 ISO 是完整成品图(图框、指北针、标题栏、材料表 BOM、下料表、焊口表、尺寸标注、标高、续接标记);我们目前只画**单线骨架 + 管径标注**,这是下一步的主要差距。 + +--- + +*生成日期:2026-10-04* diff --git a/SourceCode/Code2026/CoreCommand/PcfIso.cpp b/SourceCode/Code2026/CoreCommand/PcfIso.cpp new file mode 100644 index 0000000..59a4573 --- /dev/null +++ b/SourceCode/Code2026/CoreCommand/PcfIso.cpp @@ -0,0 +1,1537 @@ +#include "StdAfx.h" +#include "PcfIso.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +/* + PCF -> ISO 图(DWG) 实现 + ---------------------------------------------------------------- + 分层: + 1) 解析 parsePcf() : PCF 文本 -> std::vector + 2) 投影 project() : 3D 模型点 -> 2D 等轴测点(与 P3D 一致) + 3) 分图 splitPipeline() : 模型级/断点级/管段级/图幅级 + 4) 绘制 IsoDrawer : 封装"建图层/画线/画圆/画文字/画多段线/画弧" + 5) 图面 renderSheet() : 图框/标题栏/材料表/切割表/尺寸/标高/气泡/北针/续接 + 6) 主流程 makeIsoDwg() : 解析 -> 分图 -> 逐张出图 +*/ + +namespace +{ + // ======================= 常量 ======================= + const double ISO_COS30 = 0.8660254037844386; // cos(30°) + const double ISO_SIN30 = 0.5; // sin(30°) + + // 头部属性(非组件),解析时直接跳过 + const TCHAR *g_pszHeaderKeys[] = + { + _T("ISOGEN-FILES"), _T("UNITS-BORE"), _T("UNITS-CO-ORDS"), + _T("UNITS-BOLT-LENGTH"), _T("UNITS-BOLT-DIA"), _T("UNITS-WEIGHT"), + _T("PIPELINE-REFERENCE"), _T("MATERIALS"), NULL + }; + + // 图层名 + LPCTSTR LAYER_PIPE = _T("ISO-PIPE"); // 管道 + LPCTSTR LAYER_FITTING = _T("ISO-FITTING"); // 管件 + LPCTSTR LAYER_VALVE = _T("ISO-VALVE"); // 阀门 + LPCTSTR LAYER_WELD = _T("ISO-WELD"); // 焊口 + LPCTSTR LAYER_SUPPORT = _T("ISO-SUPPORT"); // 支吊架 + LPCTSTR LAYER_TEXT = _T("ISO-TEXT"); // 文字 + LPCTSTR LAYER_TITLE = _T("ISO-TITLE"); // 标题 + LPCTSTR LAYER_DIM = _T("ISO-DIM"); // 标注 + LPCTSTR LAYER_FRAME = _T("ISO-FRAME"); // 图框 + LPCTSTR LAYER_TABLE = _T("ISO-TABLE"); // 表格 + LPCTSTR LAYER_SYMBOL = _T("ISO-SYMBOL"); // 符号(北针/续接) + + // 图纸尺寸(A3 横放,单位 mm)与版区 + const double SHEET_W = 420.0, SHEET_H = 297.0; + const double BORDER_L = 10.0, BORDER_B = 10.0, BORDER_R = 410.0, BORDER_T = 287.0; + // 标题栏(右下) + const double TB_L = 230.0, TB_B = 10.0, TB_R = 410.0, TB_T = 50.0; + // 材料表(右上) + const double BOM_L = 290.0, BOM_B = 185.0, BOM_R = 410.0, BOM_T = 287.0; + // 切割表(右中) + const double CUT_L = 290.0, CUT_B = 100.0, CUT_R = 410.0, CUT_T = 185.0; + // 绘图区 + const double DRAW_L = 15.0, DRAW_B = 55.0, DRAW_R = 285.0, DRAW_T = 285.0; + + // 前置声明(分组排序工具) + void sortGroups(std::vector > &g); + void sortGroup(std::vector &g); + + // ======================= 数据结构 ======================= + // 带管径的 3D 点 + struct IsoPt + { + AcGePoint3d pt; + double bore; + IsoPt() : pt(0.0, 0.0, 0.0), bore(0.0) {} + }; + + // 一个 PCF 组件 + struct IsoComp + { + CString type; // PIPE / ELBOW / TEE ... + std::vector ends; // END-POINT + std::vector branches; // BRANCH1/2-POINT + std::vector coords; // CO-ORDS (SUPPORT) + AcGePoint3d centre; // CENTRE-POINT + bool hasCentre; + CString itemCode; + CString desc; + CString skey; + CString category; + CString spec; // PIPING-SPEC + std::vector msgs; // MESSAGE-TEXT + + IsoComp() : centre(0.0, 0.0, 0.0), hasCentre(false) {} + + // 主管管径(第一个端点的 bore) + double bore() const { return ends.empty() ? 0.0 : ends[0].bore; } + }; + + // ======================= 字符串工具 ======================= + std::wstring trimWs(const std::wstring &s) + { + const size_t a = s.find_first_not_of(L" \t\r\n"); + if (a == std::wstring::npos) return std::wstring(); + const size_t b = s.find_last_not_of(L" \t\r\n"); + return s.substr(a, b - a + 1); + } + + std::wstring firstToken(const std::wstring &s) + { + const size_t a = s.find_first_not_of(L" \t"); + if (a == std::wstring::npos) return std::wstring(); + const size_t b = s.find_first_of(L" \t", a); + return (b == std::wstring::npos) ? s.substr(a) : s.substr(a, b - a); + } + + std::wstring restToken(const std::wstring &s) + { + const size_t a = s.find_first_not_of(L" \t"); + if (a == std::wstring::npos) return std::wstring(); + const size_t b = s.find_first_of(L" \t", a); + if (b == std::wstring::npos) return std::wstring(); + const size_t c = s.find_first_not_of(L" \t", b); + return (c == std::wstring::npos) ? std::wstring() : s.substr(c); + } + + // 读 UTF-8 文本文件为宽字符串(去掉 BOM) + bool readUtf8File(LPCTSTR path, std::wstring &out) + { + HANDLE h = ::CreateFile(path, GENERIC_READ, FILE_SHARE_READ, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (h == INVALID_HANDLE_VALUE) return false; + + const DWORD size = ::GetFileSize(h, NULL); + std::string buf; + if (size > 0 && size != INVALID_FILE_SIZE) + { + buf.resize(size); + DWORD read = 0; + if (!::ReadFile(h, &buf[0], size, &read, NULL)) { ::CloseHandle(h); return false; } + buf.resize(read); + } + ::CloseHandle(h); + + const int n = ::MultiByteToWideChar(CP_UTF8, 0, buf.c_str(), (int)buf.size(), NULL, 0); + out.resize(n > 0 ? n : 0); + if (n > 0) + ::MultiByteToWideChar(CP_UTF8, 0, buf.c_str(), (int)buf.size(), &out[0], n); + + if (!out.empty() && out[0] == 0xFEFF) out.erase(out.begin()); // BOM + return true; + } + + void parsePoint(const std::wstring &val, AcGePoint3d &pt, double &bore) + { + pt = AcGePoint3d(0.0, 0.0, 0.0); + bore = 0.0; + + std::vector v; + std::wstringstream ss(val); + double d = 0.0; + while (ss >> d) v.push_back(d); + + if (v.size() >= 3) { pt.x = v[0]; pt.y = v[1]; pt.z = v[2]; } + if (v.size() >= 4) bore = v[3]; + } + + bool isHeaderKey(const std::wstring &kw) + { + for (int i = 0; g_pszHeaderKeys[i]; ++i) + if (_wcsicmp(kw.c_str(), g_pszHeaderKeys[i]) == 0) return true; + return false; + } + + // ======================= PCF 解析 ======================= + bool parsePcf(LPCTSTR path, std::vector &comps, CString &pipelineRef) + { + std::wstring content; + if (!readUtf8File(path, content)) return false; + + comps.clear(); + pipelineRef.Empty(); + + IsoComp *pCur = NULL; + size_t pos = 0; + while (pos < content.size()) + { + const size_t eol = content.find(L'\n', pos); + std::wstring line = (eol == std::wstring::npos) + ? content.substr(pos) : content.substr(pos, eol - pos); + pos = (eol == std::wstring::npos) ? content.size() : eol + 1; + + if (!line.empty() && line[line.size() - 1] == L'\r') line.erase(line.size() - 1); + if (line.empty()) continue; + + const bool bIndent = (line[0] == L' ' || line[0] == L'\t'); + + if (!bIndent) + { + const std::wstring kw = firstToken(line); + + if (_wcsicmp(kw.c_str(), L"PIPELINE-REFERENCE") == 0) + { + pipelineRef = restToken(line).c_str(); + pCur = NULL; + continue; + } + if (isHeaderKey(kw)) { pCur = NULL; continue; } + + comps.push_back(IsoComp()); + pCur = &comps.back(); + pCur->type = kw.c_str(); + } + else + { + if (!pCur) continue; + + const std::wstring s = trimWs(line); + const std::wstring key = firstToken(s); + const std::wstring val = restToken(s); + + if (_wcsicmp(key.c_str(), L"END-POINT") == 0) + { + IsoPt p; parsePoint(val, p.pt, p.bore); + pCur->ends.push_back(p); + } + else if (_wcsicmp(key.c_str(), L"BRANCH1-POINT") == 0 + || _wcsicmp(key.c_str(), L"BRANCH2-POINT") == 0) + { + IsoPt p; parsePoint(val, p.pt, p.bore); + pCur->branches.push_back(p); + } + else if (_wcsicmp(key.c_str(), L"CENTRE-POINT") == 0) + { + double b = 0.0; parsePoint(val, pCur->centre, b); + pCur->hasCentre = true; + } + else if (_wcsicmp(key.c_str(), L"CO-ORDS") == 0) + { + AcGePoint3d pt; double b = 0.0; parsePoint(val, pt, b); + pCur->coords.push_back(pt); + } + else if (_wcsicmp(key.c_str(), L"ITEM-CODE") == 0) pCur->itemCode = val.c_str(); + else if (_wcsicmp(key.c_str(), L"ITEM-DESCRIPTION") == 0) pCur->desc = val.c_str(); + else if (_wcsicmp(key.c_str(), L"SKEY") == 0) pCur->skey = val.c_str(); + else if (_wcsicmp(key.c_str(), L"CATEGORY") == 0) pCur->category = val.c_str(); + else if (_wcsicmp(key.c_str(), L"PIPING-SPEC") == 0) pCur->spec = val.c_str(); + else if (_wcsicmp(key.c_str(), L"MESSAGE-TEXT") == 0) pCur->msgs.push_back(val.c_str()); + } + } + return true; + } + + // ======================= 等轴测投影(与 P3D 一致) ======================= + AcGePoint2d project(const AcGePoint3d &p) + { + return AcGePoint2d((p.x - p.y) * ISO_COS30, (p.x + p.y) * ISO_SIN30 + p.z); + } + + AcGePoint3d closestOnSegment(const AcGePoint3d &a, const AcGePoint3d &b, const AcGePoint3d &p) + { + const AcGeVector3d ab = b - a; + const double len2 = ab.lengthSqrd(); + if (len2 < 1e-9) return a; + double t = (p - a).dotProduct(ab) / len2; + if (t < 0.0) t = 0.0; + if (t > 1.0) t = 1.0; + return a + ab * t; + } + + void updateBBox(const AcGePoint2d &p, AcGePoint2d &mn, AcGePoint2d &mx, bool &bFirst) + { + if (bFirst) { mn = mx = p; bFirst = false; return; } + if (p.x < mn.x) mn.x = p.x; + if (p.y < mn.y) mn.y = p.y; + if (p.x > mx.x) mx.x = p.x; + if (p.y > mx.y) mx.y = p.y; + } + + // 3D 包围盒(用 double 数组 [6]) + void compBBox3d(const IsoComp &c, double bb[6], bool &bFirst) + { + for (size_t k = 0; k < c.ends.size(); ++k) + { + const AcGePoint3d &p = c.ends[k].pt; + if (bFirst) { bb[0]=bb[3]=p.x; bb[1]=bb[4]=p.y; bb[2]=bb[5]=p.z; bFirst=false; continue; } + if (p.xbb[3])bb[3]=p.x; if (p.y>bb[4])bb[4]=p.y; if (p.z>bb[5])bb[5]=p.z; + } + for (size_t k = 0; k < c.branches.size(); ++k) + { + const AcGePoint3d &p = c.branches[k].pt; + if (bFirst) { bb[0]=bb[3]=p.x; bb[1]=bb[4]=p.y; bb[2]=bb[5]=p.z; bFirst=false; continue; } + if (p.xbb[3])bb[3]=p.x; if (p.y>bb[4])bb[4]=p.y; if (p.z>bb[5])bb[5]=p.z; + } + if (c.hasCentre) + { + const AcGePoint3d &p = c.centre; + if (bFirst) { bb[0]=bb[3]=p.x; bb[1]=bb[4]=p.y; bb[2]=bb[5]=p.z; bFirst=false; } + else { + if (p.xbb[3])bb[3]=p.x; if (p.y>bb[4])bb[4]=p.y; if (p.z>bb[5])bb[5]=p.z; + } + } + for (size_t k = 0; k < c.coords.size(); ++k) + { + const AcGePoint3d &p = c.coords[k]; + if (bFirst) { bb[0]=bb[3]=p.x; bb[1]=bb[4]=p.y; bb[2]=bb[5]=p.z; bFirst=false; } + else { + if (p.xbb[3])bb[3]=p.x; if (p.y>bb[4])bb[4]=p.y; if (p.z>bb[5])bb[5]=p.z; + } + } + } + + // ======================= 绘图器 ======================= + class IsoDrawer + { + public: + explicit IsoDrawer(AcDbDatabase *pDb) + : m_pDb(pDb), m_pMS(NULL), m_offset(0.0, 0.0), m_scale(1.0) {} + ~IsoDrawer() { close(); } + + bool begin() + { + AcDbBlockTable *pBT = NULL; + if (m_pDb->getBlockTable(pBT, AcDb::kForRead) != Acad::eOk) return false; + const Acad::ErrorStatus es = pBT->getAt(ACDB_MODEL_SPACE, m_pMS, AcDb::kForWrite); + pBT->close(); + if (es != Acad::eOk) return false; + + initLayers(); + return true; + } + + void close() + { + if (m_pMS) { m_pMS->close(); m_pMS = NULL; } + } + + void setOffset(const AcGePoint2d &o) { m_offset = o; } + void setScale(double s) { m_scale = (s > 1e-9) ? s : 1.0; } + + // 图纸坐标(mm) -> 世界坐标(应用缩放与平移) + AcGePoint2d apply(const AcGePoint2d &p) const + { + return AcGePoint2d(p.x * m_scale + m_offset.x, p.y * m_scale + m_offset.y); + } + + // ---- 基本图元 ---- + AcDbObjectId addLine(const AcGePoint2d &a, const AcGePoint2d &b, LPCTSTR layer) + { + const AcGePoint2d p = apply(a), q = apply(b); + AcDbLine *pEnt = new AcDbLine(AcGePoint3d(p.x, p.y, 0.0), AcGePoint3d(q.x, q.y, 0.0)); + return append(pEnt, layer); + } + + AcDbObjectId addCircle(const AcGePoint2d &c, double r, LPCTSTR layer) + { + const double rr = r * m_scale; + if (!(rr > 1e-9)) return AcDbObjectId::kNull; // 半径<=0 会让 AcDbCircle 抛异常 + const AcGePoint2d p = apply(c); + AcDbCircle *pEnt = new AcDbCircle(AcGePoint3d(p.x, p.y, 0.0), AcGeVector3d(0.0, 0.0, 1.0), rr); + return append(pEnt, layer); + } + + AcDbObjectId addText(const AcGePoint2d &p, double h, LPCTSTR txt, LPCTSTR layer) + { + if (!txt || !*txt) return AcDbObjectId::kNull; + const AcGePoint2d q = apply(p); + AcDbText *pEnt = new AcDbText(AcGePoint3d(q.x, q.y, 0.0), txt, AcDbObjectId::kNull, h, 0.0); + return append(pEnt, layer); + } + + // 带对齐/旋转的文字(rot 弧度) + AcDbObjectId addTextEx(const AcGePoint2d &p, double h, LPCTSTR txt, double rot, int hMode, LPCTSTR layer) + { + if (!txt || !*txt) return AcDbObjectId::kNull; + const AcGePoint2d q = apply(p); + AcDbText *pEnt = new AcDbText(AcGePoint3d(q.x, q.y, 0.0), txt, AcDbObjectId::kNull, h, rot); + if (hMode == 1) pEnt->setHorizontalMode(AcDb::kTextCenter); + else if (hMode == 2) pEnt->setHorizontalMode(AcDb::kTextRight); + pEnt->setVerticalMode(AcDb::kTextVertMid); + pEnt->setAlignmentPoint(AcGePoint3d(q.x, q.y, 0.0)); + return append(pEnt, layer); + } + + void addPolyline(const std::vector &pts, LPCTSTR layer) + { + if (pts.size() < 2) return; + AcDbPolyline *pEnt = new AcDbPolyline((unsigned int)pts.size()); + for (size_t i = 0; i < pts.size(); ++i) + { + const AcGePoint2d q = apply(pts[i]); + pEnt->addVertexAt((unsigned int)i, q); + } + append(pEnt, layer); + } + + // 矩形(4 条线) + void addRect(double l, double b, double r, double t, LPCTSTR layer) + { + addLine(AcGePoint2d(l, b), AcGePoint2d(r, b), layer); + addLine(AcGePoint2d(r, b), AcGePoint2d(r, t), layer); + addLine(AcGePoint2d(r, t), AcGePoint2d(l, t), layer); + addLine(AcGePoint2d(l, t), AcGePoint2d(l, b), layer); + } + + // 虚线? 简化:用多段线模拟不了线型,直接画实线 + AcDbObjectId layerIdOf(LPCTSTR name) { return layerId(name); } + + private: + void initLayers() + { + struct { LPCTSTR name; int color; } defs[] = + { + { LAYER_PIPE, 5 }, // 蓝 + { LAYER_FITTING, 3 }, // 绿 + { LAYER_VALVE, 1 }, // 红 + { LAYER_WELD, 1 }, // 红 + { LAYER_SUPPORT, 6 }, // 洋红 + { LAYER_TEXT, 7 }, + { LAYER_TITLE, 7 }, + { LAYER_DIM, 4 }, // 青 + { LAYER_FRAME, 7 }, + { LAYER_TABLE, 7 }, + { LAYER_SYMBOL, 7 }, + }; + for (int i = 0; i < _countof(defs); ++i) + layerId(defs[i].name, defs[i].color); + } + + AcDbObjectId layerId(LPCTSTR name, int color = -1) + { + std::map::iterator it = m_layers.find(name); + if (it != m_layers.end()) return it->second; + + AcDbObjectId id = AcDbObjectId::kNull; + AcDbLayerTable *pLT = NULL; + if (m_pDb->getLayerTable(pLT, AcDb::kForRead) != Acad::eOk) return id; + + AcDbLayerTableRecord *pLTR = NULL; + if (pLT->getAt(name, pLTR, AcDb::kForRead) == Acad::eOk) + { + id = pLTR->objectId(); + pLTR->close(); + } + else + { + pLT->close(); + if (m_pDb->getLayerTable(pLT, AcDb::kForWrite) != Acad::eOk) return id; + pLTR = new AcDbLayerTableRecord; + pLTR->setName(name); + if (color >= 0) + { + AcCmColor clr; + clr.setColorIndex((Adesk::UInt16)color); + pLTR->setColor(clr); + } + if (pLT->add(pLTR) == Acad::eOk) id = pLTR->objectId(); + pLTR->close(); + } + pLT->close(); + + if (!id.isNull()) m_layers[name] = id; + return id; + } + + AcDbObjectId append(AcDbEntity *pEnt, LPCTSTR layer) + { + if (!pEnt || !m_pMS) { delete pEnt; return AcDbObjectId::kNull; } + pEnt->setLayer(layerId(layer)); + AcDbObjectId id; + if (m_pMS->appendAcDbEntity(id, pEnt) != Acad::eOk) id = AcDbObjectId::kNull; + pEnt->close(); + return id; + } + + AcDbDatabase *m_pDb; + AcDbBlockTableRecord *m_pMS; + AcGePoint2d m_offset; + double m_scale; + std::map m_layers; + }; + + // ======================= 组件落图 ======================= + void drawWeld(IsoDrawer &dr, const IsoComp &c, double txtH) + { + if (c.ends.empty()) return; + dr.addCircle(project(c.ends[0].pt), txtH * 0.3, LAYER_WELD); + } + + void drawSupport(IsoDrawer &dr, const IsoComp &c, double txtH) + { + if (c.coords.empty()) return; + const AcGePoint2d p = project(c.coords[0]); + const double s = txtH * 0.8; + const AcGePoint2d a(p.x - s * 0.5, p.y - s * 0.5); + const AcGePoint2d b(p.x + s * 0.5, p.y + s * 0.5); + dr.addLine(AcGePoint2d(a.x, a.y), AcGePoint2d(b.x, a.y), LAYER_SUPPORT); + dr.addLine(AcGePoint2d(b.x, a.y), AcGePoint2d(b.x, b.y), LAYER_SUPPORT); + dr.addLine(AcGePoint2d(b.x, b.y), AcGePoint2d(a.x, b.y), LAYER_SUPPORT); + dr.addLine(AcGePoint2d(a.x, b.y), AcGePoint2d(a.x, a.y), LAYER_SUPPORT); + } + + void drawComponent(IsoDrawer &dr, const IsoComp &c, double txtH) + { + const CString &t = c.type; + + if (t == _T("WELD")) { drawWeld(dr, c, txtH); return; } + if (t == _T("SUPPORT")) { drawSupport(dr, c, txtH); return; } + + if (c.ends.empty()) return; + + LPCTSTR layer = LAYER_FITTING; + if (t == _T("PIPE")) layer = LAYER_PIPE; + else if (t == _T("VALVE")) layer = LAYER_VALVE; + + // 弯头:两腿在拐点(CENTRE-POINT)相接,画折线 + if (t == _T("ELBOW") && c.hasCentre && c.ends.size() >= 2) + { + std::vector pl; + pl.push_back(project(c.ends[0].pt)); + pl.push_back(project(c.centre)); + pl.push_back(project(c.ends[1].pt)); + dr.addPolyline(pl, layer); + } + else if (c.ends.size() >= 2) + { + dr.addLine(project(c.ends[0].pt), project(c.ends[1].pt), layer); + } + else + { + // 只有一个端点(管帽等):画个小圆示意 + dr.addCircle(project(c.ends[0].pt), txtH * 0.5, layer); + } + + // 三通/四通:支管从主管上最近点引出 + if (!c.branches.empty() && c.ends.size() >= 2) + { + for (size_t k = 0; k < c.branches.size(); ++k) + { + const AcGePoint3d junc = closestOnSegment(c.ends[0].pt, c.ends[1].pt, c.branches[k].pt); + dr.addLine(project(junc), project(c.branches[k].pt), layer); + } + } + + // 直管管径标注 + if (t == _T("PIPE") && c.ends.size() >= 2 && c.ends[0].bore > 0.0) + { + const AcGePoint2d a = project(c.ends[0].pt); + const AcGePoint2d b = project(c.ends[1].pt); + AcGePoint2d mid((a.x + b.x) * 0.5, (a.y + b.y) * 0.5); + + const double dx = b.x - a.x, dy = b.y - a.y; + const double len = sqrt(dx * dx + dy * dy); + if (len > 1e-6) { mid.x += (-dy / len) * txtH * 0.8; mid.y += (dx / len) * txtH * 0.8; } + + CString sBore; + sBore.Format(_T("%.0f"), c.ends[0].bore); + dr.addText(mid, txtH, sBore, LAYER_TEXT); + } + } + + // 最后一个路径分隔符位置(同时识别 '\' 与 '/'),找不到返回 -1 + int lastSep(const CString &s) + { + const int a = s.ReverseFind(_T('\\')); + const int b = s.ReverseFind(_T('/')); + return (b > a) ? b : a; + } + + // 取文件名(不含扩展名) + CString fileStem(LPCTSTR path) + { + CString s(path); + const int p = lastSep(s); + CString name = (p >= 0) ? s.Mid(p + 1) : s; + const int dot = name.ReverseFind(_T('.')); + return (dot > 0) ? name.Left(dot) : name; + } + + // ======================= 连通性(P3D "模型级") ======================= + std::wstring pointKey(const AcGePoint3d &p) + { + std::wstringstream ss; + ss.setf(std::ios::fixed); + ss.precision(2); + ss << p.x << L'|' << p.y << L'|' << p.z; + return ss.str(); + } + + // 一个组件参与连通的连接点。 + // 规则(与 P3D 一致):端点/中心点/支点参与;缺少 CENTRE-POINT 的支管组件 + // 其支管不参与连通(主管在支管处断开)。 + void collectConnPoints(const IsoComp &c, std::vector &pts) + { + pts.clear(); + for (size_t k = 0; k < c.ends.size(); ++k) pts.push_back(c.ends[k].pt); + if (c.hasCentre) pts.push_back(c.centre); + for (size_t k = 0; k < c.coords.size(); ++k) pts.push_back(c.coords[k]); + if (!c.branches.empty() && c.hasCentre) + for (size_t k = 0; k < c.branches.size(); ++k) pts.push_back(c.branches[k].pt); + } + + // 并查集工具 + class UnionFind + { + public: + explicit UnionFind(int n) : m_p(n) { for (int i = 0; i < n; ++i) m_p[i] = i; } + int find(int x) { while (m_p[x] != x) { m_p[x] = m_p[m_p[x]]; x = m_p[x]; } return x; } + void unite(int a, int b) { int ra = find(a), rb = find(b); if (ra != rb) m_p[ra] = rb; } + private: + std::vector m_p; + }; + + // 由"连通点集合"构造组件连通子网。excluded!=NULL 时不参与连通(如断点组件)。 + std::vector > groupByPoints(const std::vector &comps, + const std::set *excluded) + { + const int n = (int)comps.size(); + UnionFind uf(n); + std::map firstAt; + std::vector pts; + + for (int i = 0; i < n; ++i) + { + if (excluded && excluded->count(i)) continue; + collectConnPoints(comps[i], pts); + for (size_t k = 0; k < pts.size(); ++k) + { + const std::wstring key = pointKey(pts[k]); + std::map::iterator it = firstAt.find(key); + if (it == firstAt.end()) { firstAt[key] = i; continue; } + uf.unite(i, it->second); + } + } + + std::map > byRoot; + for (int i = 0; i < n; ++i) + { + if (excluded && excluded->count(i)) continue; + byRoot[uf.find(i)].push_back(i); + } + + std::vector > out; + for (std::map >::iterator it = byRoot.begin(); it != byRoot.end(); ++it) + out.push_back(it->second); + sortGroups(out); + return out; + } + + // 按"最小组件下标"排序 + void sortGroups(std::vector > &g) + { + struct ByFirst + { + bool operator()(const std::vector &a, const std::vector &b) const + { return a.front() < b.front(); } + }; + std::sort(g.begin(), g.end(), ByFirst()); + } + + std::vector > groupComponents(const std::vector &comps) + { + return groupByPoints(comps, NULL); + } + + // ======================= 断点级 ======================= + // 断点组件:法兰(FLANGE)、现场焊口(WELD 且类别/描述含 FIELD)。 + bool isBreakPoint(const IsoComp &c) + { + if (c.type == _T("FLANGE")) return true; + if (c.type == _T("WELD")) + { + if (c.category.Find(_T("FIELD")) >= 0) return true; + if (c.desc.Find(_T("FIELD")) >= 0) return true; + if (c.skey.Find(_T("FIELD")) >= 0) return true; + } + return false; + } + + // 在断点处把每个连通组切开:断点组件不参与"连接中介", + // 之后再把它并入其任一邻接组(保证不丢件)。 + std::vector > splitByBreakPoints(const std::vector &comps, + const std::vector > &groups) + { + std::vector > out; + for (size_t gi = 0; gi < groups.size(); ++gi) + { + const std::vector &g = groups[gi]; + std::set breaks; + for (size_t k = 0; k < g.size(); ++k) + if (isBreakPoint(comps[g[k]])) breaks.insert(g[k]); + + if (breaks.empty()) { out.push_back(g); continue; } + + // 非断点组件做连通(全库范围),再筛出属于本组的子组 + std::set inGroup(g.begin(), g.end()); + std::vector > raw = groupByPoints(comps, &breaks); + std::vector > sub; + for (size_t s = 0; s < raw.size(); ++s) + { + std::vector piece; + for (size_t k = 0; k < raw[s].size(); ++k) + if (inGroup.count(raw[s][k])) piece.push_back(raw[s][k]); + if (!piece.empty()) sub.push_back(piece); + } + // 把断点组件并入其邻接的第一个非断点组 + std::map compToGroup; // 组件下标 -> sub 组号 + for (size_t s = 0; s < sub.size(); ++s) + for (size_t k = 0; k < sub[s].size(); ++k) compToGroup[sub[s][k]] = (int)s; + + std::vector bp, op; + for (std::set::iterator it = breaks.begin(); it != breaks.end(); ++it) + { + collectConnPoints(comps[*it], bp); + int target = -1; + for (size_t j = 0; j < g.size() && target < 0; ++j) + { + if (breaks.count(g[j])) continue; + collectConnPoints(comps[g[j]], op); + for (size_t a = 0; a < bp.size() && target < 0; ++a) + for (size_t b = 0; b < op.size(); ++b) + if (pointKey(bp[a]) == pointKey(op[b])) { target = compToGroup[g[j]]; break; } + } + if (target >= 0) sub[target].push_back(*it); + else { sub.push_back(std::vector(1, *it)); } // 孤立断点自成一组 + } + for (size_t s = 0; s < sub.size(); ++s) { sortGroup(sub[s]); out.push_back(sub[s]); } + } + sortGroups(out); + return out; + } + + void sortGroup(std::vector &g) { std::sort(g.begin(), g.end()); } + + // ======================= 邻接表 ======================= + void buildAdjacency(const std::vector &comps, const std::vector &g, + std::map > &adj) + { + adj.clear(); + std::map > at; + std::vector pts; + for (size_t k = 0; k < g.size(); ++k) + { + const int i = g[k]; + collectConnPoints(comps[i], pts); + for (size_t a = 0; a < pts.size(); ++a) + at[pointKey(pts[a])].push_back(i); + } + for (std::map >::iterator it = at.begin(); it != at.end(); ++it) + { + const std::vector &v = it->second; + for (size_t a = 0; a < v.size(); ++a) + for (size_t b = a + 1; b < v.size(); ++b) + { + adj[v[a]].push_back(v[b]); + adj[v[b]].push_back(v[a]); + } + } + // 邻接表去重并排序,保证遍历顺序确定 + for (std::map >::iterator it = adj.begin(); it != adj.end(); ++it) + { + std::vector &v = it->second; + std::sort(v.begin(), v.end()); + v.erase(std::unique(v.begin(), v.end()), v.end()); + } + } + + // ======================= 管段级 / 图幅级:按尺寸贪心切分 ======================= + // 从种子出发 BFS,累积包围盒;超过上限时切断,另起一段(保证每段连通)。 + std::vector > partitionBySize(const std::vector &comps, + const std::vector &g, double maxX, double maxY, double maxZ, int maxCount) + { + std::map > adj; + buildAdjacency(comps, g, adj); + + std::map visited; + std::vector > parts; + + for (size_t s = 0; s < g.size(); ++s) + { + const int seed = g[s]; + if (visited[seed]) continue; + + std::vector part; + double bb[6]; for (int k = 0; k < 6; ++k) bb[k] = 0.0; + bool bHave = false; + std::vector queue; queue.push_back(seed); + size_t qi = 0; + + while (qi < queue.size()) + { + const int n = queue[qi++]; + if (visited[n]) continue; + + // 组件 n 的 3D 包围盒 + double cb[6]; bool bf = true; compBBox3d(comps[n], cb, bf); + + // 并入后的包围盒 + double nb[6]; + if (!bHave) { for (int k = 0; k < 6; ++k) nb[k] = cb[k]; } + else + { + nb[0]=bb[0]; nb[1]=bb[1]; nb[2]=bb[2]; nb[3]=bb[3]; nb[4]=bb[4]; nb[5]=bb[5]; + if (cb[0]nb[3])nb[3]=cb[3]; if (cb[4]>nb[4])nb[4]=cb[4]; if (cb[5]>nb[5])nb[5]=cb[5]; + } + + const double sx = nb[3]-nb[0], sy = nb[4]-nb[1], sz = nb[5]-nb[2]; + const bool bExceed = (maxX>0 && sx>maxX) || (maxY>0 && sy>maxY) || (maxZ>0 && sz>maxZ) + || (maxCount>0 && (int)part.size()>=maxCount); + if (bHave && bExceed) { break; } // 不再并入本段,留给下一段 + + // 接受 n + part.push_back(n); + visited[n] = true; + for (int k = 0; k < 6; ++k) bb[k] = nb[k]; + bHave = true; + std::map >::iterator it = adj.find(n); + if (it != adj.end()) + for (size_t a = 0; a < it->second.size(); ++a) + if (!visited[it->second[a]]) queue.push_back(it->second[a]); + } + + if (!part.empty()) { sortGroup(part); parts.push_back(part); } + else { std::vector one(1, seed); visited[seed] = true; parts.push_back(one); } + } + sortGroups(parts); + return parts; + } + + // ======================= 分图主流程 ======================= + std::vector > splitPipeline(const std::vector &comps, const PcfIso::SplitOptions &opt) + { + std::vector > groups; + + if (opt.modelSplit) + groups = groupComponents(comps); + else + { + std::vector all; for (size_t i = 0; i < comps.size(); ++i) all.push_back((int)i); + groups.push_back(all); + } + + if (opt.breakPointSplit) + groups = splitByBreakPoints(comps, groups); + + if (opt.spoolSplit) + { + std::vector > next; + for (size_t i = 0; i < groups.size(); ++i) + { + std::vector > sub = partitionBySize(comps, groups[i], + opt.spoolMaxX, opt.spoolMaxY, opt.spoolMaxZ, 0); + for (size_t s = 0; s < sub.size(); ++s) next.push_back(sub[s]); + } + sortGroups(next); + groups = next; + } + + if (opt.sheetSplit) + { + std::vector > next; + for (size_t i = 0; i < groups.size(); ++i) + { + std::vector > sub = partitionBySize(comps, groups[i], + 0, 0, 0, opt.maxCompPerSheet); + for (size_t s = 0; s < sub.size(); ++s) next.push_back(sub[s]); + } + sortGroups(next); + groups = next; + } + return groups; + } +} + +// ======================= 图面:图框/标题栏/表格/标注 ======================= +namespace +{ + // 估算文字宽度(单位: ASCII 字符宽),中文按 2 计 + double strUnits(const CString &s) + { + double w = 0.0; + for (int i = 0; i < s.GetLength(); ++i) + w += ((unsigned short)s[i] > 0x2E80) ? 2.0 : 1.0; + return w; + } + + // 按列宽裁剪文字,避免压到下一列 + CString fitText(const CString &s, double colW, double txtH) + { + if (txtH <= 1e-6) return s; + const double maxUnits = colW / (txtH * 0.62); + if (strUnits(s) <= maxUnits) return s; + CString out; + double w = 0.0; + for (int i = 0; i < s.GetLength(); ++i) + { + const double u = ((unsigned short)s[i] > 0x2E80) ? 2.0 : 1.0; + if (w + u > maxUnits - 0.4) break; + out += s[i]; + w += u; + } + return out; + } + + // 在图纸坐标里画一个单元格表格 + void drawTable(IsoDrawer &dr, double l, double b, double r, double t, + const std::vector &colW, const std::vector &rows, double rowH, LPCTSTR layer) + { + // 外框 + dr.addRect(l, b, r, t, layer); + // 列分隔 + double x = l; + for (size_t i = 0; i + 1 < colW.size(); ++i) + { + x += colW[i]; + dr.addLine(AcGePoint2d(x, b), AcGePoint2d(x, t), layer); + } + // 行分隔 + 文字 + double y = t; + for (size_t ri = 0; ri < rows.size(); ++ri) + { + dr.addLine(AcGePoint2d(l, y), AcGePoint2d(r, y), layer); + + // 手动按 '|' 切分:不能用 CString::Tokenize——切到末尾后再调用 + // 会以 iStart<0 触发 AtlThrow(E_INVALIDARG),抛出未捕获异常导致致命错误。 + std::vector cells; + { + const CString &s = rows[ri]; + int start = 0; + for (;;) + { + const int bar = s.Find(_T('|'), start); + if (bar < 0) { cells.push_back(s.Mid(start)); break; } + cells.push_back(s.Mid(start, bar - start)); + start = bar + 1; + } + } + + const double txtH = rowH * 0.5; + double cx = l; + for (size_t ci = 0; ci < colW.size(); ++ci) + { + if (ci < cells.size() && !cells[ci].IsEmpty()) + { + const CString cell = fitText(cells[ci], colW[ci], txtH); + if (!cell.IsEmpty()) + dr.addText(AcGePoint2d(cx + 1.0, y - rowH * 0.5), txtH, cell, LAYER_TEXT); + } + cx += colW[ci]; + } + y -= rowH; + } + } + + // 标题栏(右下):若干字段 + void drawTitleBlock(IsoDrawer &dr, LPCTSTR pipeline, LPCTSTR sheetName, int idx, int total) + { + dr.addRect(TB_L, TB_B, TB_R, TB_T, LAYER_FRAME); + // 内部分隔 + dr.addLine(AcGePoint2d(TB_L, TB_B + 13.0), AcGePoint2d(TB_R, TB_B + 13.0), LAYER_FRAME); + dr.addLine(AcGePoint2d(TB_L, TB_B + 26.0), AcGePoint2d(TB_R, TB_B + 26.0), LAYER_FRAME); + dr.addLine(AcGePoint2d(TB_L + 120.0, TB_B), AcGePoint2d(TB_L + 120.0, TB_T), LAYER_FRAME); + + dr.addText(AcGePoint2d(TB_L + 3.0, TB_T - 6.0), 4.0, _T("管线号: "), LAYER_TEXT); + CString s; s.Format(_T("%s"), pipeline ? pipeline : _T("")); + dr.addText(AcGePoint2d(TB_L + 24.0, TB_T - 6.0), 4.0, s, LAYER_TEXT); + + dr.addText(AcGePoint2d(TB_L + 3.0, TB_B + 19.0), 4.0, _T("图号: "), LAYER_TEXT); + dr.addText(AcGePoint2d(TB_L + 24.0, TB_B + 19.0), 4.0, sheetName ? sheetName : _T(""), LAYER_TEXT); + + dr.addText(AcGePoint2d(TB_L + 3.0, TB_B + 6.0), 4.0, _T("比例: 1:1"), LAYER_TEXT); + CString sp; sp.Format(_T("第 %d / %d 张"), idx, total); + dr.addText(AcGePoint2d(TB_L + 60.0, TB_B + 6.0), 4.0, sp, LAYER_TEXT); + dr.addText(AcGePoint2d(TB_L + 125.0, TB_B + 6.0), 5.5, _T("等轴测图 (ISO)"), LAYER_TITLE); + dr.addText(AcGePoint2d(TB_L + 125.0, TB_T - 8.0), 4.0, _T("Envi3D 2026"), LAYER_TEXT); + } + + // 材料表:按 类型+规格+管径 归并计数 + void drawBom(IsoDrawer &dr, const std::vector &comps, const std::vector &g) + { + std::map counter; + std::map nameOf; + for (size_t k = 0; k < g.size(); ++k) + { + const IsoComp &c = comps[g[k]]; + if (c.type == _T("WELD")) continue; + CString nm = c.desc.IsEmpty() ? c.type : c.desc; + // 材料描述很长(含标准/材质),而表格列窄——只取首个逗号前的名称,过长再截断,避免压到下一列 + const int comma = nm.Find(_T(',')); + if (comma > 0) nm = nm.Left(comma); + if (nm.GetLength() > 12) nm = nm.Left(12); + CString key; + key.Format(_T("%s|%s|%.0f"), (LPCTSTR)c.type, (LPCTSTR)c.spec, c.bore()); + counter[key]++; + nameOf[key] = nm; + } + + std::vector rows; + rows.push_back(_T("材料表")); + rows.push_back(_T("序号|名称|规格|数量")); + int no = 1; + for (std::map::iterator it = counter.begin(); it != counter.end(); ++it) + { + CString s; + CString nm = nameOf[it->first]; + int p = it->first.Find(_T('|')); + int q = it->first.Find(_T('|'), p + 1); + CString specPart = (q > p) ? it->first.Mid(p + 1, q - p - 1) : CString(); + CString borePart = (q >= 0) ? it->first.Mid(q + 1) : CString(); + CString spec; + spec.Format(_T("%s DN%s"), (LPCTSTR)specPart, (LPCTSTR)borePart); + s.Format(_T("%d|%s|%s|%d"), no++, (LPCTSTR)nm, (LPCTSTR)spec, it->second); + rows.push_back(s); + } + + std::vector colW; colW.push_back(12.0); colW.push_back(44.0); colW.push_back(42.0); colW.push_back(22.0); + double rowH = (BOM_T - BOM_B) / (double)(rows.size() + 1); + if (rowH > 7.0) rowH = 7.0; + drawTable(dr, BOM_L, BOM_B, BOM_R, BOM_T, colW, rows, rowH, LAYER_TABLE); + } + + // 切割表:列出每段直管长度与编号 + void drawCutList(IsoDrawer &dr, const std::vector &comps, const std::vector &g) + { + std::vector rows; + rows.push_back(_T("切割表")); + rows.push_back(_T("管段号|长度(mm)|管径")); + int no = 1; + for (size_t k = 0; k < g.size(); ++k) + { + const IsoComp &c = comps[g[k]]; + if (c.type != _T("PIPE") || c.ends.size() < 2) continue; + const double len = (c.ends[1].pt - c.ends[0].pt).length(); + CString s; + s.Format(_T("P%d|%.0f|DN%.0f"), no++, len, c.bore()); + rows.push_back(s); + } + std::vector colW; colW.push_back(24.0); colW.push_back(48.0); colW.push_back(48.0); + double rowH = (CUT_T - CUT_B) / (double)(rows.size() + 1); + if (rowH > 7.0) rowH = 7.0; + drawTable(dr, CUT_L, CUT_B, CUT_R, CUT_T, colW, rows, rowH, LAYER_TABLE); + } + + // 指北针(等轴测):+Y 方向为北(左上 30°) + void drawNorthArrow(IsoDrawer &dr, const AcGePoint2d &at, double size) + { + // 北向单位向量(投影后的 +Y) + const AcGePoint2d dir(-ISO_COS30, ISO_SIN30); + const AcGePoint2d tip(at.x + dir.x * size, at.y + dir.y * size); + dr.addLine(at, tip, LAYER_SYMBOL); + // 箭头 + const AcGePoint2d perp(-dir.y, dir.x); + const double hs = size * 0.28; + const AcGePoint2d b1(tip.x - dir.x * hs + perp.x * hs * 0.6, tip.y - dir.y * hs + perp.y * hs * 0.6); + const AcGePoint2d b2(tip.x - dir.x * hs - perp.x * hs * 0.6, tip.y - dir.y * hs - perp.y * hs * 0.6); + dr.addLine(tip, b1, LAYER_SYMBOL); + dr.addLine(tip, b2, LAYER_SYMBOL); + dr.addText(AcGePoint2d(tip.x + dir.x * size * 0.18, tip.y + dir.y * size * 0.18), size * 0.22, _T("N"), LAYER_SYMBOL); + } + + // 续接标记:本图与其它图共享的"连接点"(被切开的接缝)处,画续接符号+目标图号 + void drawContinuation(IsoDrawer &dr, const std::vector &comps, + const std::vector &g, int thisIdx, + const std::vector > &allGroups, double txtH) + { + if (allGroups.size() < 2) return; + + // 点键 -> 触到的组集合 + std::map > pg; + std::vector pts; + for (size_t s = 0; s < allGroups.size(); ++s) + for (size_t k = 0; k < allGroups[s].size(); ++k) + { + collectConnPoints(comps[allGroups[s][k]], pts); + for (size_t a = 0; a < pts.size(); ++a) pg[pointKey(pts[a])].insert((int)s); + } + + std::set done; + for (size_t k = 0; k < g.size(); ++k) + { + collectConnPoints(comps[g[k]], pts); + for (size_t a = 0; a < pts.size(); ++a) + { + const std::wstring key = pointKey(pts[a]); + if (done.count(key)) continue; + std::map >::iterator it = pg.find(key); + if (it == pg.end() || it->second.size() < 2) continue; + done.insert(key); + + int target = -1; + for (std::set::iterator jt = it->second.begin(); jt != it->second.end(); ++jt) + if (*jt != thisIdx) { target = *jt; break; } + if (target < 0) continue; + + const AcGePoint2d p = project(pts[a]); + dr.addCircle(p, txtH * 0.35, LAYER_SYMBOL); + CString s; s.Format(_T("续接 %d"), target + 1); + dr.addText(AcGePoint2d(p.x + txtH * 0.6, p.y - txtH * 1.6), txtH * 0.8, s, LAYER_SYMBOL); + } + } + } + + // 焊口气泡:给每个 WELD 编号并画气泡+引线 + int drawWeldBalloons(IsoDrawer &dr, const std::vector &comps, const std::vector &g, double txtH) + { + int no = 0; + for (size_t k = 0; k < g.size(); ++k) + { + const IsoComp &c = comps[g[k]]; + if (c.type != _T("WELD") || c.ends.empty()) continue; + ++no; + const AcGePoint2d p = project(c.ends[0].pt); + // 气泡左右交替,避免密集区气泡互相压叠 + const double sx = (no % 2) ? 1.0 : -1.0; + const AcGePoint2d bp(p.x + sx * txtH * 3.2, p.y + txtH * 3.0); + dr.addLine(p, bp, LAYER_WELD); + dr.addCircle(bp, txtH * 0.9, LAYER_WELD); + CString s; s.Format(_T("%d"), no); + dr.addTextEx(bp, txtH * 0.7, s, 0.0, 1, LAYER_WELD); + } + return no; + } + + // 尺寸标注:直管画对齐尺寸线(偏移)+长度文字 + void drawDimensions(IsoDrawer &dr, const std::vector &comps, const std::vector &g, double txtH) + { + for (size_t k = 0; k < g.size(); ++k) + { + const IsoComp &c = comps[g[k]]; + if (c.type != _T("PIPE") || c.ends.size() < 2) continue; + const double len = (c.ends[1].pt - c.ends[0].pt).length(); + if (len < 1e-3) continue; + + const AcGePoint2d a = project(c.ends[0].pt), b = project(c.ends[1].pt); + const double dx = b.x - a.x, dy = b.y - a.y; + const double l = sqrt(dx * dx + dy * dy); + if (l < 1e-6) continue; + const double ux = -dy / l, uy = dx / l; // 法向 + const double off = txtH * 1.6; + const AcGePoint2d a2(a.x + ux * off, a.y + uy * off); + const AcGePoint2d b2(b.x + ux * off, b.y + uy * off); + // 尺寸线 + dr.addLine(a2, b2, LAYER_DIM); + // 界线 + dr.addLine(a, a2, LAYER_DIM); + dr.addLine(b, b2, LAYER_DIM); + // 文字 + CString s; s.Format(_T("%.0f"), len); + const AcGePoint2d mid((a2.x + b2.x) * 0.5, (a2.y + b2.y) * 0.5); + dr.addTextEx(AcGePoint2d(mid.x + ux * txtH * 0.6, mid.y + uy * txtH * 0.6), txtH, s, 0.0, 1, LAYER_DIM); + } + } + + // 标高:只在"开口端(度<=1)"或"最高/最低点"标注 EL +xxx,避免同一标高重复堆叠 + void drawElevations(IsoDrawer &dr, const std::vector &comps, const std::vector &g, double txtH) + { + // 统计各连接点被多少组件共用(度) + std::map deg; + std::vector pts; + double zmin = 1e18, zmax = -1e18; + for (size_t k = 0; k < g.size(); ++k) + { + const IsoComp &c = comps[g[k]]; + collectConnPoints(c, pts); + for (size_t a = 0; a < pts.size(); ++a) deg[pointKey(pts[a])]++; + for (size_t e = 0; e < c.ends.size(); ++e) + { + if (c.ends[e].pt.z < zmin) zmin = c.ends[e].pt.z; + if (c.ends[e].pt.z > zmax) zmax = c.ends[e].pt.z; + } + } + + // 两趟:先标最高/最低点,再标开口端;并用最小间距过滤,避免密集区文字互相压叠 + std::set done; + std::vector placed; + const double minGap = txtH * 6.0; + for (int pass = 0; pass < 2; ++pass) + { + for (size_t k = 0; k < g.size(); ++k) + { + const IsoComp &c = comps[g[k]]; + for (size_t e = 0; e < c.ends.size(); ++e) + { + const AcGePoint3d &p3 = c.ends[e].pt; + const std::wstring key = pointKey(p3); + if (done.count(key)) continue; + + const bool bOpen = (deg[key] <= 1); + const bool bExtreme = (fabs(p3.z - zmin) < 1e-6) || (fabs(p3.z - zmax) < 1e-6); + if (pass == 0) { if (!bExtreme) continue; } + else { if (bExtreme || !bOpen) continue; } + + done.insert(key); + const AcGePoint2d p = project(p3); + bool ok = true; + for (size_t j = 0; j < placed.size(); ++j) + if ((placed[j] - p).length() < minGap) { ok = false; break; } + if (!ok) continue; + placed.push_back(p); + + CString s; + s.Format(_T("EL +%.0f"), p3.z); + dr.addText(AcGePoint2d(p.x + txtH * 0.6, p.y + txtH * 1.2), txtH * 0.85, s, LAYER_TEXT); + } + } + } + } +} + +// ======================= 主流程 ======================= +// 把一组组件渲染成一张带图面样式的 DWG。 +static bool renderSheet(const std::vector &comps, const std::vector &g, + const std::vector > &allGroups, + LPCTSTR dwgPath, LPCTSTR pipeline, LPCTSTR sheetName, int idx, int total) +{ + if (g.empty()) return false; + + // 1) 投影求包围盒(模型坐标, mm) + AcGePoint2d mn(0.0, 0.0), mx(0.0, 0.0); + bool bFirst = true; + for (size_t k = 0; k < g.size(); ++k) + { + const IsoComp &c = comps[g[k]]; + for (size_t i = 0; i < c.ends.size(); ++i) updateBBox(project(c.ends[i].pt), mn, mx, bFirst); + for (size_t i = 0; i < c.branches.size(); ++i) updateBBox(project(c.branches[i].pt), mn, mx, bFirst); + for (size_t i = 0; i < c.coords.size(); ++i) updateBBox(project(c.coords[i]), mn, mx, bFirst); + if (c.hasCentre) updateBBox(project(c.centre), mn, mx, bFirst); + } + if (bFirst) return false; + + const double mw = mx.x - mn.x, mh = mx.y - mn.y; + + // 2) 计算绘图区缩放(模型 -> 图纸 mm) + const double dw = DRAW_R - DRAW_L, dh = DRAW_T - DRAW_B; + const double pad = 0.10; + double sc = 1.0; + if (mw > 1e-6 || mh > 1e-6) + { + const double sx = (dw * (1.0 - pad)) / (mw > 1e-6 ? mw : 1.0); + const double sy = (dh * (1.0 - pad)) / (mh > 1e-6 ? mh : 1.0); + sc = (sx < sy) ? sx : sy; + } + // 图纸上模型占位尺寸 + const double placedW = mw * sc, placedH = mh * sc; + const double ox = DRAW_L + (dw - placedW) * 0.5 - mn.x * sc; + const double oy = DRAW_B + (dh - placedH) * 0.5 - mn.y * sc; + + // 3) 建库 + AcDbDatabase *pDb = new AcDbDatabase(Adesk::kTrue, Adesk::kTrue); + bool bDrawn = false; + { + IsoDrawer dr(pDb); + if (!dr.begin()) { delete pDb; return false; } + try + { + // ---- 模型(用模型坐标;apply 会按 sc/offset 变换) ---- + dr.setScale(sc); + dr.setOffset(AcGePoint2d(ox, oy)); + + // 模型文字高度:图纸 3mm 反算到模型坐标 + const double modelTxtH = (sc > 1e-9) ? (3.0 / sc) : 3.0; + for (size_t k = 0; k < g.size(); ++k) + drawComponent(dr, comps[g[k]], modelTxtH); + + // 标注(模型坐标,会被缩放) + drawDimensions(dr, comps, g, modelTxtH); + drawElevations(dr, comps, g, modelTxtH); + drawWeldBalloons(dr, comps, g, modelTxtH); + drawContinuation(dr, comps, g, idx - 1, allGroups, modelTxtH); + + // ---- 图框/标题栏/表格/北针(图纸坐标:scale 复位为 1) ---- + dr.setScale(1.0); + dr.setOffset(AcGePoint2d(0.0, 0.0)); + + dr.addRect(BORDER_L, BORDER_B, BORDER_R, BORDER_T, LAYER_FRAME); + drawTitleBlock(dr, pipeline, sheetName, idx, total); + drawBom(dr, comps, g); + drawCutList(dr, comps, g); + drawNorthArrow(dr, AcGePoint2d(DRAW_L + 18.0, DRAW_T - 22.0), 16.0); + + bDrawn = true; + } + catch (...) + { + acutPrintf(_T("\n[ISO] 出图异常,已跳过该张。")); + } + + dr.close(); + } + + const Acad::ErrorStatus es = bDrawn ? pDb->saveAs(dwgPath) : Acad::eNotApplicable; + delete pDb; + return (es == Acad::eOk); +} + +// 计算输出目录(为空时取输入文件所在目录),并保证以反斜杠结尾 +static CString resolveOutDir(LPCTSTR inFile, LPCTSTR outDir) +{ + CString sDir(outDir); + if (sDir.IsEmpty()) + { + CString sIn(inFile); + const int p = lastSep(sIn); + sDir = (p >= 0) ? sIn.Left(p) : CString(); + } + if (!sDir.IsEmpty()) + { + const TCHAR c = sDir[sDir.GetLength() - 1]; + if (c != _T('\\') && c != _T('/')) sDir += _T('\\'); + } + return sDir; +} + +// ---------------- 选项默认值 ---------------- +namespace PcfIso +{ + SplitOptions::SplitOptions() + : modelSplit(true), breakPointSplit(false), spoolSplit(false), sheetSplit(true), + spoolMaxX(12192.0), spoolMaxY(3048.0), spoolMaxZ(2438.0), // 480/120/96 英寸 -> mm + maxDirChanges(3), maxCompPerSheet(200) + { + } +} + +bool PcfIso::makeIsoDwg(LPCTSTR pcfPath, LPCTSTR dwgPath) +{ + std::vector comps; + CString pipelineRef; + if (!parsePcf(pcfPath, comps, pipelineRef)) return false; + if (comps.empty()) return false; + + std::vector all; for (size_t i = 0; i < comps.size(); ++i) all.push_back((int)i); + std::vector > allGroups(1, all); + const CString stem = fileStem(pcfPath); + return renderSheet(comps, all, allGroups, dwgPath, pipelineRef, stem, 1, 1); +} + +int PcfIso::makeIsoDwgSplitEx(LPCTSTR pcfPath, LPCTSTR outDir, const SplitOptions &opt) +{ + std::vector comps; + CString pipelineRef; + if (!parsePcf(pcfPath, comps, pipelineRef)) return 0; + if (comps.empty()) return 0; + + const std::vector > groups = splitPipeline(comps, opt); + const int total = (int)groups.size(); + const CString sStem = fileStem(pcfPath); + const CString sDir = resolveOutDir(pcfPath, outDir); + + int nOk = 0; + for (int gi = 0; gi < total; ++gi) + { + CString sDwg, sheetName; + if (total <= 1) + { + sDwg.Format(_T("%s%s.dwg"), (LPCTSTR)sDir, (LPCTSTR)sStem); + sheetName = sStem; + } + else + { + sDwg.Format(_T("%s%s-%d.dwg"), (LPCTSTR)sDir, (LPCTSTR)sStem, gi + 1); + sheetName.Format(_T("%s-%d"), (LPCTSTR)sStem, gi + 1); + } + if (renderSheet(comps, groups[gi], groups, sDwg, pipelineRef, sheetName, gi + 1, total)) ++nOk; + } + return nOk; +} + +int PcfIso::makeIsoDwgSplit(LPCTSTR pcfPath, LPCTSTR outDir) +{ + SplitOptions opt; // 默认:模型级 + 图幅级(与 P3D 默认一致) + return makeIsoDwgSplitEx(pcfPath, outDir, opt); +} + +int PcfIso::makeIsoDwgFromPath(LPCTSTR inPath, LPCTSTR outDir) +{ + CString sIn(inPath); + if (sIn.IsEmpty()) return 0; + + CString sInTrim = sIn; + while (sInTrim.GetLength() > 3 + && (sInTrim[sInTrim.GetLength() - 1] == _T('\\') || sInTrim[sInTrim.GetLength() - 1] == _T('/'))) + sInTrim = sInTrim.Left(sInTrim.GetLength() - 1); + + const DWORD attr = ::GetFileAttributes(sInTrim); + const bool bDir = (attr != INVALID_FILE_ATTRIBUTES) && (attr & FILE_ATTRIBUTE_DIRECTORY); + + int nOk = 0; + + if (bDir) + { + CString sPattern = sInTrim; + if (sPattern[sPattern.GetLength() - 1] != _T('\\')) sPattern += _T('\\'); + sPattern += _T("*.pcf"); + + CFileFind finder; + BOOL bWorking = finder.FindFile(sPattern); + while (bWorking) + { + bWorking = finder.FindNextFile(); + if (finder.IsDots() || finder.IsDirectory()) continue; + + CString sDwgDir = resolveOutDir(finder.GetFilePath(), outDir); + CString sDwg = sDwgDir + finder.GetFileTitle() + _T(".dwg"); + if (makeIsoDwg(finder.GetFilePath(), sDwg)) ++nOk; + } + finder.Close(); + } + else + { + CString sDwgDir = resolveOutDir(sInTrim, outDir); + CString sDwg = sDwgDir + fileStem(sInTrim) + _T(".dwg"); + if (makeIsoDwg(sInTrim, sDwg)) ++nOk; + } + return nOk; +} + +int PcfIso::makeIsoDwgSplitFromPath(LPCTSTR inPath, LPCTSTR outDir) +{ + CString sIn(inPath); + if (sIn.IsEmpty()) return 0; + + CString sInTrim = sIn; + while (sInTrim.GetLength() > 3 + && (sInTrim[sInTrim.GetLength() - 1] == _T('\\') || sInTrim[sInTrim.GetLength() - 1] == _T('/'))) + sInTrim = sInTrim.Left(sInTrim.GetLength() - 1); + + const DWORD attr = ::GetFileAttributes(sInTrim); + const bool bDir = (attr != INVALID_FILE_ATTRIBUTES) && (attr & FILE_ATTRIBUTE_DIRECTORY); + + int nOk = 0; + if (bDir) + { + CString sPattern = sInTrim; + if (sPattern[sPattern.GetLength() - 1] != _T('\\')) sPattern += _T('\\'); + sPattern += _T("*.pcf"); + + CFileFind finder; + BOOL bWorking = finder.FindFile(sPattern); + while (bWorking) + { + bWorking = finder.FindNextFile(); + if (finder.IsDots() || finder.IsDirectory()) continue; + nOk += makeIsoDwgSplit(finder.GetFilePath(), outDir); + } + finder.Close(); + } + else + { + nOk += makeIsoDwgSplit(sInTrim, outDir); + } + return nOk; +} + +// ======================= 命令入口 ======================= +void EW_PCFISOCmdFnc() +{ + TCHAR szIn[512] = { 0 }; + if (acedGetString(FALSE, _T("\n请输入 PCF 文件或所在目录: "), szIn) != RTNORM) + return; + CString sIn = szIn; + if (sIn.IsEmpty()) { acutPrintf(_T("\n未输入路径,取消。")); return; } + + TCHAR szOut[512] = { 0 }; + if (acedGetString(FALSE, _T("\n请输入输出 DWG 目录<直接回车=与 PCF 同目录>: "), szOut) != RTNORM) + return; + + const int n = PcfIso::makeIsoDwgFromPath(sIn, szOut); + acutPrintf(_T("\nISO 图导出完成,共生成 %d 个 DWG。"), n); +} + +void EW_PCFISOSPLITCmdFnc() +{ + TCHAR szIn[512] = { 0 }; + if (acedGetString(FALSE, _T("\n请输入 PCF 文件或所在目录: "), szIn) != RTNORM) + return; + CString sIn = szIn; + if (sIn.IsEmpty()) { acutPrintf(_T("\n未输入路径,取消。")); return; } + + TCHAR szOut[512] = { 0 }; + if (acedGetString(FALSE, _T("\n请输入输出 DWG 目录<直接回车=与 PCF 同目录>: "), szOut) != RTNORM) + return; + + // 分图层级:1=模型级(默认) 2=+断点级 3=+管段级 4=+全部 + TCHAR szLv[32] = { 0 }; + if (acedGetString(FALSE, _T("\n分图层级 1=模型(默认) 2=+断点 3=+管段 4=+全部<回车=1>: "), szLv) != RTNORM) + return; + const int lv = (_ttoi(szLv) >= 1 && _ttoi(szLv) <= 4) ? _ttoi(szLv) : 1; + + PcfIso::SplitOptions opt; + opt.modelSplit = true; + opt.breakPointSplit = (lv >= 2); + opt.spoolSplit = (lv >= 3); + opt.sheetSplit = true; + + CString sInTrim = sIn; + while (sInTrim.GetLength() > 3 + && (sInTrim[sInTrim.GetLength() - 1] == _T('\\') || sInTrim[sInTrim.GetLength() - 1] == _T('/'))) + sInTrim = sInTrim.Left(sInTrim.GetLength() - 1); + + const DWORD attr = ::GetFileAttributes(sInTrim); + const bool bDir = (attr != INVALID_FILE_ATTRIBUTES) && (attr & FILE_ATTRIBUTE_DIRECTORY); + + int n = 0; + if (bDir) + { + CString sPattern = sInTrim; + if (sPattern[sPattern.GetLength() - 1] != _T('\\')) sPattern += _T('\\'); + sPattern += _T("*.pcf"); + CFileFind finder; + BOOL bWorking = finder.FindFile(sPattern); + while (bWorking) + { + bWorking = finder.FindNextFile(); + if (finder.IsDots() || finder.IsDirectory()) continue; + n += PcfIso::makeIsoDwgSplitEx(finder.GetFilePath(), szOut, opt); + } + finder.Close(); + } + else + { + n += PcfIso::makeIsoDwgSplitEx(sInTrim, szOut, opt); + } + + acutPrintf(_T("\nISO 分图完成(层级 %d),共生成 %d 个 DWG。"), lv, n); +} diff --git a/SourceCode/Code2026/CoreCommand/PcfIso.h b/SourceCode/Code2026/CoreCommand/PcfIso.h new file mode 100644 index 0000000..4caf9c1 --- /dev/null +++ b/SourceCode/Code2026/CoreCommand/PcfIso.h @@ -0,0 +1,62 @@ +#pragma once + +/* + PCF -> ISO 图(DWG) 导出 + ================================================================ + 把 EW_PCF / EW_PCFSPLIT 导出的 PCF 中间文件解析后,按等轴测(30°)投影, + 生成带图框/标题栏/材料表/切割表/尺寸/标高/焊口气泡/北针的 ISO 图并保存为 DWG。 + + 本文件与主代码(pcf.cpp)分离,算法自包含,便于后续单独修改/替换。 + + 坐标约定:PCF 用 3D 模型坐标(单位 mm),Z 向上。 + 等轴测投影(与 Plant 3D / Isogen 一致): + u = (x - y) * cos30 (+X 指向右上 30°) + v = (x + y) * sin30 + z (+Y 指向左上 30°,+Z 向上) + + 分图层级(与 P3D 的 IsoConfig.xml 对应): + 1) 模型级 Model —— 连通性断开(含"缺少中心点的三通"支管断开) + 2) 断点级 Break —— 法兰/现场焊口/焊口处切开 + 3) 管段级 Spool —— 尺寸超过 SizeLimits 时切开 + 4) 图幅级 Sheet —— 一张图放不下/过密时续接 +*/ + +namespace PcfIso +{ + // 分图选项。默认与 P3D 默认工程一致:只做"模型级"连通性分图。 + struct SplitOptions + { + bool modelSplit; // 1) 连通性分图(默认 true) + bool breakPointSplit; // 2) 断点分图(法兰/现场焊口) 默认 false + bool spoolSplit; // 3) 管段尺寸分图 默认 false + bool sheetSplit; // 4) 图幅/拥塞分图 默认 true + double spoolMaxX; // 管段尺寸上限 X(mm) + double spoolMaxY; // 管段尺寸上限 Y(mm) + double spoolMaxZ; // 管段尺寸上限 Z(mm) + int maxDirChanges; // 最大转向次数 + int maxCompPerSheet; // 单张图最大元件数(拥塞控制) + SplitOptions(); + }; + + // 把一个 PCF 文件渲染为单张 ISO 图(带图面样式)并存成 DWG。 + // pcfPath : 输入 PCF 文件路径 + // dwgPath : 输出 DWG 文件路径(含 .dwg 扩展名) + // 返回是否成功。 + bool makeIsoDwg(LPCTSTR pcfPath, LPCTSTR dwgPath); + + // 输入可以是单个 .pcf 文件,也可以是一个目录(处理目录下所有 *.pcf)。 + // outDir 为空时,输出到各 PCF 所在目录。 + // 返回成功生成的 DWG 个数。 + int makeIsoDwgFromPath(LPCTSTR inPath, LPCTSTR outDir); + + // 分图(按 P3D 规则把一个 PCF 拆成多张 ISO 图),使用默认选项。 + // 每个连通子网输出一个 DWG,命名为 <文件名>-N.dwg;只有一个子网时输出 <文件名>.dwg。 + // outDir 为空时输出到 PCF 所在目录。返回成功生成的 DWG 个数。 + int makeIsoDwgSplit(LPCTSTR pcfPath, LPCTSTR outDir); + + // 分图(可指定分层选项)。 + int makeIsoDwgSplitEx(LPCTSTR pcfPath, LPCTSTR outDir, const SplitOptions &opt); + + // 输入单个 .pcf 或目录,逐个按上面的规则分图。 + // 返回成功生成的 DWG 个数。 + int makeIsoDwgSplitFromPath(LPCTSTR inPath, LPCTSTR outDir); +} diff --git a/SourceCode/Code2026/CoreCommand/StdArx.h b/SourceCode/Code2026/CoreCommand/StdArx.h index 380b5ff..5f0d072 100644 --- a/SourceCode/Code2026/CoreCommand/StdArx.h +++ b/SourceCode/Code2026/CoreCommand/StdArx.h @@ -130,6 +130,8 @@ void EW_PDWGOUTCmdFnc(); void EW_IGSOUTCmdFnc(); void EW_PCFCmdFnc(); void EW_PCFSPLITCmdFnc(); +void EW_PCFISOCmdFnc(); +void EW_PCFISOSPLITCmdFnc(); void EW_GLMYCmdFnc(); void EW_ENODECmdFnc(); void EW_CTRLVIEWCmdFnc(); diff --git a/SourceCode/Code2026/CoreCommand/cmakelists.txt b/SourceCode/Code2026/CoreCommand/cmakelists.txt index 26941ed..d4c79f2 100644 --- a/SourceCode/Code2026/CoreCommand/cmakelists.txt +++ b/SourceCode/Code2026/CoreCommand/cmakelists.txt @@ -100,6 +100,7 @@ set(SOURCES "sortedmenuitemdlg.cpp" "collisiondetection.cpp" "pcf.cpp" + "PcfIso.cpp" "EarthWorm.rc" # CoreCommand.def )