2817 lines
85 KiB
C++
2817 lines
85 KiB
C++
#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自动分图失败!请检查管道系统下是否存在 管道/管件/设备。"));
|
||
}
|