Files
envi-code/SourceCode/Code2026/CoreCommand/PcfIso.cpp
T
gjm cb38f5071b 新增 PCF→ISO 图(DWG)模块 PcfIso:P3D 一致的分图分层与图面样式
- 新增 PcfIso.h/.cpp:PCF 解析、等轴测投影(朝向与 Plant 3D 一致)、
  A3 图框/标题栏/材料表/切割表/尺寸/标高/焊口气泡/北针/跨图续接。
- 分图 4 级:模型连通性 → 断点(法兰/现场焊口) → 管段尺寸 → 图幅/拥塞。
- 新增命令 EW_PCFISO(单图)、EW_PCFISOSPLIT(分图,运行时选层级 1/2/3/4)。
- 注册到 EarthWorm.cpp、StdArx.h,并接入 cmakelists.txt。
- 附《PCF-ISO分图规则说明.md》。
2026-10-08 08:29:49 +08:00

1538 lines
50 KiB
C++

#include "StdAfx.h"
#include "PcfIso.h"
#include <dbents.h>
#include <dbpl.h>
#include <dbsymtb.h>
#include <dbmain.h>
#include <acutads.h>
#include <geassign.h>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <map>
#include <set>
#include <algorithm>
#include <cmath>
/*
PCF -> ISO 图(DWG) 实现
----------------------------------------------------------------
分层:
1) 解析 parsePcf() : PCF 文本 -> std::vector<IsoComp>
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<std::vector<int> > &g);
void sortGroup(std::vector<int> &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<IsoPt> ends; // END-POINT
std::vector<IsoPt> branches; // BRANCH1/2-POINT
std::vector<AcGePoint3d> coords; // CO-ORDS (SUPPORT)
AcGePoint3d centre; // CENTRE-POINT
bool hasCentre;
CString itemCode;
CString desc;
CString skey;
CString category;
CString spec; // PIPING-SPEC
std::vector<CString> 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<double> 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<IsoComp> &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.x<bb[0])bb[0]=p.x; if (p.y<bb[1])bb[1]=p.y; if (p.z<bb[2])bb[2]=p.z;
if (p.x>bb[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.x<bb[0])bb[0]=p.x; if (p.y<bb[1])bb[1]=p.y; if (p.z<bb[2])bb[2]=p.z;
if (p.x>bb[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.x<bb[0])bb[0]=p.x; if (p.y<bb[1])bb[1]=p.y; if (p.z<bb[2])bb[2]=p.z;
if (p.x>bb[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.x<bb[0])bb[0]=p.x; if (p.y<bb[1])bb[1]=p.y; if (p.z<bb[2])bb[2]=p.z;
if (p.x>bb[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<AcGePoint2d> &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<CString, AcDbObjectId>::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<CString, AcDbObjectId> 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<AcGePoint2d> 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<AcGePoint3d> &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<int> m_p;
};
// 由"连通点集合"构造组件连通子网。excluded!=NULL 时不参与连通(如断点组件)。
std::vector<std::vector<int> > groupByPoints(const std::vector<IsoComp> &comps,
const std::set<int> *excluded)
{
const int n = (int)comps.size();
UnionFind uf(n);
std::map<std::wstring, int> firstAt;
std::vector<AcGePoint3d> 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<std::wstring, int>::iterator it = firstAt.find(key);
if (it == firstAt.end()) { firstAt[key] = i; continue; }
uf.unite(i, it->second);
}
}
std::map<int, std::vector<int> > byRoot;
for (int i = 0; i < n; ++i)
{
if (excluded && excluded->count(i)) continue;
byRoot[uf.find(i)].push_back(i);
}
std::vector<std::vector<int> > out;
for (std::map<int, std::vector<int> >::iterator it = byRoot.begin(); it != byRoot.end(); ++it)
out.push_back(it->second);
sortGroups(out);
return out;
}
// 按"最小组件下标"排序
void sortGroups(std::vector<std::vector<int> > &g)
{
struct ByFirst
{
bool operator()(const std::vector<int> &a, const std::vector<int> &b) const
{ return a.front() < b.front(); }
};
std::sort(g.begin(), g.end(), ByFirst());
}
std::vector<std::vector<int> > groupComponents(const std::vector<IsoComp> &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<std::vector<int> > splitByBreakPoints(const std::vector<IsoComp> &comps,
const std::vector<std::vector<int> > &groups)
{
std::vector<std::vector<int> > out;
for (size_t gi = 0; gi < groups.size(); ++gi)
{
const std::vector<int> &g = groups[gi];
std::set<int> 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<int> inGroup(g.begin(), g.end());
std::vector<std::vector<int> > raw = groupByPoints(comps, &breaks);
std::vector<std::vector<int> > sub;
for (size_t s = 0; s < raw.size(); ++s)
{
std::vector<int> 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<int, int> 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<AcGePoint3d> bp, op;
for (std::set<int>::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<int>(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<int> &g) { std::sort(g.begin(), g.end()); }
// ======================= 邻接表 =======================
void buildAdjacency(const std::vector<IsoComp> &comps, const std::vector<int> &g,
std::map<int, std::vector<int> > &adj)
{
adj.clear();
std::map<std::wstring, std::vector<int> > at;
std::vector<AcGePoint3d> 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<std::wstring, std::vector<int> >::iterator it = at.begin(); it != at.end(); ++it)
{
const std::vector<int> &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<int, std::vector<int> >::iterator it = adj.begin(); it != adj.end(); ++it)
{
std::vector<int> &v = it->second;
std::sort(v.begin(), v.end());
v.erase(std::unique(v.begin(), v.end()), v.end());
}
}
// ======================= 管段级 / 图幅级:按尺寸贪心切分 =======================
// 从种子出发 BFS,累积包围盒;超过上限时切断,另起一段(保证每段连通)。
std::vector<std::vector<int> > partitionBySize(const std::vector<IsoComp> &comps,
const std::vector<int> &g, double maxX, double maxY, double maxZ, int maxCount)
{
std::map<int, std::vector<int> > adj;
buildAdjacency(comps, g, adj);
std::map<int, bool> visited;
std::vector<std::vector<int> > parts;
for (size_t s = 0; s < g.size(); ++s)
{
const int seed = g[s];
if (visited[seed]) continue;
std::vector<int> part;
double bb[6]; for (int k = 0; k < 6; ++k) bb[k] = 0.0;
bool bHave = false;
std::vector<int> 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[0])nb[0]=cb[0]; if (cb[1]<nb[1])nb[1]=cb[1]; if (cb[2]<nb[2])nb[2]=cb[2];
if (cb[3]>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<int, std::vector<int> >::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<int> one(1, seed); visited[seed] = true; parts.push_back(one); }
}
sortGroups(parts);
return parts;
}
// ======================= 分图主流程 =======================
std::vector<std::vector<int> > splitPipeline(const std::vector<IsoComp> &comps, const PcfIso::SplitOptions &opt)
{
std::vector<std::vector<int> > groups;
if (opt.modelSplit)
groups = groupComponents(comps);
else
{
std::vector<int> 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<std::vector<int> > next;
for (size_t i = 0; i < groups.size(); ++i)
{
std::vector<std::vector<int> > 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<std::vector<int> > next;
for (size_t i = 0; i < groups.size(); ++i)
{
std::vector<std::vector<int> > 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<double> &colW, const std::vector<CString> &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<CString> 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<IsoComp> &comps, const std::vector<int> &g)
{
std::map<CString, int> counter;
std::map<CString, CString> 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<CString> rows;
rows.push_back(_T("材料表"));
rows.push_back(_T("序号|名称|规格|数量"));
int no = 1;
for (std::map<CString, int>::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<double> 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<IsoComp> &comps, const std::vector<int> &g)
{
std::vector<CString> 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<double> 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<IsoComp> &comps,
const std::vector<int> &g, int thisIdx,
const std::vector<std::vector<int> > &allGroups, double txtH)
{
if (allGroups.size() < 2) return;
// 点键 -> 触到的组集合
std::map<std::wstring, std::set<int> > pg;
std::vector<AcGePoint3d> 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<std::wstring> 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<std::wstring, std::set<int> >::iterator it = pg.find(key);
if (it == pg.end() || it->second.size() < 2) continue;
done.insert(key);
int target = -1;
for (std::set<int>::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<IsoComp> &comps, const std::vector<int> &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<IsoComp> &comps, const std::vector<int> &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<IsoComp> &comps, const std::vector<int> &g, double txtH)
{
// 统计各连接点被多少组件共用(度)
std::map<std::wstring, int> deg;
std::vector<AcGePoint3d> 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<std::wstring> done;
std::vector<AcGePoint2d> 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<IsoComp> &comps, const std::vector<int> &g,
const std::vector<std::vector<int> > &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<IsoComp> comps;
CString pipelineRef;
if (!parsePcf(pcfPath, comps, pipelineRef)) return false;
if (comps.empty()) return false;
std::vector<int> all; for (size_t i = 0; i < comps.size(); ++i) all.push_back((int)i);
std::vector<std::vector<int> > 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<IsoComp> comps;
CString pipelineRef;
if (!parsePcf(pcfPath, comps, pipelineRef)) return 0;
if (comps.empty()) return 0;
const std::vector<std::vector<int> > 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);
}