Files
envi-code/SourceCode/Code2026/CoreCommand/pcf.cpp
T
gjm 164968b62e chore
把非utf8-bom编码的cpp/h文件改为 utf8 bom 编码, msvc识别utf8编码时,如果不是bom格式的,会使用当前cp_oem来解码.
2026-10-04 00:04:20 +08:00

2817 lines
85 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "StdAfx.h"
#include "pcf.h"
#include "uiiado.h"
#include "base64.h"
#include "coretools.h"
#include "aecdbewpipe.h"
#include "aecdbewfitting.h"
#include "aecdbewequipment.h"
#include "aecdbxequipment.h"
#include "aecdbpipehanger.h"
#include <ostream>
#include <string>
#include <iomanip>
#include <set>
using namespace std;
namespace
{
typedef const wchar_t *STR_PTR;
typedef pair<wstring, wstring> STR_PAIR;
typedef map<wstring, wstring> STR_MAP;
// 定义属性字符串,避免直接输入造成笔误,用到那个,定义哪个
// ==========================================
// 1. 基础单位 (Units)
// ==========================================
STR_PTR PCF_UNITS_MM = L"MM";
STR_PTR PCF_UNITS_METER = L"METER";
STR_PTR PCF_UNITS_INCH = L"INCH";
STR_PTR PCF_UNITS_FEET = L"FEET";
STR_PTR PCF_UNITS_INCH_SIXTEENTH = L"INCH-SIXTEENTH";
STR_PTR PCF_UNITS_MM_HUNDREDTHS = L"MM-HUNDREDTHS";
STR_PTR PCF_UNITS_DEGREES = L"DEGREES";
STR_PTR PCF_UNITS_RADIANS = L"RADIANS";
STR_PTR PCF_UNITS_KGS = L"KGS";
STR_PTR PCF_UNITS_LBS = L"LBS";
STR_PTR PCF_UNITS_NM = L"NM";
STR_PTR PCF_UNITS_LBFT = L"LBFT";
// ==========================================
// 2. 全局头部属性 (Header Attributes)
// ==========================================
STR_PTR PCF_HEAD_ISOGEN_FILES = L"ISOGEN-FILES";
STR_PTR PCF_HEAD_UNITS_BORE = L"UNITS-BORE";
STR_PTR PCF_HEAD_UNITS_CO_ORDS = L"UNITS-CO-ORDS";
STR_PTR PCF_HEAD_UNITS_BOLT_DIA = L"UNITS-BOLT-DIA";
STR_PTR PCF_HEAD_UNITS_BOLT_LENGTH = L"UNITS-BOLT-LENGTH";
STR_PTR PCF_HEAD_UNITS_WEIGHT = L"UNITS-WEIGHT";
STR_PTR PCF_HEAD_PIPELINE_REFERENCE = L"PIPELINE-REFERENCE";
STR_PTR PCF_HEAD_PIPING_SPEC = L"PIPING-SPEC";
STR_PTR PCF_HEAD_REVISION = L"REVISION";
STR_PTR PCF_HEAD_PROJECT_IDENTIFIER = L"PROJECT-IDENTIFIER";
STR_PTR PCF_HEAD_SYSTEM_IDENTIFIER = L"SYSTEM-IDENTIFIER";
// ==========================================
// 3. 组件主类型 (Component Types) - 增加前缀 COMP_ 以防命名冲突
// ==========================================
STR_PTR PCF_COMP_PIPE = L"PIPE";
STR_PTR PCF_COMP_ELBOW = L"ELBOW";
STR_PTR PCF_COMP_TEE = L"TEE";
STR_PTR PCF_COMP_CROSS = L"CROSS";
STR_PTR PCF_COMP_REDUCER = L"REDUCER";
STR_PTR PCF_COMP_VALVE = L"VALVE";
STR_PTR PCF_COMP_FLANGE = L"FLANGE";
STR_PTR PCF_COMP_GASKET = L"GASKET";
STR_PTR PCF_COMP_BOLT = L"BOLT";
STR_PTR PCF_COMP_WELD = L"WELD";
STR_PTR PCF_COMP_CAP = L"CAP";
STR_PTR PCF_COMP_BLIND_FLANGE = L"FLANGE-BLIND";
STR_PTR PCF_COMP_REDUCER_CONC = L"REDUCER-CONCENTRIC";
STR_PTR PCF_COMP_REDUCER_ECC = L"REDUCER-ECCENTRIC";
STR_PTR PCF_COMP_INSTRUMENT = L"INSTRUMENT";
STR_PTR PCF_COMP_SUPPORT = L"SUPPORT";
STR_PTR PCF_COMP_EQUIPMENT = L"EQUIPMENT";
STR_PTR PCF_COMP_COUPLING = L"COUPLING";
STR_PTR PCF_COMP_OLET = L"OLET";
STR_PTR PCF_COMP_BEND = L"BEND";
// 设备(EQUIPMENT)块关键字
STR_PTR PCF_KW_REFERENCE = L"REFERENCE";
STR_PTR PCF_KW_LOCATION = L"LOCATION";
STR_PTR PCF_KW_ORIENTATION = L"ORIENTATION";
STR_PTR PCF_KW_NOZZLE = L"NOZZLE";
STR_PTR PCF_KW_DIRECTION = L"DIRECTION";
STR_PTR PCF_KW_SHAPE = L"SHAPE";
STR_PTR PCF_KW_CATEGORY = L"CATEGORY";
// 支吊架(SUPPORT)块关键字 / 取值
STR_PTR PCF_ATTR_NAME = L"NAME";
STR_PTR PCF_SKEY_SUPPORT_DEFAULT = L"01HG";
STR_PTR PCF_CAT_ERECTION = L"ERECTION";
STR_PTR PCF_ATTR_COMPONENT_ATTRIBUTE0 = L"COMPONENT-ATTRIBUTE0";
STR_PTR PCF_KEY_SUPPORT_DN = L"DN"; // 支吊架专业数据键(取自图集表的列名)
STR_PTR PCF_KEY_SUPPORT_MODE = L"MODE"; // 支吊架类型 0~4
// 焊口(WELD)块关键字 / 取值
STR_PTR PCF_ATTR_SKEY_WELD = L"WW"; // Isogen 焊接符号键
STR_PTR PCF_CAT_FABRICATION = L"FABRICATION";
// ==========================================
// 4. 坐标与节点类型 (Point Types)
// ==========================================
STR_PTR PCF_PT_END_POINT = L"END-POINT";
STR_PTR PCF_PT_CENTRE_POINT = L"CENTRE-POINT";
STR_PTR PCF_PT_BRANCH1_POINT = L"BRANCH1-POINT";
STR_PTR PCF_PT_BRANCH2_POINT = L"BRANCH2-POINT";
STR_PTR PCF_PT_CO_ORDS = L"CO-ORDS";
// ==========================================
// 5. 组件明细与业务属性 (Component Attributes)
// ==========================================
STR_PTR PCF_ATTR_SKEY = L"SKEY";
STR_PTR PCF_ATTR_ITEM_CODE = L"ITEM-CODE";
STR_PTR PCF_ATTR_ITEM_DESCRIPTION = L"ITEM-DESCRIPTION";
STR_PTR PCF_ATTR_MATERIAL_IDENTIFIER = L"MATERIAL-IDENTIFIER";
STR_PTR PCF_ATTR_WEIGHT = L"WEIGHT";
STR_PTR PCF_ATTR_WALL_THICKNESS = L"WALL-THICKNESS";
STR_PTR PCF_ATTR_RATING = L"RATING";
STR_PTR PCF_ATTR_FABRICATION_ITEM = L"FABRICATION-ITEM";
STR_PTR PCF_ATTR_ERECTION_ITEM = L"ERECTION-ITEM";
STR_PTR PCF_ATTR_SPINDLE_DIRECTION = L"SPINDLE-DIRECTION";
STR_PTR PCF_ATTR_MESSAGE_TEXT = L"MESSAGE-TEXT";
class PcfRow
{
public:
private:
ostream &m_out; // 统一使用窄字符流基类!
bool m_isFirstItem;
public:
PcfRow(ostream &outStream, int numOfSpace)
: m_out(outStream), m_isFirstItem(true)
{
for (int i = 0; i < numOfSpace; ++i) m_out << " ";
}
~PcfRow() { m_out << "\n"; }
// 1. 基础类型模板 (如 int, double, AcGePoint3d)
template <typename T>
PcfRow &operator<<(const T &value)
{
if (!m_isFirstItem) m_out << " ";
m_out << fixed << setprecision(4) << value;
m_isFirstItem = false;
return *this;
}
// 3. 核心升级:拦截 AutoCAD 宽字符 (处理带中文的描述信息)
PcfRow &operator<<(STR_PTR wValue)
{
if (!m_isFirstItem) m_out << " ";
// 自动转为 UTF-8 并推入底层字节流
m_out << Base64::unicode2Ansii(wValue, CP_UTF8);
m_isFirstItem = false;
return *this;
}
// 写一个属性
PcfRow &write_Attribute(STR_PTR attri, STR_PTR svalue)
{
if ((attri && svalue)
&& (wcslen(attri) > 0 && wcslen(svalue) > 0))
{
*this<< attri << svalue;
}
return *this;
}
};
PcfRow &operator<<(PcfRow &row, const AcGePoint3d &pt)
{
// 自动拆解并保持间距
row << pt.x << pt.y << pt.z;
return row;
}
void writePcfAttribute(ostream &out, int indent, STR_PTR attri, STR_PTR svalue)
{
// 只有当属性名和属性值都切实有效时,才实例化 PcfRow
if (attri && svalue && wcslen(attri) > 0 && wcslen(svalue) > 0)
PcfRow(out, indent) << attri << svalue;
}
STR_PAIR makePair(STR_PTR field, STR_PTR v)
{
return make_pair(field, v);
}
template <typename T>
STR_PAIR makePair(STR_PTR field, T &v, const int &prec = 0)
{
std::wstringstream wss;
wss << fixed << setprecision(prec) << v;
// 显式构造 std::pair,避免再匹配到本模板造成无限递归(C4717)
return std::make_pair(std::wstring(field), wss.str());
}
wstring getValue(STR_MAP mm, STR_PTR key)
{
STR_MAP::const_iterator it(mm.find(key));
if (it != mm.end())
return it->second;
return L"";
}
// ============================ 规格库(物料码)查询 ============================
// 合法等级名集合(取自 AecSpecname),用于在实体专业数据里反查"等级"。
// 进程内只加载一次。
const std::set<CString> &validLevels(CUIISQLiteServer *pDb)
{
static std::set<CString> s_levels;
static bool s_bLoaded = false;
if (!s_bLoaded && pDb)
{
CStringArray ar;
if (pDb->GetSelData(_T("SELECT DISTINCT 等级名称 FROM AecSpecname"), ar) > 0)
{
for (int i = 0; i < ar.GetSize(); ++i)
{
CString s(ar[i]);
s.Trim();
if (!s.IsEmpty()) s_levels.insert(s);
}
}
// 加载失败(库没打开等)时下次再试
s_bLoaded = !s_levels.empty();
}
return s_levels;
}
/*
解析组件的"等级"。
先读 PIPE_LEVEL("Level");为空时扫描专业数据里的全部字符串值,
命中 AecSpecname 里的合法等级名即采用。
实测图纸里 PIPE_LEVEL 经常为空(例如插入阀门时 command.cpp:18836 会把它清空),
但规格库里的等级是有效的,所以必须做这层兜底,否则 ITEM-CODE 永远查不出来。
*/
CString resolveLevel(AecDbObjXDataMap *pMap, CUIISQLiteServer *pDb)
{
if (!pMap) return _T("");
CString sLevel;
if (pMap->GetAt(PIPE_LEVEL, sLevel) && !sLevel.IsEmpty())
{
sLevel.Trim();
return sLevel;
}
const std::set<CString> &levels = validLevels(pDb);
if (levels.empty()) return _T("");
// 需要非 const 迭代器才能遍历专业数据
for (AecDbObjXDataMap::It it = pMap->Begin(); it != pMap->End(); ++it)
{
const AecXData &xd = it->second;
if (xd.nVt == VT_BSTR && xd.pszVal)
{
CString s(xd.pszVal);
s.Trim();
if (levels.find(s) != levels.end())
return s;
}
}
return _T("");
}
// 拼规格查询 SQL:按 等级 + 若干等值过滤列
void buildSpecSql(CString &sql, LPCTSTR pszTable, const CString &sLevel
, const std::vector<std::pair<CString, CString> > &filters)
{
sql.Format(_T("SELECT * FROM %s t")
_T(" INNER JOIN AecSpecname a")
_T(" ON t.[项目名称] = a.[项目名称] AND t.[等级名称] = a.[等级名称]")
_T(" WHERE a.[等级名称] = '%s'"), pszTable, (LPCTSTR)sLevel);
for (size_t k = 0; k < filters.size(); ++k)
{
sql += _T(" AND t.[");
sql += filters[k].first;
sql += _T("] = '");
sql += filters[k].second;
sql += _T("'");
}
}
// 按 等级 + 若干等值过滤列 查规格库,返回所有匹配行(列名 -> 值)
bool querySpecRows(CUIISQLiteServer *pDb, LPCTSTR pszTable, const CString &sLevel
, const std::vector<std::pair<CString, CString> > &filters
, std::vector<std::map<CString, CString> > &rows)
{
rows.clear();
if (!pDb || !pszTable || sLevel.IsEmpty()) return false;
CString sql;
buildSpecSql(sql, pszTable, sLevel, filters);
return pDb->GetSelDataEx(sql, rows) > 0 && !rows.empty();
}
// 按 等级 + 若干等值过滤列 查规格库,返回第一行。查不到返回 false。
bool querySpecRow(CUIISQLiteServer *pDb, LPCTSTR pszTable, const CString &sLevel
, const std::vector<std::pair<CString, CString> > &filters
, std::map<CString, CString> &row)
{
std::vector<std::map<CString, CString> > rows;
if (!querySpecRows(pDb, pszTable, sLevel, filters, rows) || rows.empty()) return false;
row = rows[0];
return true;
}
// 从规格行里取一列(大小写与前后空格不敏感)
CString specField(const std::map<CString, CString> &row, LPCTSTR pszCol)
{
CString sWant(pszCol ? pszCol : _T(""));
sWant.Trim();
for (std::map<CString, CString>::const_iterator it = row.begin(); it != row.end(); ++it)
{
CString sKey(it->first);
sKey.Trim();
if (sKey.CompareNoCase(sWant) == 0)
{
CString sVal(it->second);
sVal.Trim();
return sVal;
}
}
return _T("");
}
// 用规格行填充 PCF 的 ITEM-CODE / ITEM-DESCRIPTION / PIPING-SPEC。
// pszDescOverride 非空时用它替换规格库里的"描述"(阀门用型号名比"BB"这类类型码更可读)。
bool writeSpecAttributes(std::ostream &out, const std::map<CString, CString> &row
, LPCTSTR pszDescOverride = NULL)
{
const CString sCode = specField(row, _T("元件代码"));
CString sDesc = specField(row, _T("描述"));
if (pszDescOverride && *pszDescOverride) sDesc = pszDescOverride;
const CString sSpec = specField(row, _T("等级名称"));
if (sCode.IsEmpty() && sDesc.IsEmpty()) return false;
writePcfAttribute(out, 4, PCF_ATTR_ITEM_CODE, (LPCTSTR)sCode);
writePcfAttribute(out, 4, PCF_ATTR_ITEM_DESCRIPTION, (LPCTSTR)sDesc);
writePcfAttribute(out, 4, PCF_HEAD_PIPING_SPEC, (LPCTSTR)sSpec);
return true;
}
// 数值 -> 规格库里的"公称直径"文本(库中以整数字符串存储,如 "50")
CString dnText(const double &dn)
{
CString s;
s.Format(_T("%d"), (int)(dn + 0.5));
return s;
}
/*
pcf 文件头(仅单位部分)。PIPELINE-REFERENCE 见 pcf_pipeline_reference(),
因为设备(EQUIPMENT)定义块必须写在 PIPELINE-REFERENCE 之前。
*/
bool pcf_begin(std::ostream &out
, STR_PTR isogen_files = L"ISOGEN.FLS"
, STR_PTR bore = PCF_UNITS_MM
, STR_PTR co_ords = PCF_UNITS_MM
, STR_PTR bolt_length = PCF_UNITS_MM
, STR_PTR bolt_dia = PCF_UNITS_MM
, STR_PTR weight = PCF_UNITS_KGS
)
{
if (!out.good()) return false;
PcfRow(out, 0) << PCF_HEAD_ISOGEN_FILES << isogen_files;
PcfRow(out, 0) << PCF_HEAD_UNITS_BORE << bore;
PcfRow(out, 0) << PCF_HEAD_UNITS_CO_ORDS << co_ords;
PcfRow(out, 0) << PCF_HEAD_UNITS_BOLT_LENGTH << bolt_length;
PcfRow(out, 0) << PCF_HEAD_UNITS_BOLT_DIA << bolt_dia;
PcfRow(out, 0) << PCF_HEAD_UNITS_WEIGHT << weight;
return true;
}
// 管线引用(放在设备定义之后)
bool pcf_pipeline_reference(std::ostream &out, STR_PTR pipeline_reference)
{
if (!out.good()) return false;
PcfRow(out, 0) << PCF_HEAD_PIPELINE_REFERENCE << pipeline_reference;
return true;
}
/*
遍历模型空间,收集所有 管道/管件/设备 的 ObjectId 及其管道系统(PIPE_SYS)。
管道系统存放在实体专业数据里,键为 PIPE_SYS。
*/
void collectComponents(std::vector<AcDbObjectId> &ids, std::vector<CString> &systems)
{
ids.clear();
systems.clear();
AcDbDatabase *pDb = acdbHostApplicationServices()->workingDatabase();
if (!pDb) return;
AcDbBlockTable *pTbl = NULL;
if (Acad::eOk != pDb->getBlockTable(pTbl, AcDb::kForRead)) return;
AcDbBlockTableRecord *pBTR = NULL;
if (Acad::eOk != pTbl->getAt(ACDB_MODEL_SPACE, pBTR, AcDb::kForRead))
{
pTbl->close();
return;
}
pTbl->close();
AcDbBlockTableRecordIterator *pIter = NULL;
if (Acad::eOk != pBTR->newIterator(pIter))
{
pBTR->close();
return;
}
AcDbEntity *pEnt = NULL;
for (pIter->start(); !pIter->done(); pIter->step())
{
if (Acad::eOk != pIter->getEntity(pEnt, AcDb::kForRead) || !pEnt)
continue;
// 注意:AecDbEwPipe / AecDbXEquipment 派生自 AecDbCurveBase(→AcDbCurve),
// 与 AecDbEntityBase(→AcDbEntity) 是“兄弟”类,并非其派生类。
// 因此不能用 AecDbEntityBase::cast 去读管道的专业数据(会返回 NULL),
// 必须按具体类型分别调用 GetAt,否则管道取不到 PIPE_SYS 而被漏掉。
CString sSys;
bool bIsComponent = false;
if (AecDbEwPipe *pPipe = AecDbEwPipe::cast(pEnt))
{
pPipe->GetAt(PIPE_SYS, sSys);
bIsComponent = true;
}
else if (AecDbEwFitting *pFit = AecDbEwFitting::cast(pEnt))
{
pFit->GetAt(PIPE_SYS, sSys);
bIsComponent = true;
}
else if (AecDbEwEquipment *pEqu = AecDbEwEquipment::cast(pEnt))
{
pEqu->GetAt(PIPE_SYS, sSys);
bIsComponent = true;
}
else if (AecDbXEquipment *pXEqu = AecDbXEquipment::cast(pEnt))
{
pXEqu->GetAt(PIPE_SYS, sSys);
bIsComponent = true;
}
if (bIsComponent)
{
ids.push_back(pEnt->objectId());
systems.push_back(sSys);
}
pEnt->close();
}
DELETE_PTR(pIter);
pBTR->close();
}
// 收集图纸中出现过的所有管道系统(去重,忽略空值)
void collectSystems(std::vector<CString> &out)
{
out.clear();
std::vector<AcDbObjectId> ids;
std::vector<CString> systems;
collectComponents(ids, systems);
for (size_t k = 0; k < systems.size(); ++k)
{
if (systems[k].IsEmpty()) continue;
bool bFound = false;
for (size_t m = 0; m < out.size(); ++m)
{
if (out[m] == systems[k]) { bFound = true; break; }
}
if (!bFound) out.push_back(systems[k]);
}
}
// 按管道系统(PIPE_SYS)收集 管道/管件/设备
bool getComponentBySystem(LPCTSTR pszSys, AcDbObjectIdArray &ids)
{
if (!pszSys || _tcslen(pszSys) == 0) return false;
std::vector<AcDbObjectId> allIds;
std::vector<CString> systems;
collectComponents(allIds, systems);
for (size_t k = 0; k < allIds.size(); ++k)
{
if (systems[k] == pszSys)
ids.append(allIds[k]);
}
return !ids.isEmpty();
}
/*
* =================================================================================
* [PCF 降级说明:无 ITEM-CODE 时的保底策略]
* 适用组件:PIPE (直管)
* =================================================================================
* 如果当前管道没有物料码 (ITEM-CODE),你至少需要以下字段才能描述一段管道:
* * 1. 语法绝对必填字段(仅画几何骨架):
* - END-POINT (起点): 定义管道空间的起点三维坐标 (X, Y, Z) 及 该端的公称直径。
* - END-POINT (终点): 定义管道空间的终点三维坐标 (X, Y, Z) 及 该端的公称直径。
* *意义:有了这两个端点,引擎就能在两点之间画出一条中心线。这是底线。*
* * 2. 工程强烈建议补充的字段(为了让下游软件不报错,且 BOM 表不为空):
* - ITEM-DESCRIPTION : 极其重要!物料的文本描述(如 L"非标工艺直管"),下游软件的 BOM 会直接打印这句话。
* - WALL-THICKNESS : 壁厚数值。部分查看器(如 Navisworks)依赖“管径+壁厚”来推算真实的管道外径,不传可能会画出极细的线。
* - MATERIAL-IDENTIFIER : 材质(如 L"CS" 或 L"碳钢"),辅助下游进行管道应力分析。
* - FABRICATION-ITEM (或 ERECTION-ITEM) : 预制/现场安装标识。不带值(直接写属性名),表示这是一根工厂预制管。
* =================================================================================
*/
// 处理普通管道,非法兰管道
// 直管规格查询:按 等级 + 公称直径 在 AecSpecPipe 中查物料码/描述/等级
bool getPipeSpecRow(CUIISQLiteServer *pDb, const CString &sLevel, const double &dn
, std::map<CString, CString> &row)
{
std::vector<std::pair<CString, CString> > filters;
filters.push_back(std::make_pair(CString(_T("公称直径")), dnText(dn)));
return querySpecRow(pDb, _T("AecSpecPipe"), sLevel, filters, row);
}
// 读取数值型专业数据,兼容存为 double / 字符串 / 整数 三种情况。
// 实测部分图纸把 PIPE_DN / PIPE_THICK 存成字符串(如 "450"),
// 若只用 double 读取会失败,导致直管被静默跳过。
template <typename T>
bool getNumericValue(T *pEnt, LPCTSTR pszKey, double &dVal)
{
if (!pEnt) return false;
if (pEnt->GetAt(pszKey, dVal)) return true;
CString s;
if (pEnt->GetAt(pszKey, s) && !s.IsEmpty())
{
dVal = _ttof(s);
return true;
}
int n(0);
if (pEnt->GetAt(pszKey, n))
{
dVal = (double)n;
return true;
}
return false;
}
// pStart/pEnd 用于"长直管中点切断":把一根管按参数段拆成多根虚拟管输出,
// 传 NULL 时按管子自身的起止点输出。
bool pcf_pipe_normal(CUIISQLiteServer *pDb, AecDbEwPipe *pPipe, std::ostream &out
, const AcGePoint3d *pStart = NULL, const AcGePoint3d *pEnd = NULL)
{
if (!pPipe || !out.good())
return false;
AcGePoint3d ptStart = pStart ? *pStart : pPipe->StartPoint();
AcGePoint3d ptEnd = pEnd ? *pEnd : pPipe->EndPoint();
// 公称直径:先读专业数据(兼容字符串),再退回 PipeParam
double bore(.0);
if (!getNumericValue(pPipe, PIPE_DN, bore) || bore <= 0.0)
{
WPipe::PipeParam *pParam = pPipe->GetPipeParam();
if (pParam) bore = pParam->m_dDN;
}
if (bore <= 0.0) return false;
// 壁厚:可选,缺失不阻断导出
double thickness(.0);
if (!getNumericValue(pPipe, PIPE_THICK, thickness) || thickness <= 0.0)
{
WPipe::PipeParam *pParam = pPipe->GetPipeParam();
if (pParam) thickness = pParam->m_dThick;
}
// 等级:先读 PIPE_LEVEL,为空时在专业数据里反查合法等级名
AecDbObjXDataMap *pMap = pPipe->GetSpecialtyData(AcDb::kForRead);
const CString level = resolveLevel(pMap, pDb);
// 写入组件主类标 (顶格,无缩进)
PcfRow(out, 0) << PCF_COMP_PIPE;
// 写入起止坐标与管径 (必须紧跟在 PIPE 后面,缩进 4 格)
PcfRow(out, 4) << PCF_PT_END_POINT << ptStart << bore;
PcfRow(out, 4) << PCF_PT_END_POINT << ptEnd << bore;
// 写入壁厚,保证在没有item-code和piping-spec时能够正常显示
if (thickness > 0.0)
PcfRow(out, 4) << PCF_ATTR_WALL_THICKNESS << thickness;
// 等级 -> 规格库 -> 物料码 / 描述 / 等级
if (!level.IsEmpty())
{
std::map<CString, CString> row;
if (getPipeSpecRow(pDb, level, bore, row))
writeSpecAttributes(out, row);
}
// 预制管件
PcfRow(out, 4) << PCF_ATTR_FABRICATION_ITEM;
out << "\n";
return true;
}
// ============================ Phase 2: 管件导出 ============================
// 读取管件某接口的管径:优先接口自身 dSize1,缺失时退回管件参数
double fitInfBore(AecDbEwFitting *pFit, const AecInterface &inf, int nInf)
{
double d = inf.dSize1;
if (d <= 0.0 && pFit)
{
WPipe::PipeParam *pParam = pFit->GetPipeParam(nInf);
if (pParam) d = pParam->m_dDN;
}
return d;
}
// 管件壁厚:优先专业数据(兼容字符串),再退回管件参数
double fitThickness(AecDbEwFitting *pFit)
{
double t(0.0);
if (!getNumericValue(pFit, PIPE_THICK, t) || t <= 0.0)
{
WPipe::PipeParam *pParam = pFit ? pFit->GetPipeParam(0) : NULL;
if (pParam) t = pParam->m_dThick;
}
return t;
}
// 变径类型:集中式/偏心式,用于选择 PCF 组件名
STR_PTR getReducerTypeName(AecDbEwFitting *pFit)
{
WPipe::PipeParam *pParam = pFit ? pFit->GetPipeParam(0) : NULL;
if (pParam && pParam->m_eRed != WPipe::eCen)
return PCF_COMP_REDUCER_ECC;
return PCF_COMP_REDUCER_CONC;
}
// 弯头实际夹角:两接口方向向量的夹角(0~180°)。
// 90° 弯头约为 90°;45° 弯头约为 135°(即转弯 45°)。取不到返回 -1。
double fitBendAngle(AecDbEwFitting *pFit)
{
if (!pFit) return -1.0;
AcArray<AecInterface> arInfs;
if (!pFit->GetInterface(arInfs) || arInfs.length() < 2) return -1.0;
AcGeVector3d u = arInfs.at(0).vtDir;
AcGeVector3d v = arInfs.at(1).vtDir;
if (u.length() < 1e-9 || v.length() < 1e-9) return -1.0;
u.normalize();
v.normalize();
double c = u.dotProduct(v);
if (c > 1.0) c = 1.0;
if (c < -1.0) c = -1.0;
return acos(c) * 180.0 / 3.14159265358979323846;
}
/*
管件在规格库里的"类型"候选名(按优先级)。
实测 AecSpecFit.类型 只有:
弯头 / 三通 / 半管接头 / 补强圈 / 变径 / 管帽 / 斜三通 / 90度弯头 / 45度弯头 / 同心变径 / 偏心变径
而 AecSpecFlange.类型 只有 PL。
*/
void fitTypeCandidates(int nType, AecDbEwFitting *pFit, std::vector<CString> &names)
{
names.clear();
switch (nType)
{
case eElbow:
case eReduceElbow:
// 弯头的 90/45 度在 getFitSpecRow 里按实测夹角单独挑行,这里只列候选名。
names.push_back(_T("弯头"));
names.push_back(_T("90度弯头"));
names.push_back(_T("45度弯头"));
break;
case eReduce:
if (pFit && wcscmp(getReducerTypeName(pFit), PCF_COMP_REDUCER_ECC) == 0)
names.push_back(_T("偏心变径"));
else
names.push_back(_T("同心变径"));
names.push_back(_T("变径"));
break;
case eTee:
names.push_back(_T("三通"));
break;
case eCross:
names.push_back(_T("四通"));
names.push_back(_T("三通"));
break;
case eLateral:
case eWye:
names.push_back(_T("斜三通"));
names.push_back(_T("三通"));
break;
case eWcap:
names.push_back(_T("管帽"));
break;
case eRP:
names.push_back(_T("补强圈"));
break;
case eHP:
names.push_back(_T("半管接头"));
break;
case eFlange:
names.push_back(_T("PL"));
break;
}
}
// 管件规格查询:法兰走 AecSpecFlange,其余走 AecSpecFit。
// 普通管件按类型候选名逐个试,最后退化为"只按管径"。
// 弯头特殊处理:90/45 度有时写在"类型"列、有时只在"描述"里区分,
// 需取回全部候选行后按实测夹角挑选。
bool getFitSpecRow(CUIISQLiteServer *pDb, AecDbEwFitting *pFit
, const CString &sLevel, const double &dn, std::map<CString, CString> &row)
{
if (!pFit || sLevel.IsEmpty() || dn <= 0.0) return false;
const int nType = pFit->GetFittingType();
const bool bFlange = (nType == eFlange);
LPCTSTR pszTable = bFlange ? _T("AecSpecFlange") : _T("AecSpecFit");
// ---- 弯头:按实测夹角在 90度/45度 行里挑 ----
if (nType == eElbow || nType == eReduceElbow)
{
std::vector<std::map<CString, CString> > rows;
const LPCTSTR aNames[3] = { _T("弯头"), _T("90度弯头"), _T("45度弯头") };
for (int i = 0; i < 3; ++i)
{
std::vector<std::pair<CString, CString> > filters;
filters.push_back(std::make_pair(CString(_T("公称直径")), dnText(dn)));
filters.push_back(std::make_pair(CString(_T("类型")), CString(aNames[i])));
std::vector<std::map<CString, CString> > part;
if (querySpecRows(pDb, pszTable, sLevel, filters, part))
rows.insert(rows.end(), part.begin(), part.end());
}
if (rows.empty())
{
std::vector<std::pair<CString, CString> > filters;
filters.push_back(std::make_pair(CString(_T("公称直径")), dnText(dn)));
querySpecRows(pDb, pszTable, sLevel, filters, rows);
}
if (rows.empty()) return false;
const double ang = fitBendAngle(pFit);
const bool bWant45 = (ang >= 112.5); // 夹角约 135° -> 45 度弯头
int nBest = -1;
for (size_t i = 0; i < rows.size(); ++i)
{
CString sAll = specField(rows[i], _T("类型")) + specField(rows[i], _T("描述"));
const bool bIs45 = (sAll.Find(_T("45度")) >= 0);
const bool bIs90 = (sAll.Find(_T("90度")) >= 0);
if (bWant45 && bIs45) { nBest = (int)i; break; }
if (!bWant45 && bIs90) { nBest = (int)i; break; }
}
if (nBest < 0) nBest = 0;
row = rows[nBest];
return true;
}
// ---- 其余管件:按类型候选名逐个试 ----
std::vector<CString> names;
fitTypeCandidates(nType, pFit, names);
for (size_t k = 0; k < names.size(); ++k)
{
std::vector<std::pair<CString, CString> > filters;
filters.push_back(std::make_pair(CString(_T("公称直径")), dnText(dn)));
filters.push_back(std::make_pair(CString(_T("类型")), names[k]));
if (querySpecRow(pDb, pszTable, sLevel, filters, row))
return true;
}
// 再退化为只按管径(类型名对不上时的兜底)
{
std::vector<std::pair<CString, CString> > filters;
filters.push_back(std::make_pair(CString(_T("公称直径")), dnText(dn)));
if (querySpecRow(pDb, pszTable, sLevel, filters, row))
return true;
}
// 法兰表查不到时,退回管件表再试一次
if (bFlange)
{
std::vector<std::pair<CString, CString> > filters;
filters.push_back(std::make_pair(CString(_T("公称直径")), dnText(dn)));
if (querySpecRow(pDb, _T("AecSpecFit"), sLevel, filters, row))
return true;
}
return false;
}
// 写出管件公共属性:壁厚 / 规格库(物料码,描述,等级) / 预制标记
void writeFittingCommon(CUIISQLiteServer *pDb, AecDbEwFitting *pFit
, const double &bore, std::ostream &out)
{
double thickness = fitThickness(pFit);
if (thickness > 0.0)
PcfRow(out, 4) << PCF_ATTR_WALL_THICKNESS << thickness;
AecDbObjXDataMap *pMap = pFit->GetSpecialtyData(AcDb::kForRead);
const CString level = resolveLevel(pMap, pDb);
if (!level.IsEmpty() && pDb && bore > 0.0)
{
std::map<CString, CString> row;
if (getFitSpecRow(pDb, pFit, level, bore, row))
writeSpecAttributes(out, row);
}
// 预制管件
PcfRow(out, 4) << PCF_ATTR_FABRICATION_ITEM;
}
// 求两条射线(点+方向)在空间中的交点。
// 弯头两端方向一般相交(如 90° 弯头),相交时返回角点;接近平行时退化为两端点中点。
AcGePoint3d intersectRays(const AcGePoint3d &p0, const AcGeVector3d &d0
, const AcGePoint3d &p1, const AcGeVector3d &d1)
{
AcGeVector3d u(d0), v(d1);
u.normalize();
v.normalize();
AcGeVector3d w(p0 - p1);
const double a = u.dotProduct(u);
const double b = u.dotProduct(v);
const double c = v.dotProduct(v);
const double d = u.dotProduct(w);
const double e = v.dotProduct(w);
const double denom = a * c - b * b;
if (fabs(denom) < 1e-9)
return AcGePoint3d((p0.x + p1.x) * 0.5, (p0.y + p1.y) * 0.5, (p0.z + p1.z) * 0.5);
const double sc = (b * e - c * d) / denom;
return p0 + u * sc;
}
// 方向向量 -> PCF 方位(NORTH/SOUTH/EAST/WEST/UP/DOWN)
STR_PTR dirToOrientation(const AcGeVector3d &v)
{
const double ax = fabs(v.x), ay = fabs(v.y), az = fabs(v.z);
if (az >= ax && az >= ay) return (v.z >= 0.0) ? L"UP" : L"DOWN";
if (ax >= ay) return (v.x >= 0.0) ? L"EAST" : L"WEST";
return (v.y >= 0.0) ? L"NORTH" : L"SOUTH";
}
/*
* 通用:写出组件头与点位(不含属性)。
* 参考 PCF Reference Guide 的点位要求:
* 1 个接口 -> END-POINT
* 2 个接口 -> END-POINT x2
* 2 个接口 + bCentre -> END-POINT x2 + CENTRE-POINT
* 3 个接口 -> END-POINT x2 + BRANCH1-POINT
* 4+ 个接口 -> END-POINT x2 + BRANCH1/2-POINT
* 接口按 nLevel 排序:0/1=主管两端,>=2=支管。bores 与 arInfs 一一对应。
*/
bool writeComponentPoints(std::ostream &out, AcArray<AecInterface> &arInfs
, const std::vector<double> &bores, STR_PTR compType, bool bCentre, double &dBore)
{
if (!out.good() || arInfs.isEmpty()) return false;
// 按 nLevel 升序排序(插入排序,接口数很少)
std::vector<int> arOrder;
for (int i = 0; i < arInfs.length(); ++i)
{
int lv = arInfs.at(i).nLevel;
size_t nPos = arOrder.size();
for (size_t k = 0; k < arOrder.size(); ++k)
{
if (lv < arInfs.at(arOrder[k]).nLevel) { nPos = k; break; }
}
arOrder.insert(arOrder.begin() + nPos, i);
}
const int nCount = (int)arOrder.size();
const int nMain = nCount >= 2 ? 2 : 1;
PcfRow(out, 0) << compType;
dBore = 0.0;
for (int i = 0; i < nMain; ++i)
{
const int idx = arOrder[i];
double d = (idx < (int)bores.size()) ? bores[idx] : arInfs.at(idx).dSize1;
if (dBore <= 0.0) dBore = d;
PcfRow(out, 4) << PCF_PT_END_POINT << arInfs.at(idx).ptPos << d;
}
if (bCentre && nCount >= 2)
{
const AecInterface &i0 = arInfs.at(arOrder[0]);
const AecInterface &i1 = arInfs.at(arOrder[1]);
PcfRow(out, 4) << PCF_PT_CENTRE_POINT
<< intersectRays(i0.ptPos, i0.vtDir, i1.ptPos, i1.vtDir);
}
for (int i = 2; i < nCount; ++i)
{
const int idx = arOrder[i];
double d = (idx < (int)bores.size()) ? bores[idx] : arInfs.at(idx).dSize1;
STR_PTR ptType = (i == 2) ? PCF_PT_BRANCH1_POINT : PCF_PT_BRANCH2_POINT;
PcfRow(out, 4) << ptType << arInfs.at(idx).ptPos << d;
}
return true;
}
// 管件:接口管径已解析后写入点位,再补管件属性
bool pcf_genericFitting(CUIISQLiteServer *pDb, AecDbEwFitting *pFit
, STR_PTR compType, std::ostream &out, bool bCentre)
{
if (!pFit || !out.good()) return false;
AcArray<AecInterface> arInfs;
if (!pFit->GetInterface(arInfs) || arInfs.isEmpty()) return false;
std::vector<double> bores;
for (int i = 0; i < arInfs.length(); ++i)
bores.push_back(fitInfBore(pFit, arInfs.at(i), i));
double dBore = 0.0;
if (!writeComponentPoints(out, arInfs, bores, compType, bCentre, dBore))
return false;
writeFittingCommon(pDb, pFit, dBore, out);
out << "\n";
return true;
}
// ============================ Phase 3: 设备导出 ============================
// 阀门类型枚举(AecEqui::ValveType, EarThwormDef.h:2267) -> 规格库 AecSpecValve.[类别] 名称。
// 名称须与 g_vlvType[] (PipelineCmd/command.cpp:158) 及 Plumbing.db 的 [类别] 值一致。
LPCTSTR valveCategoryName(int nType)
{
switch (nType)
{
case 1: return _T("闸阀");
case 2: return _T("截止阀");
case 3: return _T("球阀");
case 4: return _T("止回阀");
case 5: return _T("蝶阀");
case 6: return _T("角阀");
case 7: return _T("安全阀");
case 8: return _T("三通阀");
case 9: return _T("四通阀");
case 10: return _T("调节阀");
case 11: return _T("疏水阀");
case 12: return _T("旋塞阀");
case 13: return _T("隔膜阀");
case 14: return _T("减压阀");
case 15: return _T("电磁阀");
case 16: return _T("橡胶接头");
case 17: return _T("排气阀");
case 18: return _T("排泥阀");
case 19: return _T("浆液阀");
case 20: return _T("闸门");
case 21: return _T("孔板");
case 22: return _T("桶式过滤器");
case 23: return _T("临时过滤器");
case 24: return _T("T型过滤器");
case 25: return _T("T型过滤器");
case 26: return _T("视镜");
case 27: return _T("流量计");
case 28: return _T("阻火器");
case 31: return _T("伸缩接头");
case 32: return _T("传力接头");
default: return _T("");
}
}
// 写出阀门/管道附件类设备的公共属性:壁厚 / 名称 / 阀门规格库 / 预制标记
void writeEquipmentCommon(CUIISQLiteServer *pDb, AecDbEwEquipment *pEqu
, const double &bore, std::ostream &out)
{
double thickness(0.0);
if (getNumericValue(pEqu, PIPE_THICK, thickness) && thickness > 0.0)
PcfRow(out, 4) << PCF_ATTR_WALL_THICKNESS << thickness;
// 名称:阀门型号名(如 D373H法兰硬密封蝶阀DN450),比规格库"描述"更具可读性,用作 ITEM-DESCRIPTION
CString sName;
pEqu->GetAt(KEY_NAME, sName);
// 阀门规格库:按 等级 + 类别(阀门类型) + 公称直径 在 AecSpecValve 中查物料码/描述/等级。
// 同一等级+管径下闸阀/止回阀/蝶阀等多行并存,必须再按"类别"过滤,否则会取错行。
bool bSpec = false;
AecDbObjXDataMap *pMap = pEqu->GetSpecialtyData(AcDb::kForRead);
const CString level = resolveLevel(pMap, pDb);
if (!level.IsEmpty() && pDb && bore > 0.0)
{
int nVlvType = -1;
if (pMap) pMap->GetAt(PIPE_VALVE, nVlvType);
const LPCTSTR pszCat = valveCategoryName(nVlvType);
// 必须按 类别 精确匹配:同一等级+管径下闸阀/止回阀/蝶阀等并存,
// 类别未知或库中无此类别时宁可不写物料码,也不能退化为按管径取到别的阀种。
std::map<CString, CString> row;
bool bFound = false;
if (pszCat && *pszCat)
{
std::vector<std::pair<CString, CString> > filters;
filters.push_back(std::make_pair(CString(_T("公称直径")), dnText(bore)));
filters.push_back(std::make_pair(CString(_T("类别")), CString(pszCat)));
bFound = querySpecRow(pDb, _T("AecSpecValve"), level, filters, row);
}
if (bFound)
bSpec = writeSpecAttributes(out, row, (LPCTSTR)sName);
}
if (!bSpec)
writePcfAttribute(out, 4, PCF_ATTR_ITEM_DESCRIPTION, (LPCTSTR)sName);
PcfRow(out, 4) << PCF_ATTR_FABRICATION_ITEM;
}
// 阀门/管道附件类设备(AecDbEwEquipment):作为 VALVE 组件导出
bool pcf_ewEquipment(CUIISQLiteServer *pDb, AecDbEwEquipment *pEqu, std::ostream &out)
{
if (!pEqu || !out.good()) return false;
AcArray<AecInterface> arInfs;
if (!pEqu->GetInterface(arInfs) || arInfs.isEmpty()) return false;
std::vector<double> bores;
for (int i = 0; i < arInfs.length(); ++i)
bores.push_back(arInfs.at(i).dSize1);
double dBore = 0.0;
if (!writeComponentPoints(out, arInfs, bores, PCF_COMP_VALVE, true, dBore))
return false;
writeEquipmentCommon(pDb, pEqu, dBore, out);
out << "\n";
return true;
}
// 容器/设备类(AecDbXEquipment):导出为 EQUIPMENT 定义块(含 NOZZLE),需放在 PIPELINE-REFERENCE 之前
bool pcf_xEquipment(CUIISQLiteServer *pDb, AecDbXEquipment *pXEqu, std::ostream &out)
{
if (!pXEqu || !out.good()) return false;
AcGePoint3d ptIns;
AcGeVector3d vtDir;
pXEqu->GetInsert(ptIns, vtDir);
// 设备名:优先 NAME,其次唯一名,最后给个兜底
CString sName;
pXEqu->GetAt(KEY_NAME, sName);
if (sName.IsEmpty()) sName = pXEqu->getUniqueName();
if (sName.IsEmpty()) sName = _T("EQUIPMENT");
PcfRow(out, 0) << PCF_COMP_EQUIPMENT;
PcfRow(out, 4) << PCF_KW_REFERENCE << (LPCTSTR)sName;
PcfRow(out, 4) << PCF_KW_LOCATION << ptIns;
PcfRow(out, 4) << PCF_KW_ORIENTATION << dirToOrientation(vtDir);
// 管嘴
AcArray<AecInterface> arInfs;
if (pXEqu->GetInterface(arInfs))
{
for (int i = 0; i < arInfs.length(); ++i)
{
const AecInterface &inf = arInfs.at(i);
CString sNoz;
sNoz.Format(_T("%sN%d"), (LPCTSTR)sName, i + 1);
PcfRow(out, 4) << PCF_KW_NOZZLE;
PcfRow(out, 8) << PCF_KW_REFERENCE << (LPCTSTR)sNoz;
PcfRow(out, 8) << PCF_KW_DIRECTION << dirToOrientation(inf.vtDir);
PcfRow(out, 8) << PCF_PT_CO_ORDS << inf.ptPos << inf.dSize1;
}
}
out << "\n";
return true;
}
// 管件按 EFittingType 分发到通用导出
bool pcf_fitting(CUIISQLiteServer *pDb, AecDbEwFitting *pFit, std::ostream &out)
{
if (!pFit) return false;
switch (pFit->GetFittingType())
{
case eElbow:
case eReduceElbow:
return pcf_genericFitting(pDb, pFit, PCF_COMP_ELBOW, out, true);
case eReduce:
return pcf_genericFitting(pDb, pFit, getReducerTypeName(pFit), out, false);
case eTee:
case eWye:
case eLateral:
return pcf_genericFitting(pDb, pFit, PCF_COMP_TEE, out, false);
case eCross:
return pcf_genericFitting(pDb, pFit, PCF_COMP_CROSS, out, false);
case eFlange:
return pcf_genericFitting(pDb, pFit, PCF_COMP_FLANGE, out, false);
case eWcap:
return pcf_genericFitting(pDb, pFit, PCF_COMP_CAP, out, false);
case eSPB://八字盲板
return pcf_genericFitting(pDb, pFit, PCF_COMP_BLIND_FLANGE, out, false);
case eGastket://垫片
return pcf_genericFitting(pDb, pFit, PCF_COMP_GASKET, out, false);
case eFlxJoin://箍
case eThrough://直通
case eUJoin://活接
case eRJoin://缩接
case eOJoin://外螺纹接头
case eIJoin://内螺纹接头
return pcf_genericFitting(pDb, pFit, PCF_COMP_COUPLING, out, false);
default:
break;
}
return false;
}
// ============================ Phase 4: 支吊架导出 ============================
// 点到线段的最短距离
double distPointSegment(const AcGePoint3d &p, const AcGePoint3d &a, const AcGePoint3d &b)
{
AcGeVector3d ab(b - a);
const double len2 = ab.lengthSqrd();
if (len2 < 1e-12) return p.distanceTo(a);
double t = AcGeVector3d(p - a).dotProduct(ab) / len2;
if (t < 0.0) t = 0.0;
else if (t > 1.0) t = 1.0;
return p.distanceTo(a + ab * t);
}
// 取组件主轴线段(两个 nLevel<2 的接口);接口不足两个时退化为一个点
bool mainAxisSegment(AcArray<AecInterface> &arInfs, AcGePoint3d &a, AcGePoint3d &b)
{
if (arInfs.isEmpty()) return false;
std::vector<const AecInterface *> arMain;
for (int i = 0; i < arInfs.length(); ++i)
{
if (arInfs.at(i).nLevel < 2) arMain.push_back(&arInfs.at(i));
}
if (arMain.empty()) arMain.push_back(&arInfs.at(0));
a = arMain[0]->ptPos;
b = (arMain.size() >= 2) ? arMain[1]->ptPos : a;
return true;
}
// 支吊架既没有 PIPE_SYS,也没有宿主管道句柄,只能按空间位置就近归属到组件,
// 再取该组件的管道系统。这里把组件的中心线段连同系统名收集起来备用。
struct SupportTarget
{
CString sys;
AcGePoint3d a;
AcGePoint3d b;
};
void collectSupportTargets(std::vector<SupportTarget> &targets)
{
targets.clear();
AcDbDatabase *pDb = acdbHostApplicationServices()->workingDatabase();
if (!pDb) return;
AcDbBlockTable *pTbl = NULL;
if (Acad::eOk != pDb->getBlockTable(pTbl, AcDb::kForRead)) return;
AcDbBlockTableRecord *pBTR = NULL;
if (Acad::eOk != pTbl->getAt(ACDB_MODEL_SPACE, pBTR, AcDb::kForRead))
{
pTbl->close();
return;
}
pTbl->close();
AcDbBlockTableRecordIterator *pIter = NULL;
if (Acad::eOk != pBTR->newIterator(pIter))
{
pBTR->close();
return;
}
AcDbEntity *pEnt = NULL;
for (pIter->start(); !pIter->done(); pIter->step())
{
if (Acad::eOk != pIter->getEntity(pEnt, AcDb::kForRead) || !pEnt)
continue;
SupportTarget t;
bool bOk = false;
if (AecDbEwPipe *pPipe = AecDbEwPipe::cast(pEnt))
{
t.a = pPipe->StartPoint();
t.b = pPipe->EndPoint();
pPipe->GetAt(PIPE_SYS, t.sys);
bOk = true;
}
else if (AecDbEwFitting *pFit = AecDbEwFitting::cast(pEnt))
{
AcArray<AecInterface> arInfs;
if (pFit->GetInterface(arInfs) && mainAxisSegment(arInfs, t.a, t.b))
{
pFit->GetAt(PIPE_SYS, t.sys);
bOk = true;
}
}
else if (AecDbEwEquipment *pEqu = AecDbEwEquipment::cast(pEnt))
{
AcArray<AecInterface> arInfs;
if (pEqu->GetInterface(arInfs) && mainAxisSegment(arInfs, t.a, t.b))
{
pEqu->GetAt(PIPE_SYS, t.sys);
bOk = true;
}
}
else if (AecDbXEquipment *pXEqu = AecDbXEquipment::cast(pEnt))
{
AcArray<AecInterface> arInfs;
if (pXEqu->GetInterface(arInfs) && mainAxisSegment(arInfs, t.a, t.b))
{
pXEqu->GetAt(PIPE_SYS, t.sys);
bOk = true;
}
}
pEnt->close();
if (bOk && !t.sys.IsEmpty())
targets.push_back(t);
}
DELETE_PTR(pIter);
pBTR->close();
}
// 支吊架(插入点 pt)是否属于指定管道系统:取最近组件所属的系统
bool supportBelongsToSystem(const AcGePoint3d &pt, LPCTSTR pszSys
, const std::vector<SupportTarget> &targets)
{
double dMin = 0.0;
CString sBest;
for (size_t k = 0; k < targets.size(); ++k)
{
const double d = distPointSegment(pt, targets[k].a, targets[k].b);
if (sBest.IsEmpty() || d < dMin)
{
dMin = d;
sBest = targets[k].sys;
}
}
return (sBest == pszSys);
}
// 收集图纸中所有支吊架(AecDbPipeHanger, DXF 名 AEC_PIPEHNGR)
void collectPipeHangers(std::vector<AcDbObjectId> &ids)
{
ids.clear();
AcDbDatabase *pDb = acdbHostApplicationServices()->workingDatabase();
if (!pDb) return;
AcDbBlockTable *pTbl = NULL;
if (Acad::eOk != pDb->getBlockTable(pTbl, AcDb::kForRead)) return;
AcDbBlockTableRecord *pBTR = NULL;
if (Acad::eOk != pTbl->getAt(ACDB_MODEL_SPACE, pBTR, AcDb::kForRead))
{
pTbl->close();
return;
}
pTbl->close();
AcDbBlockTableRecordIterator *pIter = NULL;
if (Acad::eOk != pBTR->newIterator(pIter))
{
pBTR->close();
return;
}
AcDbEntity *pEnt = NULL;
for (pIter->start(); !pIter->done(); pIter->step())
{
if (Acad::eOk != pIter->getEntity(pEnt, AcDb::kForRead) || !pEnt)
continue;
if (AecDbPipeHanger::cast(pEnt))
ids.push_back(pEnt->objectId());
pEnt->close();
}
DELETE_PTR(pIter);
pBTR->close();
}
/*
支吊架 -> SUPPORT 组件块(单坐标)。
参考 PCF Reference Guide:
SUPPORT 必填 CO-ORDS(含公称尺寸) + SKEY;默认 SKEY 为 01HG(双平行线支架符号);
CATEGORY 取 ERECTION(现场安装);名称走 NAME / ITEM-DESCRIPTION。
支架类型(0~4)不是 PCF 标准属性,借 COMPONENT-ATTRIBUTE0 带出,方便下游重映射符号。
*/
bool pcf_support(AecDbPipeHanger *pHanger, std::ostream &out)
{
if (!pHanger || !out.good()) return false;
const AcGePoint3d pt = pHanger->position();
double dn(0.0);
getNumericValue(pHanger, PCF_KEY_SUPPORT_DN, dn);
PcfRow(out, 0) << PCF_COMP_SUPPORT;
if (dn > 0.0)
PcfRow(out, 4) << PCF_PT_CO_ORDS << pt << dn;
else
PcfRow(out, 4) << PCF_PT_CO_ORDS << pt;
PcfRow(out, 4) << PCF_ATTR_SKEY << PCF_SKEY_SUPPORT_DEFAULT;
CString sName;
pHanger->GetAt(KEY_NAME, sName);
writePcfAttribute(out, 4, PCF_ATTR_NAME, (LPCTSTR)sName);
writePcfAttribute(out, 4, PCF_ATTR_ITEM_DESCRIPTION, (LPCTSTR)sName);
int nMode(-1);
if (pHanger->GetAt(PCF_KEY_SUPPORT_MODE, nMode))
{
CString sMode;
sMode.Format(_T("%d"), nMode);
writePcfAttribute(out, 4, PCF_ATTR_COMPONENT_ATTRIBUTE0, (LPCTSTR)sMode);
}
PcfRow(out, 4) << PCF_KW_CATEGORY << PCF_CAT_ERECTION;
out << "\n";
return true;
}
// ============================ Phase 5: 自动分图 ============================
// 接图标识的 PCF 属性名。写在下游组件属性之后,随该组件一起被识别。
STR_PTR PCF_ATTR_CONTINUATION = L"MESSAGE-TEXT";
// ---- 5.1 组件通用读取 ----
// 候选"管号"键,按优先级排列。
// 注意:工程里"管号"存在 PIPE_NO("No"),但"改管"路径
// (PipelineCmd\AecPipeLine.cpp:3647) 会用"顺序号"把 PIPE_NO 覆盖掉,
// 所以不能硬编码,由 probeGroupKey() 实测后挑一个可用的键。
LPCTSTR g_pszNoKeys[] =
{
PIPE_NO, // "No" 管号(可能已被顺序号覆盖)
PIPE_LINENO, // "LineNo" 管线号(PID 用,现场常为空)
PIPE_SPEC, // "Spec" 管子上标
KEY_NO, // "No." 顺序号
PIPE_UNITNO, // "UnitNo" 单元号
PIPE_ZONE, // "Zone" 区号
};
template <typename T>
bool readKeyString(T *pEnt, LPCTSTR pszKey, CString &s)
{
s.Empty();
if (!pEnt || !pszKey) return false;
if (pEnt->GetAt(pszKey, s) && !s.IsEmpty()) return true;
s.Empty();
return false;
}
// 管道/管件/设备是"兄弟"类,必须分别 cast 才能读到专业数据
bool readComponentKey(AcDbObject *pObj, LPCTSTR pszKey, CString &s)
{
s.Empty();
if (!pObj) return false;
if (AecDbEwPipe *pPipe = AecDbEwPipe::cast(pObj))
return readKeyString(pPipe, pszKey, s);
if (AecDbEwFitting *pFit = AecDbEwFitting::cast(pObj))
return readKeyString(pFit, pszKey, s);
if (AecDbEwEquipment *pEqu = AecDbEwEquipment::cast(pObj))
return readKeyString(pEqu, pszKey, s);
if (AecDbXEquipment *pXEqu = AecDbXEquipment::cast(pObj))
return readKeyString(pXEqu, pszKey, s);
return false;
}
// 组件的 EFittingType;非本插件组件返回 -1
int getCompType(AcDbObject *pObj)
{
if (!pObj) return -1;
if (AecDbEwPipe::cast(pObj)) return ePipe;
if (AecDbEwFitting *pFit = AecDbEwFitting::cast(pObj)) return pFit->GetFittingType();
if (AecDbEwEquipment::cast(pObj)) return eEquipment;
if (AecDbXEquipment::cast(pObj)) return eEquipment;
return -1;
}
// ---- 5.3 网络图与切点 ----
// 网络里的一个组件节点(长直管被切断时,同一实体可对应多个节点)
struct NetNode
{
AcDbObjectId id;
int nType; // EFITTINGTYPE
AcGePoint3d a, b; // 该节点占据的主轴线段
AcArray<AecInterface> infs;
int nSplitIdx; // 对应的 PipeSplit 下标(用于把切段精确落到图上)
NetNode() : nType(-1), nSplitIdx(-1) {}
};
// 一个可用于连接的接口点
struct ConnPt
{
AcGePoint3d pt;
int nConn; // AecInterface::nConnectType: 0焊接 1承插 2螺纹 3法兰 4卡箍
double bore; // 接口管径
ConnPt() : nConn(-1), bore(0.0) {}
};
// 两个组件之间的连接 —— 潜在切点 / 焊缝
struct NetJoint
{
int nA, nB; // NetNode 下标
AcGePoint3d pt; // 接口位置
bool bFlange; // 至少一端是法兰 -> 法兰面
bool bWeld; // 焊接接口 -> 需要输出 WELD 记录
int nConnA, nConnB; // 两侧接口的连接类型
double dBore; // 接口管径
NetJoint() : nA(-1), nB(-1), bFlange(false), bWeld(false)
, nConnA(-1), nConnB(-1), dBore(0.0) {}
};
// 切点优先级:法兰面(2) > 现场焊缝(1) > 普通接口(0)
int jointPriority(const NetJoint &j)
{
if (j.bFlange) return 2;
if (j.bWeld) return 1;
return 0;
}
// 直管某端(起点/终点)的接口连接类型;接口数据不足时返回 -1
int pipeEndConnect(const NetNode &nd, const AcGePoint3d &pt)
{
int nBest = -1;
double dMin = 1e18;
for (int i = 0; i < nd.infs.length(); ++i)
{
const double d = nd.infs.at(i).ptPos.distanceTo(pt);
if (d < dMin) { dMin = d; nBest = nd.infs.at(i).nConnectType; }
}
return nBest;
}
// 直管某端的接口管径
double pipeEndBore(const NetNode &nd, const AcGePoint3d &pt)
{
double dBore = 0.0;
double dMin = 1e18;
for (int i = 0; i < nd.infs.length(); ++i)
{
const double d = nd.infs.at(i).ptPos.distanceTo(pt);
if (d < dMin) { dMin = d; dBore = nd.infs.at(i).dSize1; }
}
return dBore;
}
// 节点上所有可用于连接的接口(带连接类型与管径)
void nodeConnPoints(const NetNode &nd, std::vector<ConnPt> &pts)
{
pts.clear();
if (nd.nType == ePipe)
{
ConnPt c0, c1;
c0.pt = nd.a; c0.nConn = pipeEndConnect(nd, nd.a); c0.bore = pipeEndBore(nd, nd.a);
c1.pt = nd.b; c1.nConn = pipeEndConnect(nd, nd.b); c1.bore = pipeEndBore(nd, nd.b);
pts.push_back(c0);
pts.push_back(c1);
return;
}
for (int i = 0; i < nd.infs.length(); ++i)
{
ConnPt c;
c.pt = nd.infs.at(i).ptPos;
c.nConn = nd.infs.at(i).nConnectType;
c.bore = nd.infs.at(i).dSize1;
pts.push_back(c);
}
if (pts.empty())
{
ConnPt c0, c1;
c0.pt = nd.a;
pts.push_back(c0);
if (nd.a.distanceTo(nd.b) > 1e-9) { c1.pt = nd.b; pts.push_back(c1); }
}
}
// 接口重合判定的容差(图纸单位)。本图纸里相邻组件的接口位置是严格重合的,
// 取 0.01 足够;不再借用底层按垫片尺寸(dGasket)放大的容差。
double netTolerance()
{
double dUnit = 1.0;
struct resbuf rb;
if (acedGetVar(_T("INSUNITS"), &rb) == RTNORM && rb.resval.rint == AcDb::kUnitsMeters)
dUnit = 0.001;
return __max(0.01 * dUnit, 0.01);
}
// 读一个实体的接口与主轴线段(接口不足两个时退化为点)
bool readAxisSegment(AcDbObject *pObj, AcArray<AecInterface> &infs
, AcGePoint3d &a, AcGePoint3d &b)
{
if (AecDbEwPipe *pPipe = AecDbEwPipe::cast(pObj))
{
a = pPipe->StartPoint();
b = pPipe->EndPoint();
pPipe->GetInterface(infs);
return true;
}
if (AecDbEwFitting *pFit = AecDbEwFitting::cast(pObj))
{
pFit->GetInterface(infs);
}
else if (AecDbEwEquipment *pEqu = AecDbEwEquipment::cast(pObj))
{
pEqu->GetInterface(infs);
}
else if (AecDbXEquipment *pXEqu = AecDbXEquipment::cast(pObj))
{
pXEqu->GetInterface(infs);
}
else
{
return false;
}
if (!mainAxisSegment(infs, a, b))
{
a = b = AcGePoint3d::kOrigin;
return false;
}
return true;
}
/*
组装网络图。
dSplitLen > 0 时,长度超过 dSplitLen 的直管会被拆成多段"虚拟节点"
(按 dSplitLen 等分),这样后续既可以在节点数上控制图幅,也能在长直管
的中点处自然形成切点(符合"长直管段中心点切断"的要求)。
pipeSplits 回传每个被拆直管的切段参数,供写 PCF 时输出多根虚拟管。
*/
struct PipeSplit
{
AcDbObjectId id;
double t0, t1; // 沿管子起点->终点方向的归一化参数
};
void buildNetwork(const AcDbObjectIdArray &ids, double dSplitLen
, std::vector<NetNode> &nodes, std::vector<NetJoint> &joints
, std::vector<PipeSplit> &pipeSplits)
{
nodes.clear();
joints.clear();
pipeSplits.clear();
for (int i = 0; i < ids.length(); ++i)
{
AcDbObjectPointer<AcDbObject> pObj(ids[i], AcDb::kForRead);
if (pObj.openStatus() != Acad::eOk) continue;
const int nType = getCompType(pObj.object());
if (nType < 0) continue;
AcGePoint3d a, b;
AcArray<AecInterface> infs;
if (!readAxisSegment(pObj.object(), infs, a, b)) continue;
// 长直管:按 dSplitLen 等分,逐段建节点
const double dLen = a.distanceTo(b);
if (dSplitLen > 0.0 && nType == ePipe && dLen > dSplitLen * 1.5)
{
const int nSeg = (int)(dLen / dSplitLen) + 1;
const AcGeVector3d vt(b - a);
for (int k = 0; k < nSeg; ++k)
{
const double t0 = (double)k / nSeg;
const double t1 = (double)(k + 1) / nSeg;
NetNode nd;
nd.id = ids[i];
nd.nType = ePipe;
nd.a = a + vt * t0;
nd.b = a + vt * t1;
nd.infs = infs; // 保留接口,供判断端部连接类型(焊接/法兰)
nd.nSplitIdx = (int)pipeSplits.size();
nodes.push_back(nd);
PipeSplit sp;
sp.id = ids[i];
sp.t0 = t0;
sp.t1 = t1;
pipeSplits.push_back(sp);
}
continue;
}
NetNode nd;
nd.id = ids[i];
nd.nType = nType;
nd.a = a;
nd.b = b;
nd.infs = infs;
nd.nSplitIdx = (int)pipeSplits.size();
nodes.push_back(nd);
PipeSplit sp;
sp.id = ids[i];
sp.t0 = 0.0;
sp.t1 = 1.0;
pipeSplits.push_back(sp);
}
// 连接关系:接口位置重合即视为相接
const double dTol = netTolerance();
for (size_t i = 0; i < nodes.size(); ++i)
{
std::vector<ConnPt> ptI;
nodeConnPoints(nodes[i], ptI);
for (size_t k = i + 1; k < nodes.size(); ++k)
{
std::vector<ConnPt> ptK;
nodeConnPoints(nodes[k], ptK);
int nHitX = -1, nHitY = -1;
for (size_t x = 0; x < ptI.size() && nHitX < 0; ++x)
{
for (size_t y = 0; y < ptK.size() && nHitX < 0; ++y)
{
if (ptI[x].pt.distanceTo(ptK[y].pt) <= dTol)
{
nHitX = (int)x;
nHitY = (int)y;
}
}
}
if (nHitX < 0) continue;
NetJoint jt;
jt.nA = (int)i;
jt.nB = (int)k;
jt.pt = ptI[nHitX].pt;
jt.nConnA = ptI[nHitX].nConn;
jt.nConnB = ptK[nHitY].nConn;
jt.dBore = (ptI[nHitX].bore > 0.0) ? ptI[nHitX].bore : ptK[nHitY].bore;
// 法兰面:一端是法兰管件,或某一侧接口本身就是法兰连接(3)
jt.bFlange = (nodes[i].nType == eFlange) || (nodes[k].nType == eFlange)
|| (jt.nConnA == 3) || (jt.nConnB == 3);
/*
螺栓连接的法兰面不算焊口:
- 任一接口的连接类型是"法兰(3)";或
- 一端是法兰管件,另一端是阀门/设备(均以 eEquipment 表示)或另一片法兰。
注意:法兰的另一端(焊颈)与直管/管件相接仍是焊口,故不能"凡法兰即不算焊"。
*/
bool bBoltedFace = (jt.nConnA == 3) || (jt.nConnB == 3);
if (!bBoltedFace && (nodes[i].nType == eFlange || nodes[k].nType == eFlange))
{
const int nOther = (nodes[i].nType == eFlange) ? nodes[k].nType : nodes[i].nType;
if (nOther == eEquipment || nOther == eFlange)
bBoltedFace = true;
}
// 焊接接头:两侧接口的连接类型都是"焊接(0)",且不是螺栓法兰面。
// 连接类型取不到(-1,接口数据缺失)时,退回"非螺栓面且至少一端是直管"的旧判据。
if (bBoltedFace)
jt.bWeld = false;
else if (jt.nConnA == 0 && jt.nConnB == 0)
jt.bWeld = true;
else if (jt.nConnA < 0 && jt.nConnB < 0)
jt.bWeld = (nodes[i].nType == ePipe || nodes[k].nType == ePipe);
joints.push_back(jt);
}
}
}
// ---- 5.3b 连通子网剥离 ----
/*
把一组组件按"接口相接"的连通性拆成若干连通子网。
连通性完全以本文件的接口重合判定为准(即 buildNetwork 建的图),这样
"拆子网"、"选切点"、"跨图续接标注"三处用的是同一套几何规则,结果自洽。
不用底层 GetPipeNetNodes:它的容差取的是目标接口的垫片尺寸(dGasket),
在本图纸上会把接口位置完全重合(距离 0)的法兰与阀门判成不连通,导致
一个系统被切成上百个碎片。
*/
void splitNetByConnectivity(const AcDbObjectIdArray &ids, std::vector<AcDbObjectIdArray> &nets)
{
nets.clear();
if (ids.isEmpty()) return;
std::vector<NetNode> nodes;
std::vector<NetJoint> joints;
std::vector<PipeSplit> splits;
buildNetwork(ids, 0.0, nodes, joints, splits);
const int n = (int)nodes.size();
if (n <= 0) return;
// 并查集:接口重合即视为同一子网
std::vector<int> par(n);
for (int i = 0; i < n; ++i) par[i] = i;
for (size_t j = 0; j < joints.size(); ++j)
{
int a = joints[j].nA, b = joints[j].nB;
while (par[a] != a) { par[a] = par[par[a]]; a = par[a]; }
while (par[b] != b) { par[b] = par[par[b]]; b = par[b]; }
if (a != b) par[a] = b;
}
// 按根聚拢;节点顺序沿用系统内的组件顺序,保证导出顺序稳定
std::map<int, int> rootToNet;
for (int i = 0; i < n; ++i)
{
int r = i;
while (par[r] != r) { par[r] = par[par[r]]; r = par[r]; }
std::map<int, int>::iterator it = rootToNet.find(r);
int idx;
if (it == rootToNet.end())
{
idx = (int)nets.size();
rootToNet.insert(std::make_pair(r, idx));
nets.push_back(AcDbObjectIdArray());
}
else
{
idx = it->second;
}
if (nets[idx].find(nodes[i].id) == -1)
nets[idx].append(nodes[i].id);
}
acutPrintf(_T("\n连通子网 %d 个。"), (int)nets.size());
}
// 管号里常见的占位值(空、全分隔符)视为"没填"
bool isBlankKey(const CString &s)
{
if (s.IsEmpty()) return true;
for (int i = 0; i < s.GetLength(); ++i)
{
const TCHAR c = s[i];
if (c != _T('-') && c != _T('_') && c != _T('.') && c != _T(' ') && c != _T('/'))
return false;
}
return true;
}
/*
取一个连通子网的"管号":子网内出现次数最多的非空管号。
阀门/附件这类不带管号的组件因此会跟着它所在的那条管线走,
而不是被单独分出去。取不到时退回系统名。
*/
CString dominantKey(const AcDbObjectIdArray &ids, LPCTSTR pszKey, LPCTSTR pszDefault)
{
CString sDefault(pszDefault ? pszDefault : _T(""));
if (!pszKey || _tcslen(pszKey) == 0) return sDefault;
std::map<CString, int> hist;
for (int i = 0; i < ids.length(); ++i)
{
AcDbObjectPointer<AcDbObject> pObj(ids[i], AcDb::kForRead);
if (pObj.openStatus() != Acad::eOk) continue;
CString s;
if (!readComponentKey(pObj.object(), pszKey, s) || isBlankKey(s)) continue;
hist[s]++;
}
CString sBest;
int nBest = 0;
for (std::map<CString, int>::const_iterator it = hist.begin(); it != hist.end(); ++it)
{
if (it->second > nBest) { nBest = it->second; sBest = it->first; }
}
return sBest.IsEmpty() ? sDefault : sBest;
}
// ---- 5.4 尺寸/密度二次切分 ----
/*
一张图放不下时,沿管线走向从一端开始累加组件;一旦再加就会超过
nMaxNodes 个组件或 dMaxSpan 的跨度,就回退到"最近的优先切点"再切。
因为只在组件之间的接口处切,所以绝不会切在弯头/三通内部。
*/
void splitBySize(const std::vector<NetNode> &nodes, const std::vector<NetJoint> &joints
, int nMaxNodes, double dMaxSpan, std::vector<std::vector<int> > &sheets)
{
sheets.clear();
const int n = (int)nodes.size();
if (n <= 0) return;
// 邻接表:node -> (邻居, joint 下标)
std::vector<std::vector<std::pair<int, int> > > adj(n);
for (int j = 0; j < (int)joints.size(); ++j)
{
adj[joints[j].nA].push_back(std::make_pair(joints[j].nB, j));
adj[joints[j].nB].push_back(std::make_pair(joints[j].nA, j));
}
std::vector<char> used(n, 0);
for (int s = 0; s < n; ++s)
{
if (used[s]) continue;
// 优先从"端点"(度为 1)起步,符合管线走向
int start = s;
for (int k = 0; k < n; ++k)
{
if (!used[k] && adj[k].size() <= 1) { start = k; break; }
}
std::vector<int> order; // 遍历顺序
std::vector<int> entry; // 进入 order[k] 所用的 joint;order[0] = -1
std::vector<int> stack;
order.push_back(start);
entry.push_back(-1);
used[start] = 1;
stack.push_back(start);
AcGePoint3d ptMin, ptMax;
bool bBox = false;
while (!stack.empty())
{
const int u = stack.back();
int v = -1, je = -1;
for (size_t t = 0; t < adj[u].size(); ++t)
{
const int w = adj[u][t].first;
if (used[w]) continue;
v = w;
je = adj[u][t].second;
break;
}
if (v < 0) { stack.pop_back(); continue; }
// 组件数上限
if (nMaxNodes > 0 && (int)order.size() >= nMaxNodes)
break;
// 空间跨度上限
if (dMaxSpan > 0.0)
{
AcGePoint3d mn = nodes[v].a, mx = nodes[v].b;
if (mn.x > mx.x) { const double t = mn.x; mn.x = mx.x; mx.x = t; }
if (mn.y > mx.y) { const double t = mn.y; mn.y = mx.y; mx.y = t; }
if (mn.z > mx.z) { const double t = mn.z; mn.z = mx.z; mx.z = t; }
AcGePoint3d tmn = mn, tmx = mx;
if (bBox)
{
if (ptMin.x < tmn.x) tmn.x = ptMin.x;
if (ptMin.y < tmn.y) tmn.y = ptMin.y;
if (ptMin.z < tmn.z) tmn.z = ptMin.z;
if (ptMax.x > tmx.x) tmx.x = ptMax.x;
if (ptMax.y > tmx.y) tmx.y = ptMax.y;
if (ptMax.z > tmx.z) tmx.z = ptMax.z;
}
const double dx = tmx.x - tmn.x;
const double dy = tmx.y - tmn.y;
const double dz = tmx.z - tmn.z;
if (__max(dx, __max(dy, dz)) > dMaxSpan)
break;
ptMin = tmn;
ptMax = tmx;
bBox = true;
}
order.push_back(v);
entry.push_back(je);
used[v] = 1;
stack.push_back(v);
}
int nKeep = (int)order.size();
// 是否"因超限被截断"(最后一个节点还有没用到的邻居)
bool bTruncated = false;
{
const int last = order.back();
for (size_t t = 0; t < adj[last].size(); ++t)
{
if (!used[adj[last][t].first]) { bTruncated = true; break; }
}
}
// 回退到最近的优先切点(法兰面/现场焊缝),让切点落在该落的地方
if (bTruncated)
{
for (int k = nKeep - 1; k >= 1; --k)
{
if (jointPriority(joints[entry[k]]) >= 1)
{
// order[k..] 交还给下一轮
for (int t = k; t < nKeep; ++t) used[order[t]] = 0;
nKeep = k;
break;
}
}
}
sheets.push_back(std::vector<int>(order.begin(), order.begin() + nKeep));
}
}
// ---- 5.5 分图导出 ----
// 文件名非法字符替换
CString sanitizeFileName(LPCTSTR psz)
{
CString s(psz ? psz : _T(""));
LPCTSTR pszBad = _T("\\/:*?\"<>|");
for (int i = 0; i < (int)_tcslen(pszBad); ++i)
s.Replace(pszBad[i], _T('_'));
s.Trim();
if (s.IsEmpty()) s = _T("PCF");
return s;
}
// 一个焊口:位置 + 管径 + 两侧组件(用于把焊口挂到组件所在的那张图上)
struct WeldRec
{
AcGePoint3d pt;
double bore;
AcDbObjectId idA, idB;
WeldRec() : bore(0.0) {}
};
/*
焊口(WELD)。PCF 参考手册里 WELD 的标准写法:
WELD
END-POINT x y z <size>
END-POINT x y z <size>
SKEY WW
CATEGORY FABRICATION
(两个 END-POINT 分别代表焊缝两侧;本模型里两侧位置重合,故坐标相同。)
*/
bool pcf_weld(const WeldRec &w, std::ostream &out)
{
if (!out.good()) return false;
PcfRow(out, 0) << PCF_COMP_WELD;
if (w.bore > 0.0)
{
PcfRow(out, 4) << PCF_PT_END_POINT << w.pt << w.bore;
PcfRow(out, 4) << PCF_PT_END_POINT << w.pt << w.bore;
}
else
{
PcfRow(out, 4) << PCF_PT_CO_ORDS << w.pt;
}
PcfRow(out, 4) << PCF_ATTR_SKEY << PCF_ATTR_SKEY_WELD;
PcfRow(out, 4) << PCF_KW_CATEGORY << PCF_CAT_FABRICATION;
out << "\n";
return true;
}
// 设备(EQUIPMENT)定义块:必须写在 PIPELINE-REFERENCE 之前
void writePcfEquipmentDefs(std::ostream &out, CUIISQLiteServer *pDb
, const AcDbObjectIdArray &ids, int &nDef, int &nDefOk)
{
nDef = nDefOk = 0;
for (int i(0); i < ids.length(); ++i)
{
AcDbObjectPointer<AcDbObject> pObj(ids[i], AcDb::kForRead);
if (pObj.openStatus() != Acad::eOk) continue;
if (pObj->isA() != AecDbXEquipment::desc()) continue;
++nDef;
AecDbXEquipment *pXEqu = AecDbXEquipment::cast(pObj.object());
if (pcf_xEquipment(pDb, pXEqu, out)) ++nDefOk;
}
}
// 管道 / 管件 / 阀门(设备) / 焊口
void writePcfComponents(std::ostream &out, CUIISQLiteServer *pDb
, const AcDbObjectIdArray &ids
, const std::vector<PipeSplit> *pSplits
, const std::vector<std::pair<AcDbObjectId, CString> > *pMsgs
, const std::vector<WeldRec> *pWelds
, int &nPipe, int &nPipeOk, int &nFit, int &nFitOk, int &nEqu, int &nEquOk
, int &nWeld, int &nWeldOk)
{
nPipe = nPipeOk = nFit = nFitOk = nEqu = nEquOk = 0;
nWeld = nWeldOk = 0;
// 同一实体被拆成多段时,先按实体归拢切段参数
std::vector<size_t> hit;
for (int i(0); i < ids.length(); ++i)
{
const AcDbObjectId &id(ids[i]);
AcDbObjectPointer<AcDbObject> pObj(id, AcDb::kForRead);
if (pObj.openStatus() != Acad::eOk) continue;
bool bOk = false;
if (pObj->isA() == AecDbEwPipe::desc())
{
AecDbEwPipe *pPipe = AecDbEwPipe::cast(pObj.object());
// 收集本实体对应的切段(通常只有一段)
hit.clear();
if (pSplits)
{
for (size_t k = 0; k < pSplits->size(); ++k)
if ((*pSplits)[k].id == id) hit.push_back(k);
}
if (!hit.empty())
{
// 长直管被切断:本图只输出落在本图上的那几段虚拟管
const AcGePoint3d ptA = pPipe->StartPoint();
const AcGeVector3d vt(pPipe->EndPoint() - ptA);
nPipe += (int)hit.size();
for (size_t k = 0; k < hit.size(); ++k)
{
const PipeSplit &sp = (*pSplits)[hit[k]];
const AcGePoint3d ptS = ptA + vt * sp.t0;
const AcGePoint3d ptE = ptA + vt * sp.t1;
if (pcf_pipe_normal(pDb, pPipe, out, &ptS, &ptE)) ++nPipeOk;
}
bOk = true;
}
else
{
++nPipe;
if (pcf_pipe_normal(pDb, pPipe, out)) { ++nPipeOk; bOk = true; }
}
}
else if (pObj->isA() == AecDbEwFitting::desc())
{
++nFit;
AecDbEwFitting *pFit = AecDbEwFitting::cast(pObj.object());
if (pcf_fitting(pDb, pFit, out)) { ++nFitOk; bOk = true; }
}
else if (pObj->isA() == AecDbEwEquipment::desc())
{
++nEqu;
AecDbEwEquipment *pEqu = AecDbEwEquipment::cast(pObj.object());
if (pcf_ewEquipment(pDb, pEqu, out)) { ++nEquOk; bOk = true; }
}
// 接图标识:紧跟在本组件属性之后写,随该组件一起被下游识别。
// 同一个组件可能是两个切缝的端点(例如三通的两条支管各去了不同的图),
// 所以这里要把该组件的所有接图标识都写出来,不能 break。
if (bOk && pMsgs)
{
for (size_t k = 0; k < pMsgs->size(); ++k)
{
if ((*pMsgs)[k].first == id)
writePcfAttribute(out, 4, PCF_ATTR_CONTINUATION, (LPCTSTR)(*pMsgs)[k].second);
}
}
// 焊口:挂在焊缝 A 端组件之后,保持与相邻组件的前后关系
if (bOk && pWelds)
{
for (size_t k = 0; k < pWelds->size(); ++k)
{
if ((*pWelds)[k].idA != id) continue;
++nWeld;
if (pcf_weld((*pWelds)[k], out)) ++nWeldOk;
}
}
}
}
// 支吊架:本身不带 PIPE_SYS,也没有宿主管道句柄,按空间就近归属到本管道系统
void writePcfSupports(std::ostream &out, LPCTSTR pszSys
, const std::vector<AcDbObjectId> &hangers
, const std::vector<SupportTarget> &targets, int &nSup, int &nSupOk)
{
nSup = nSupOk = 0;
for (size_t k(0); k < hangers.size(); ++k)
{
AcDbObjectPointer<AcDbObject> pObj(hangers[k], AcDb::kForRead);
if (pObj.openStatus() != Acad::eOk) continue;
AecDbPipeHanger *pHanger = AecDbPipeHanger::cast(pObj.object());
if (!pHanger) continue;
if (!supportBelongsToSystem(pHanger->position(), pszSys, targets)) continue;
++nSup;
if (pcf_support(pHanger, out)) ++nSupOk;
}
}
// 一张图(一个 PCF 文件)的统计
struct PcfSheetCounts
{
int nPipe, nPipeOk, nFit, nFitOk, nEqu, nEquOk, nEquDef, nEquDefOk, nSup, nSupOk, nWeld, nWeldOk;
PcfSheetCounts()
: nPipe(0), nPipeOk(0), nFit(0), nFitOk(0), nEqu(0), nEquOk(0)
, nEquDef(0), nEquDefOk(0), nSup(0), nSupOk(0), nWeld(0), nWeldOk(0) {}
};
/*
写一张图的完整 PCF 内容(文件流的打开/关闭由调用方负责)。
pszPipelineRef : PIPELINE-REFERENCE 取值(管号,取不到时用系统名)
pSplits : 长直管切段(可空)
pMsgs : 接图标识(可空)
*/
void writePcfSheet(std::ostream &out, CUIISQLiteServer *pDb, LPCTSTR pszPipelineRef
, const AcDbObjectIdArray &ids
, const std::vector<SupportTarget> &targets
, const std::vector<AcDbObjectId> &hangers
, LPCTSTR pszSupportSys
, const std::vector<PipeSplit> *pSplits
, const std::vector<std::pair<AcDbObjectId, CString> > *pMsgs
, const std::vector<WeldRec> *pWelds
, PcfSheetCounts &c)
{
pcf_begin(out);
writePcfEquipmentDefs(out, pDb, ids, c.nEquDef, c.nEquDefOk);
pcf_pipeline_reference(out, pszPipelineRef);
writePcfComponents(out, pDb, ids, pSplits, pMsgs, pWelds
, c.nPipe, c.nPipeOk, c.nFit, c.nFitOk, c.nEqu, c.nEquOk, c.nWeld, c.nWeldOk);
writePcfSupports(out, pszSupportSys, hangers, targets, c.nSup, c.nSupOk);
}
// 一次遍历同时统计所有候选键的取值分布(避免按 6 个键反复打开实体)
void probeGroupKeys(const AcDbObjectIdArray &ids, std::vector<std::map<CString, int> > &hists)
{
const int nKeys = (int)(sizeof(g_pszNoKeys) / sizeof(g_pszNoKeys[0]));
hists.assign(nKeys, std::map<CString, int>());
for (int i = 0; i < ids.length(); ++i)
{
AcDbObjectPointer<AcDbObject> pObj(ids[i], AcDb::kForRead);
if (pObj.openStatus() != Acad::eOk) continue;
for (int k = 0; k < nKeys; ++k)
{
CString s;
if (readComponentKey(pObj.object(), g_pszNoKeys[k], s) && !s.IsEmpty())
hists[k][s]++;
}
}
}
// 跨图连接判定用:一个连接点及其所属组件
struct SheetPoint
{
AcGePoint3d pt;
AcDbObjectId id;
};
// 取一个实体的节点信息(接口 + 主轴),定义见下方
bool readNode(AcDbObjectId id, NetNode &nd);
// 预计算一张图的全部连接点,避免跨图两两比较时反复打开实体
void collectSheetPoints(const AcDbObjectIdArray &ids, std::vector<SheetPoint> &pts)
{
pts.clear();
for (int i = 0; i < ids.length(); ++i)
{
NetNode nd;
if (!readNode(ids[i], nd)) continue;
std::vector<ConnPt> ar;
nodeConnPoints(nd, ar);
for (size_t k = 0; k < ar.size(); ++k)
{
SheetPoint sp;
sp.pt = ar[k].pt;
sp.id = ids[i];
pts.push_back(sp);
}
}
}
// 取一个实体的节点信息(接口 + 主轴),供跨图连接判定使用
bool readNode(AcDbObjectId id, NetNode &nd)
{
AcDbObjectPointer<AcDbObject> pObj(id, AcDb::kForRead);
if (pObj.openStatus() != Acad::eOk) return false;
const int nType = getCompType(pObj.object());
if (nType < 0) return false;
AcGePoint3d a, b;
AcArray<AecInterface> infs;
if (!readAxisSegment(pObj.object(), infs, a, b)) return false;
nd.id = id;
nd.nType = nType;
nd.a = a;
nd.b = b;
nd.infs = infs;
return true;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 默认输出路径(实现在文件末尾)
static CString makeDefaultPcfPath(LPCTSTR pszSystem);
// 对外只暴露这一个接口
// 按管道系统(pszSystem)得到该系统下的所有 管道/管件/设备,并输出到 pcf 文件
bool PCF::makePCF(LPCTSTR pszFile, LPCTSTR pszSystem)
{
if (!pszFile || _tcslen(pszFile) == 0)
return false;
// 未指定管道系统时,遍历图纸中出现的所有管道系统,取第一个
CString sSystem;
if (pszSystem && _tcslen(pszSystem) > 0)
{
sSystem = pszSystem;
}
else
{
std::vector<CString> systems;
collectSystems(systems);
if (systems.empty())
{
acutPrintf(_T("\n图纸中未找到任何管道系统(PIPE_PSYSTEM)!"));
return false;
}
sSystem = systems[0];
}
AcDbObjectIdArray ids;
if (!getComponentBySystem(sSystem, ids))
{
acutPrintf(_T("\n管道系统 [%s] 下没有 管道/管件/设备。"), (LPCTSTR)sSystem);
return false;
}
// 规格库(物料码/描述/等级)用于填充 PCF 的 ITEM-CODE 等属性;
// 打不开时降级为只输出几何骨架,不阻断导出。
CUIISQLiteServer *pDb = new CUIISQLiteServer;
if (!pDb->InitADOServer(FilePath::GetSysDir() + _T("\\Plumbing.db")))
{
AfxMessageBox(_T("数据库驱动加载数据库Plumbing.db失败!将只导出几何骨架。"));
DELETE_PTR(pDb);
pDb = NULL;
}
std::ofstream out(pszFile, std::ios::binary);
if (!out.good())
{
DELETE_PTR(pDb);
return false;
}
// 支吊架按空间就近归属,先备好归属用的组件主轴表
std::vector<SupportTarget> targets;
collectSupportTargets(targets);
std::vector<AcDbObjectId> hangers;
collectPipeHangers(hangers);
// 焊口:整系统建一次网络,取出所有焊接接头(两侧接口连接类型都是"焊接")
std::vector<WeldRec> welds;
{
std::vector<NetNode> nodes;
std::vector<NetJoint> joints;
std::vector<PipeSplit> splits;
buildNetwork(ids, 0.0, nodes, joints, splits);
for (size_t j = 0; j < joints.size(); ++j)
{
if (!joints[j].bWeld) continue;
WeldRec w;
w.pt = joints[j].pt;
w.bore = joints[j].dBore;
w.idA = nodes[joints[j].nA].id;
w.idB = nodes[joints[j].nB].id;
welds.push_back(w);
}
}
// 头部(单位) + 设备定义 + 管线引用 + 组件 + 焊口 + 支吊架
PcfSheetCounts c;
writePcfSheet(out, pDb, (LPCTSTR)sSystem, ids, targets, hangers, (LPCTSTR)sSystem
, NULL, NULL, &welds, c);
out.close();
DELETE_PTR(pDb);
acutPrintf(_T("\n管道系统 [%s]: 直管 %d/%d, 管件 %d/%d, 阀门/附件 %d/%d, 设备定义 %d/%d, 焊口 %d/%d, 支吊架 %d/%d。")
, (LPCTSTR)sSystem, c.nPipeOk, c.nPipe, c.nFitOk, c.nFit, c.nEquOk, c.nEqu
, c.nEquDefOk, c.nEquDef, c.nWeldOk, c.nWeld, c.nSupOk, c.nSup);
return true;
}
/*
自动分图:把一个管道系统拆成多张 ISO 图,每张图导出一个 PCF 文件。
见 pcf.h 里的规则说明。
*/
bool PCF::makePCFSplit(LPCTSTR pszDir, LPCTSTR pszSystem, int nMaxNodes, double dMaxSpan)
{
// 1. 选系统
CString sSystem;
if (pszSystem && _tcslen(pszSystem) > 0)
{
sSystem = pszSystem;
}
else
{
std::vector<CString> systems;
collectSystems(systems);
if (systems.empty())
{
acutPrintf(_T("\n图纸中未找到任何管道系统(PIPE_PSYSTEM)!"));
return false;
}
sSystem = systems[0];
}
// 2. 取该系统的全部组件
AcDbObjectIdArray ids;
if (!getComponentBySystem(sSystem, ids))
{
acutPrintf(_T("\n管道系统 [%s] 下没有 管道/管件/设备。"), (LPCTSTR)sSystem);
return false;
}
// 3. 探测可用的"管号"键
acutPrintf(_T("\n---- 自动分图: 管道系统 [%s],共 %d 个组件 ----")
, (LPCTSTR)sSystem, ids.length());
acutPrintf(_T("\n候选管号键的填充情况:"));
std::vector<std::map<CString, int> > hists;
probeGroupKeys(ids, hists);
LPCTSTR pszUseKey = NULL;
const int nKeys = (int)(sizeof(g_pszNoKeys) / sizeof(g_pszNoKeys[0]));
for (int k = 0; k < nKeys; ++k)
{
int nFilled = 0;
for (std::map<CString, int>::const_iterator it = hists[k].begin(); it != hists[k].end(); ++it)
nFilled += it->second;
acutPrintf(_T("\n %-10s 有值 %d/%d,取值 %d 种")
, g_pszNoKeys[k], nFilled, ids.length(), (int)hists[k].size());
// 可用的分组键:多数组件有值,且取值数明显少于组件数(否则等于按每根管分组)
if (!pszUseKey && nFilled * 2 >= ids.length() && !hists[k].empty()
&& (int)hists[k].size() * 2 <= ids.length())
pszUseKey = g_pszNoKeys[k];
}
if (pszUseKey)
acutPrintf(_T("\n采用分组键: [%s]。"), pszUseKey);
else
acutPrintf(_T("\n未找到可用的管号键(取值过于分散或全为空),改按连通子网分图。"));
// 4. 先按接口连通性把整个系统拆成连通子网,再按子网内的"多数管号"归并成管线组。
// 顺序很重要:阀门/附件这类不带管号的组件必须先靠连通性归到它所在的管线上,
// 否则会被单独分出去(实测 97 个阀门会各自成一张图)。
std::vector<AcDbObjectIdArray> nets;
splitNetByConnectivity(ids, nets);
std::map<CString, AcDbObjectIdArray> groups;
std::vector<CString> groupOrder;
for (size_t k = 0; k < nets.size(); ++k)
{
const CString sKey = dominantKey(nets[k], pszUseKey, (LPCTSTR)sSystem);
if (groups.find(sKey) == groups.end())
groupOrder.push_back(sKey);
for (int i = 0; i < nets[k].length(); ++i)
groups[sKey].append(nets[k][i]);
}
acutPrintf(_T("\n按管号归并为 %d 个管线组。"), (int)groupOrder.size());
// 5. 组内按图幅二次切分(同一管线组可能由多条互不相连的子网组成,各自成图)
struct SheetPlan
{
CString ref; // PIPELINE-REFERENCE(管号)
AcDbObjectIdArray ids;
std::vector<PipeSplit> splits;
std::vector<WeldRec> welds;
};
std::vector<SheetPlan> plans;
for (size_t gi = 0; gi < groupOrder.size(); ++gi)
{
const CString &sKey = groupOrder[gi];
const AcDbObjectIdArray &groupIds = groups[sKey];
std::vector<NetNode> nodes;
std::vector<NetJoint> joints;
std::vector<PipeSplit> splits;
// 长直管按 dMaxSpan 预切段,使"长直管中心点"也能成为切点
buildNetwork(groupIds, dMaxSpan, nodes, joints, splits);
// 单张图的限制按组件个数换算到节点个数
const int nLimit = (nMaxNodes > 0) ? nMaxNodes : 0;
std::vector<std::vector<int> > sheets;
splitBySize(nodes, joints, nLimit, dMaxSpan, sheets);
// 节点 -> 图号
std::vector<int> nodeSheet(nodes.size(), -1);
for (size_t s = 0; s < sheets.size(); ++s)
{
for (size_t t = 0; t < sheets[s].size(); ++t)
nodeSheet[sheets[s][t]] = (int)s;
}
// 焊口按 A 端所在图归属(两端分属不同图时归 A 端那张,保证全系统只出现一次)
std::vector<std::vector<WeldRec> > sheetWelds(sheets.size());
for (size_t j = 0; j < joints.size(); ++j)
{
if (!joints[j].bWeld) continue;
int s = -1;
if (joints[j].nA >= 0 && joints[j].nA < (int)nodeSheet.size())
s = nodeSheet[joints[j].nA];
if (s < 0 && joints[j].nB >= 0 && joints[j].nB < (int)nodeSheet.size())
s = nodeSheet[joints[j].nB];
if (s < 0) continue;
WeldRec w;
w.pt = joints[j].pt;
w.bore = joints[j].dBore;
w.idA = nodes[joints[j].nA].id;
w.idB = nodes[joints[j].nB].id;
sheetWelds[s].push_back(w);
}
for (size_t s = 0; s < sheets.size(); ++s)
{
SheetPlan plan;
plan.ref = sKey;
plan.welds = sheetWelds[s];
// 节点 -> 组件(去重,保持顺序)
for (size_t t = 0; t < sheets[s].size(); ++t)
{
const NetNode &nd = nodes[sheets[s][t]];
if (plan.ids.find(nd.id) == -1)
plan.ids.append(nd.id);
}
// 只保留本图用到的切段(按节点精确对应,避免把别张图的段带进来)
for (size_t t = 0; t < sheets[s].size(); ++t)
{
const int q = nodes[sheets[s][t]].nSplitIdx;
if (q < 0 || q >= (int)splits.size()) continue;
if (splits[q].t0 <= 0.0 && splits[q].t1 >= 1.0) continue; // 未切
plan.splits.push_back(splits[q]);
}
plans.push_back(plan);
}
}
if (plans.empty())
{
acutPrintf(_T("\n没有可导出的分图。"));
return false;
}
// 6. 生成图号并建立"接图纸"映射:
// 每个切点两侧各写一条"接图纸 <对方图号>",方便施工拼图。
std::vector<CString> sheetCodes;
for (size_t s = 0; s < plans.size(); ++s)
{
CString sCode;
sCode.Format(_T("ISO-%03d"), (int)(s + 1));
sheetCodes.push_back(sCode);
}
// 图号 -> 该图包含的组件
std::vector<std::vector<AcDbObjectId> > sheetIds(plans.size());
for (size_t s = 0; s < plans.size(); ++s)
{
for (int i = 0; i < plans[s].ids.length(); ++i)
sheetIds[s].push_back(plans[s].ids[i]);
}
// 逐对检查两张图之间是否存在"跨图连接":A 图某组件的接口位置与 B 图某组件的
// 接口位置重合即认为这两张图原本是接在一起的,两侧互相写"接图纸"标识。
// 先把各图的连接点算好,之后只是纯坐标比较,不再反复打开实体。
std::vector<std::vector<SheetPoint> > sheetPts(plans.size());
for (size_t s = 0; s < plans.size(); ++s)
collectSheetPoints(plans[s].ids, sheetPts[s]);
std::vector<std::pair<AcDbObjectId, CString> > msgs;
{
const double dTol = netTolerance();
for (size_t a = 0; a < plans.size(); ++a)
{
for (size_t b = 0; b < plans.size(); ++b)
{
if (a == b) continue;
CString sMsg;
sMsg.Format(_T("接图纸 %s"), (LPCTSTR)sheetCodes[b]);
// 一对图之间可能有好几处切缝,逐处都标,不能只标第一处
std::vector<AcDbObjectId> hits;
for (size_t x = 0; x < sheetPts[a].size(); ++x)
{
for (size_t y = 0; y < sheetPts[b].size(); ++y)
{
if (sheetPts[a][x].pt.distanceTo(sheetPts[b][y].pt) > dTol) continue;
const AcDbObjectId &idHit = sheetPts[a][x].id;
bool bDup = false;
for (size_t k = 0; k < hits.size(); ++k)
{
if (hits[k] == idHit) { bDup = true; break; }
}
if (!bDup) hits.push_back(idHit);
break; // 本连接点已配上,换下一个
}
}
for (size_t k = 0; k < hits.size(); ++k)
msgs.push_back(std::make_pair(hits[k], sMsg));
}
}
}
// 7. 输出目录
CString sDir = (pszDir && _tcslen(pszDir) > 0) ? pszDir : _T("");
if (sDir.IsEmpty())
{
CString sPath = makeDefaultPcfPath((LPCTSTR)sSystem);
const int nPos = sPath.ReverseFind(_T('\\'));
sDir = (nPos >= 0) ? sPath.Left(nPos) : _T("");
}
if (!sDir.IsEmpty() && sDir[sDir.GetLength() - 1] != _T('\\'))
sDir += _T("\\");
// 8. 支吊架(全图收集一次,逐图按空间就近归属)
std::vector<SupportTarget> targets;
collectSupportTargets(targets);
std::vector<AcDbObjectId> hangers;
collectPipeHangers(hangers);
// 9. 逐图写文件
CUIISQLiteServer *pDb = new CUIISQLiteServer;
if (!pDb->InitADOServer(FilePath::GetSysDir() + _T("\\Plumbing.db")))
{
AfxMessageBox(_T("数据库驱动加载数据库Plumbing.db失败!将只导出几何骨架。"));
DELETE_PTR(pDb);
pDb = NULL;
}
int nOk = 0;
for (size_t s = 0; s < plans.size(); ++s)
{
CString sFile;
sFile.Format(_T("%s%s_%s.pcf")
, (LPCTSTR)sDir
, (LPCTSTR)sheetCodes[s]
, (LPCTSTR)sanitizeFileName(plans[s].ref));
std::ofstream out((LPCTSTR)sFile, std::ios::binary);
if (!out.good())
{
acutPrintf(_T("\n分图 %s 无法写入: %s"), (LPCTSTR)sheetCodes[s], (LPCTSTR)sFile);
continue;
}
// 本图相关的接图标识
std::vector<std::pair<AcDbObjectId, CString> > sheetMsgs;
for (size_t m = 0; m < msgs.size(); ++m)
{
for (size_t i = 0; i < sheetIds[s].size(); ++i)
{
if (msgs[m].first == sheetIds[s][i])
{
sheetMsgs.push_back(msgs[m]);
break;
}
}
}
PcfSheetCounts c;
writePcfSheet(out, pDb, (LPCTSTR)plans[s].ref, plans[s].ids, targets, hangers
, (LPCTSTR)sSystem, &plans[s].splits, &sheetMsgs, &plans[s].welds, c);
out.close();
++nOk;
acutPrintf(_T("\n %s -> %s (直管 %d, 管件 %d, 阀门/附件 %d, 设备定义 %d, 焊口 %d, 支吊架 %d)")
, (LPCTSTR)sheetCodes[s], (LPCTSTR)sFile
, c.nPipeOk, c.nFitOk, c.nEquOk, c.nEquDefOk, c.nWeldOk, c.nSupOk);
}
DELETE_PTR(pDb);
acutPrintf(_T("\n自动分图完成: 系统 [%s] 共输出 %d 张图,目录 %s")
, (LPCTSTR)sSystem, nOk, (LPCTSTR)sDir);
return nOk > 0;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 默认输出路径:图纸所在目录\<系统名>.pcf;图纸未保存时退回系统目录
static CString makeDefaultPcfPath(LPCTSTR pszSystem)
{
CString sSys = (pszSystem && _tcslen(pszSystem) > 0) ? pszSystem : _T("PCF");
CString sPath;
const TCHAR *pszDwg = NULL;
acdbCurDwg()->getFilename(pszDwg);
if (pszDwg && _tcslen(pszDwg) > 0)
{
sPath = pszDwg;
int nPos = sPath.ReverseFind(_T('\\'));
if (nPos < 0) nPos = sPath.ReverseFind(_T('/'));
sPath = (nPos >= 0) ? sPath.Left(nPos + 1) : _T("");
}
if (sPath.IsEmpty())
sPath = FilePath::GetSysDir() + _T("\\");
sPath += sSys;
sPath += _T(".pcf");
return sPath;
}
// 命令:按管道系统导出 PCF 中间文件
// 交互用法:EW_PCF -> 列出系统 -> 输入系统(可空=自动取第一个) -> 输入输出路径(可空=采用默认路径)
// 非交互用法:EW_PCF + 两次回车,即自动选取第一个系统并导出到默认路径,不会挂起等待输入。
void EW_PCFCmdFnc()
{
// 1. 列出图纸中现有的管道系统,方便选择
std::vector<CString> systems;
collectSystems(systems);
if (!systems.empty())
{
acutPrintf(_T("\n图纸中的管道系统共 %d 个:"), (int)systems.size());
for (size_t k = 0; k < systems.size(); ++k)
acutPrintf(_T("\n [%d] %s"), (int)(k + 1), (LPCTSTR)systems[k]);
}
else
{
acutPrintf(_T("\n未找到管道系统(PIPE_PSYSTEM),将无法导出。"));
}
// 2. 输入系统(可空=自动取第一个)
TCHAR szSys[256] = { 0 };
if (acedGetString(FALSE, _T("\n请输入管道系统<直接回车=自动选取第一个>: "), szSys) != RTNORM)
return;
CString sSystem = szSys;
// 留空时直接取第一个系统,便于默认文件名带上系统名
if (sSystem.IsEmpty() && !systems.empty())
sSystem = systems[0];
// 3. 输出路径:先算出默认值并显示,直接回车即采用默认,避免非交互调用时挂起等待
CString sDef = makeDefaultPcfPath(sSystem.IsEmpty() ? NULL : (LPCTSTR)sSystem);
CString sPrompt;
sPrompt.Format(_T("\n请输入输出的PCF文件完整路径<直接回车采用默认: %s>: "), (LPCTSTR)sDef);
TCHAR szFile[512] = { 0 };
if (acedGetString(FALSE, sPrompt, szFile) != RTNORM)
return;
CString sFile = szFile;
if (sFile.IsEmpty())
sFile = sDef;
// 4. 导出
LPCTSTR pszSys = sSystem.IsEmpty() ? NULL : (LPCTSTR)sSystem;
if (PCF::makePCF(sFile, pszSys))
acutPrintf(_T("\nPCF导出成功: %s"), (LPCTSTR)sFile);
else
acutPrintf(_T("\nPCF导出失败!请检查管道系统下是否存在 管道/管件/设备。"));
}
// 命令:自动分图 —— 把一个管道系统自动拆成多张 ISO 图(每张图一个 PCF 文件)
// 拆分规则见 PCF::makePCFSplit()。全部提示直接回车即采用默认值,便于非交互调用。
void EW_PCFSPLITCmdFnc()
{
// 1. 列出图纸中现有的管道系统
std::vector<CString> systems;
collectSystems(systems);
if (!systems.empty())
{
acutPrintf(_T("\n图纸中的管道系统共 %d 个:"), (int)systems.size());
for (size_t k = 0; k < systems.size(); ++k)
acutPrintf(_T("\n [%d] %s"), (int)(k + 1), (LPCTSTR)systems[k]);
}
else
{
acutPrintf(_T("\n未找到管道系统(PIPE_PSYSTEM),将无法分图。"));
}
// 2. 管道系统
TCHAR szSys[256] = { 0 };
if (acedGetString(FALSE, _T("\n请输入要分图的管道系统<直接回车=自动选取第一个>: "), szSys) != RTNORM)
return;
CString sSystem = szSys;
if (sSystem.IsEmpty() && !systems.empty())
sSystem = systems[0];
// 3. 单张图最大组件数(超过则继续切分)
TCHAR szNum[64] = { 0 };
if (acedGetString(FALSE, _T("\n请输入单张图最大组件数<直接回车=60,0=不限制>: "), szNum) != RTNORM)
return;
int nMaxNodes = 60;
if (_tcslen(szNum) > 0) nMaxNodes = _ttoi(szNum);
// 4. 单张图最大空间跨度(超过则在长直管中点继续切分)
TCHAR szSpan[64] = { 0 };
if (acedGetString(FALSE, _T("\n请输入单张图最大空间跨度<直接回车=0,表示不限制>: "), szSpan) != RTNORM)
return;
double dMaxSpan = 0.0;
if (_tcslen(szSpan) > 0) dMaxSpan = _ttof(szSpan);
// 5. 输出目录:默认取图纸所在目录
CString sDefDir;
{
CString sDef = makeDefaultPcfPath(sSystem.IsEmpty() ? NULL : (LPCTSTR)sSystem);
const int nPos = sDef.ReverseFind(_T('\\'));
sDefDir = (nPos >= 0) ? sDef.Left(nPos) : _T("");
}
CString sPrompt;
sPrompt.Format(_T("\n请输入分图输出目录<直接回车=图纸所在目录: %s>: "), (LPCTSTR)sDefDir);
TCHAR szDir[512] = { 0 };
if (acedGetString(FALSE, sPrompt, szDir) != RTNORM)
return;
CString sDir = szDir;
if (sDir.IsEmpty()) sDir = sDefDir;
// 6. 分图导出
LPCTSTR pszSys = sSystem.IsEmpty() ? NULL : (LPCTSTR)sSystem;
if (PCF::makePCFSplit(sDir, pszSys, nMaxNodes, dMaxSpan))
acutPrintf(_T("\n自动分图完成。"));
else
acutPrintf(_T("\n自动分图失败!请检查管道系统下是否存在 管道/管件/设备。"));
}