1199 lines
43 KiB
C++
1199 lines
43 KiB
C++
#include "altiumparser.h"
|
||
#include "databasecontroller.h"
|
||
#include "../model/componentmodel.h"
|
||
#include <QFileInfo>
|
||
#include <QFile>
|
||
#include <QTextStream>
|
||
#include <QRegularExpression>
|
||
#include <QTextCodec>
|
||
#include <QDir>
|
||
#include <QMessageBox>
|
||
|
||
AltiumParser::AltiumParser(QObject *parent)
|
||
: QObject(parent)
|
||
, m_isPcbMultilayer(false)
|
||
, m_database(nullptr)
|
||
, m_parseWatcher(nullptr)
|
||
, m_isParsingAsync(false)
|
||
, m_pcbDocFileName("")
|
||
{
|
||
initializeRegex();
|
||
|
||
// Инициализируем watcher для асинхронного парсинга
|
||
m_parseWatcher = new QFutureWatcher<bool>(this);
|
||
connect(m_parseWatcher, &QFutureWatcher<bool>::finished,
|
||
this, &AltiumParser::onParseFinished);
|
||
connect(m_parseWatcher, &QFutureWatcher<bool>::progressValueChanged,
|
||
this, &AltiumParser::onParseProgress);
|
||
}
|
||
|
||
AltiumParser::~AltiumParser()
|
||
{
|
||
}
|
||
|
||
bool AltiumParser::parseSchFile(const QString &filename, ComponentModel *model)
|
||
{
|
||
if (!model) {
|
||
setError("Модель компонентов не инициализирована");
|
||
return false;
|
||
}
|
||
|
||
// TODO: Реализовать парсинг .sch файлов
|
||
setError("Парсинг .sch файлов еще не реализован");
|
||
return false;
|
||
}
|
||
|
||
bool AltiumParser::parsePcbFile(const QString &filename, ComponentModel *model)
|
||
{
|
||
if (!model) {
|
||
setError("Модель компонентов не инициализирована");
|
||
return false;
|
||
}
|
||
|
||
// TODO: Реализовать парсинг .pcb файлов
|
||
setError("Парсинг .pcb файлов еще не реализован");
|
||
return false;
|
||
}
|
||
|
||
bool AltiumParser::parseBomFile(const QString &filename, ComponentModel *model)
|
||
{
|
||
if (!model) {
|
||
setError("Модель компонентов не инициализирована");
|
||
return false;
|
||
}
|
||
|
||
// TODO: Реализовать парсинг BOM файлов
|
||
setError("Парсинг BOM файлов еще не реализован");
|
||
return false;
|
||
}
|
||
|
||
bool AltiumParser::parseSchDoc(const QString &filename, ComponentModel *model)
|
||
{
|
||
if (!model) {
|
||
setError("Модель компонентов не инициализирована");
|
||
return false;
|
||
}
|
||
|
||
// TODO: Реализовать парсинг .SchDoc файлов
|
||
setError("Парсинг .SchDoc файлов еще не реализован");
|
||
return false;
|
||
}
|
||
|
||
bool AltiumParser::parsePcbDoc(const QString &filename, ComponentModel *model)
|
||
{
|
||
if (!model) {
|
||
setError("Модель компонентов не инициализирована");
|
||
return false;
|
||
}
|
||
|
||
// TODO: Реализовать парсинг .PcbDoc файлов
|
||
setError("Парсинг .PcbDoc файлов еще не реализован");
|
||
return false;
|
||
}
|
||
|
||
bool AltiumParser::parseCsvBom(const QString &filename, ComponentModel *model)
|
||
{
|
||
if (!model) {
|
||
setError("Модель компонентов не инициализирована");
|
||
return false;
|
||
}
|
||
|
||
// TODO: Реализовать парсинг CSV BOM файлов
|
||
setError("Парсинг CSV BOM файлов еще не реализован");
|
||
return false;
|
||
}
|
||
|
||
bool AltiumParser::parseXlsBom(const QString &filename, ComponentModel *model)
|
||
{
|
||
if (!model) {
|
||
setError("Модель компонентов не инициализирована");
|
||
return false;
|
||
}
|
||
|
||
// TODO: Реализовать парсинг XLS BOM файлов
|
||
setError("Парсинг XLS BOM файлов еще не реализован");
|
||
return false;
|
||
}
|
||
|
||
// Новый метод для парсинга PrjPcb файлов
|
||
bool AltiumParser::parsePrjPcbFile(const QString &filename, ComponentModel *model)
|
||
{
|
||
if (!model) {
|
||
setError("Модель компонентов не инициализирована");
|
||
return false;
|
||
}
|
||
|
||
if (!QFile::exists(filename)) {
|
||
setError("Файл не существует: " + filename);
|
||
return false;
|
||
}
|
||
|
||
clearError();
|
||
emit parsingProgress(0);
|
||
|
||
// Очищаем предыдущие данные
|
||
m_componentsList.clear();
|
||
m_variantsList.clear();
|
||
m_dielMaterialsList.clear();
|
||
m_projectParamsList.clear();
|
||
m_variantNamesList.clear();
|
||
m_isPcbMultilayer = false;
|
||
|
||
// Очищаем базу данных
|
||
if (m_database) {
|
||
m_database->clearAllData();
|
||
}
|
||
|
||
emit parsingProgress(10);
|
||
|
||
bool success = parseProjectFile(filename, model);
|
||
|
||
if (success) {
|
||
emit parsingProgress(100);
|
||
emit parsingFinished(true);
|
||
} else {
|
||
emit parsingFinished(false);
|
||
}
|
||
|
||
return success;
|
||
}
|
||
|
||
bool AltiumParser::parseProjectFile(const QString &filename, ComponentModel *model)
|
||
{
|
||
QFile file(filename);
|
||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||
setError("Не удалось открыть файл: " + filename);
|
||
return false;
|
||
}
|
||
|
||
QTextStream reader(&file);
|
||
reader.setCodec(QTextCodec::codecForName("Windows-1251"));
|
||
|
||
// Очищаем базу данных
|
||
if (m_database) {
|
||
m_database->clearAllData();
|
||
}
|
||
|
||
// Сначала подсчитываем общее количество строк для равномерного прогресса
|
||
QFile countFile(filename);
|
||
countFile.open(QIODevice::ReadOnly | QIODevice::Text);
|
||
QTextStream countReader(&countFile);
|
||
countReader.setCodec(QTextCodec::codecForName("Windows-1251"));
|
||
|
||
int totalLines = 0;
|
||
while (!countReader.atEnd()) {
|
||
countReader.readLine();
|
||
totalLines++;
|
||
}
|
||
countFile.close();
|
||
|
||
// Открываем файл заново для парсинга
|
||
file.seek(0);
|
||
|
||
// Инициализация структур данных
|
||
ProjectData projectData;
|
||
projectData.variantNames.append("No Variations");
|
||
projectData.pcbLayerCount = 0;
|
||
projectData.pcbDielMaterials.clear();
|
||
|
||
QString prevPrjStr, prevPrevPrjStr;
|
||
int lineCount = 0;
|
||
int lastProgress = 0;
|
||
|
||
while (!reader.atEnd()) {
|
||
QString prjStr = reader.readLine().trimmed();
|
||
lineCount++;
|
||
|
||
// Обновляем прогресс каждые 100 строк или при достижении определенного процента
|
||
int currentProgress = (lineCount * 80) / totalLines; // 80% для парсинга
|
||
if (currentProgress > lastProgress) {
|
||
emit parsingProgress(currentProgress);
|
||
lastProgress = currentProgress;
|
||
}
|
||
|
||
// Обработка различных типов строк
|
||
parseProjectVariantSection(prjStr, projectData);
|
||
parseComponentVariations(prjStr, projectData);
|
||
parseProjectParameters(prevPrevPrjStr, prevPrjStr, prjStr, projectData);
|
||
parseSchDocFiles(prjStr, filename, projectData);
|
||
parsePcbDocFiles(prjStr, filename, projectData);
|
||
|
||
prevPrevPrjStr = prevPrjStr;
|
||
prevPrjStr = prjStr;
|
||
}
|
||
|
||
file.close();
|
||
|
||
// Вывод результатов парсинга
|
||
printParsingResults(projectData);
|
||
|
||
// Сохранение данных в базу (20% прогресса)
|
||
emit parsingProgress(80);
|
||
saveDataToDatabase(projectData);
|
||
|
||
// Сохраняем имя PcbDoc файла
|
||
m_pcbDocFileName = projectData.pcbDocFileName;
|
||
|
||
// Установка данных в модель
|
||
emit parsingProgress(90);
|
||
setupModelData(projectData, model);
|
||
|
||
return true;
|
||
}
|
||
|
||
// Обработка секций вариантов проекта
|
||
void AltiumParser::parseProjectVariantSection(const QString &prjStr, ProjectData &data)
|
||
{
|
||
// Обработка [ProjectVariant]
|
||
if (prjStr.length() > 15 && prjStr.left(15).toUpper() == "[PROJECTVARIANT") {
|
||
data.componentVariantPropList.clear();
|
||
data.isWaitingVariantDescription = true;
|
||
QString cleanStr = prjStr;
|
||
cleanStr.replace(']', QChar());
|
||
data.currentVariantNumber = cleanStr.mid(15).toInt();
|
||
}
|
||
|
||
// Обработка ParamVariationCount
|
||
if (prjStr.length() > 18 && prjStr.left(19).toUpper() == "PARAMVARIATIONCOUNT") {
|
||
finalizeCurrentVariant(data);
|
||
}
|
||
|
||
// Обработка Description
|
||
if (prjStr.length() > 12 && data.isWaitingVariantDescription &&
|
||
prjStr.left(12).toUpper() == "DESCRIPTION=") {
|
||
data.isWaitingVariantDescription = false;
|
||
QString variantName = prjStr.mid(12);
|
||
data.variantNames.append(variantName);
|
||
}
|
||
}
|
||
|
||
// Завершение текущего варианта
|
||
void AltiumParser::finalizeCurrentVariant(ProjectData &data)
|
||
{
|
||
if (data.componentVariantPropList.size() > 0) {
|
||
data.componentsPropVariantList.append(data.componentVariantPropList);
|
||
data.componentVariantPropList.clear();
|
||
}
|
||
|
||
if (data.componentsPropVariantList.size() > 0) {
|
||
data.componentsVariantList.append(data.componentsPropVariantList);
|
||
data.componentsPropVariantList.clear();
|
||
}
|
||
|
||
data.dnfVariantDesignatorsList.append(data.dnfDesignatorsList);
|
||
data.fittedVariantDesignatorsList.append(data.fittedDesignatorsList);
|
||
data.dnfDesignatorsList.clear();
|
||
data.fittedDesignatorsList.clear();
|
||
}
|
||
|
||
// Обработка вариаций компонентов
|
||
void AltiumParser::parseComponentVariations(const QString &prjStr, ProjectData &data)
|
||
{
|
||
if (prjStr.length() <= 9) return;
|
||
|
||
QStringList prjStrArray = prjStr.split('|');
|
||
if (prjStrArray.isEmpty()) return;
|
||
|
||
QStringList valuesArray = prjStrArray[0].split('=');
|
||
if (valuesArray.size() <= 2) return;
|
||
|
||
// Обработка VARIATION=DESIGNATOR
|
||
if (valuesArray[0].length() >= 9 && valuesArray[0].left(9).toUpper() == "VARIATION") {
|
||
if (valuesArray[1].left(10).toUpper() == "DESIGNATOR") {
|
||
parseVariationDesignator(prjStrArray, valuesArray, data);
|
||
}
|
||
}
|
||
|
||
// Обработка PARAMVARIATION=PARAMETERNAME
|
||
if (valuesArray[0].length() >= 14 && valuesArray[0].left(14).toUpper() == "PARAMVARIATION") {
|
||
if (valuesArray[1].toUpper() == "PARAMETERNAME") {
|
||
parseParamVariation(prjStrArray, valuesArray, data);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Парсинг VARIATION=DESIGNATOR
|
||
void AltiumParser::parseVariationDesignator(const QStringList &prjStrArray,
|
||
const QStringList &valuesArray,
|
||
ProjectData &data)
|
||
{
|
||
if (data.componentVariantPropList.size() > 0) {
|
||
data.componentsPropVariantList.append(data.componentVariantPropList);
|
||
data.componentVariantPropList.clear();
|
||
}
|
||
|
||
ComponentProperty prop;
|
||
prop.name = "Designator";
|
||
prop.text = valuesArray[2];
|
||
QString designator = valuesArray[2];
|
||
|
||
data.componentVariantPropList.append(prop);
|
||
|
||
// Обработка KIND
|
||
if (prjStrArray.size() > 1) {
|
||
QStringList kindValues = prjStrArray[2].split('=');
|
||
if (kindValues[0].toUpper() == "KIND") {
|
||
ComponentProperty kindProp;
|
||
kindProp.name = "Kind";
|
||
kindProp.text = kindValues[1];
|
||
|
||
data.componentVariantPropList.append(kindProp);
|
||
|
||
if (kindValues[1] == "1") {
|
||
data.dnfDesignatorsList.append(designator);
|
||
} else {
|
||
data.fittedDesignatorsList.append(designator);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Парсинг PARAMVARIATION=PARAMETERNAME
|
||
void AltiumParser::parseParamVariation(const QStringList &prjStrArray,
|
||
const QStringList &valuesArray,
|
||
ProjectData &data)
|
||
{
|
||
ComponentProperty prop;
|
||
prop.name = valuesArray[2];
|
||
|
||
// Считываем VariantValue
|
||
int idx = prjStrArray[1].indexOf('=');
|
||
if (idx >= 0) {
|
||
prop.text = prjStrArray[1].mid(idx + 1);
|
||
} else {
|
||
prop.text = prjStrArray[1];
|
||
}
|
||
|
||
data.componentVariantPropList.append(prop);
|
||
}
|
||
|
||
// Обработка параметров проекта
|
||
void AltiumParser::parseProjectParameters(const QString &prevPrevPrjStr,
|
||
const QString &prevPrjStr,
|
||
const QString &prjStr,
|
||
ProjectData &data)
|
||
{
|
||
if (prevPrevPrjStr.length() <= 10 || prevPrjStr.length() <= 4 || prjStr.length() <= 5) {
|
||
return;
|
||
}
|
||
|
||
if (prevPrevPrjStr.left(10).toUpper() == "[PARAMETER" &&
|
||
prevPrjStr.left(4).toUpper() == "NAME" &&
|
||
prjStr.left(5).toUpper() == "VALUE") {
|
||
|
||
QString cleanPrevPrev = prevPrevPrjStr;
|
||
cleanPrevPrev.replace(']', QChar());
|
||
QStringList paramPrjStrArray = cleanPrevPrev.split('_');
|
||
int variantNumber = (paramPrjStrArray.size() > 1) ? data.currentVariantNumber : 0;
|
||
|
||
QStringList nameValue;
|
||
nameValue.append(prevPrjStr.split('=')[1]);
|
||
nameValue.append(prjStr.split('=')[1]);
|
||
|
||
while (data.prjParamsVariantList.size() <= variantNumber) {
|
||
data.prjParamsVariantList.append(QList<QStringList>());
|
||
}
|
||
|
||
data.prjParamsVariantList[variantNumber].append(nameValue);
|
||
}
|
||
}
|
||
|
||
// Обработка файлов схем
|
||
void AltiumParser::parseSchDocFiles(const QString &prjStr, const QString &filename, ProjectData &data)
|
||
{
|
||
if (prjStr.length() <= 13) return;
|
||
|
||
if (prjStr.left(13).toUpper() == "DOCUMENTPATH=" && prjStr.right(6).toUpper() == "SCHDOC") {
|
||
QString schPath = QDir(QFileInfo(filename).absolutePath()).filePath(prjStr.mid(13));
|
||
|
||
if (QFile::exists(schPath)) {
|
||
int componentsBefore = data.componentsList.size();
|
||
parseSchDocFile(schPath, data.componentsList);
|
||
int componentsAfter = data.componentsList.size();
|
||
}
|
||
}
|
||
}
|
||
|
||
// Обработка файлов плат
|
||
void AltiumParser::parsePcbDocFiles(const QString &prjStr, const QString &filename, ProjectData &data)
|
||
{
|
||
if (prjStr.length() <= 13) return;
|
||
|
||
if (prjStr.left(13).toUpper() == "DOCUMENTPATH=" && prjStr.right(6).toUpper() == "PCBDOC") {
|
||
QString pcbPath = QDir(QFileInfo(filename).absolutePath()).filePath(prjStr.mid(13));
|
||
|
||
if (QFile::exists(pcbPath)) {
|
||
bool parseResult = parsePcbDocFile(pcbPath, data.pcbDielMaterials, data.pcbLayerCount);
|
||
// Сохраняем имя PcbDoc файла
|
||
if (parseResult) {
|
||
data.pcbDocFileName = pcbPath;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Вывод результатов парсинга
|
||
void AltiumParser::printParsingResults(const ProjectData &data)
|
||
{
|
||
}
|
||
|
||
// Сохранение данных в базу данных
|
||
void AltiumParser::saveDataToDatabase(const ProjectData &data)
|
||
{
|
||
if (!m_database) {
|
||
return;
|
||
}
|
||
|
||
// Сначала создаем варианты в БД (80-85%)
|
||
emit parsingProgress(80);
|
||
saveVariantsToDatabase(data);
|
||
|
||
// Сохраняем обычные компоненты (85-90%)
|
||
emit parsingProgress(85);
|
||
saveRegularComponents(data);
|
||
|
||
// Сохраняем вариативные свойства (90-95%)
|
||
emit parsingProgress(90);
|
||
saveVariantProperties(data);
|
||
|
||
// Сохраняем параметры проекта (95-100%)
|
||
emit parsingProgress(95);
|
||
saveProjectParameters(data);
|
||
|
||
// Сохраняем данные PCB (97-100%)
|
||
emit parsingProgress(97);
|
||
savePCBDataToDatabase(data);
|
||
|
||
emit parsingProgress(100);
|
||
}
|
||
|
||
// Сохранение вариантов в базу данных
|
||
void AltiumParser::saveVariantsToDatabase(const ProjectData &data)
|
||
{
|
||
for (const QString &variantName : data.variantNames) {
|
||
// Проверяем, существует ли вариант
|
||
int variantId = m_database->getVariantId(variantName);
|
||
if (variantId == -1) {
|
||
// Создаем новый вариант
|
||
variantId = m_database->addVariant(variantName);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Сохранение обычных компонентов
|
||
void AltiumParser::saveRegularComponents(const ProjectData &data)
|
||
{
|
||
if (data.componentsList.isEmpty()) {
|
||
return;
|
||
}
|
||
|
||
// Начинаем транзакцию для пакетной вставки
|
||
if (!m_database->beginTransaction()) {
|
||
return;
|
||
}
|
||
|
||
int savedComponents = 0;
|
||
|
||
for (const QList<ComponentProperty> &componentProps : data.componentsList) {
|
||
QString designator;
|
||
QMap<QString, QString> properties;
|
||
|
||
for (const ComponentProperty &prop : componentProps) {
|
||
if (prop.name == "Designator") {
|
||
designator = prop.text;
|
||
} else {
|
||
properties[prop.name] = prop.text;
|
||
}
|
||
}
|
||
|
||
if (!designator.isEmpty()) {
|
||
// Используем пакетную вставку
|
||
bool success = m_database->addComponentBatch(designator, properties);
|
||
if (success) {
|
||
savedComponents++;
|
||
|
||
// Получаем ID добавленного компонента для связи с вариантами
|
||
ComponentData componentData = m_database->getComponent(designator);
|
||
if (componentData.id != -1) {
|
||
// Добавляем компонент во все варианты пакетно
|
||
m_database->addComponentVariantsBatch(componentData.id, data.variantNames);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Завершаем транзакцию
|
||
if (!m_database->commitTransaction()) {
|
||
m_database->rollbackTransaction();
|
||
}
|
||
}
|
||
|
||
// Сохранение вариативных свойств
|
||
void AltiumParser::saveVariantProperties(const ProjectData &data)
|
||
{
|
||
for (int variantIndex = 0; variantIndex < data.componentsVariantList.size(); variantIndex++) {
|
||
const QList<QList<ComponentProperty>> &variantComponents = data.componentsVariantList[variantIndex];
|
||
|
||
// Получаем название варианта (пропускаем "No Variations")
|
||
if (variantIndex + 1 >= data.variantNames.size()) {
|
||
continue;
|
||
}
|
||
|
||
QString variantName = data.variantNames[variantIndex + 1];
|
||
int variantId = m_database->getVariantId(variantName);
|
||
|
||
if (variantId == -1) {
|
||
continue;
|
||
}
|
||
|
||
for (const QList<ComponentProperty> &componentProps : variantComponents) {
|
||
QString designator;
|
||
QMap<QString, QString> properties;
|
||
|
||
for (const ComponentProperty &prop : componentProps) {
|
||
if (prop.name == "Designator") {
|
||
designator = prop.text;
|
||
} else {
|
||
properties[prop.name] = prop.text;
|
||
}
|
||
}
|
||
|
||
if (!designator.isEmpty()) {
|
||
|
||
// Получаем ID компонента
|
||
ComponentData componentData = m_database->getComponent(designator);
|
||
int componentId = componentData.id;
|
||
|
||
if (componentId != -1) {
|
||
// Добавляем вариативные свойства
|
||
for (auto it = properties.begin(); it != properties.end(); ++it) {
|
||
m_database->setVariantProperty(componentId, variantId, it.key(), it.value());
|
||
}
|
||
|
||
// Устанавливаем статус DNF/Fitted
|
||
bool isDnf = false;
|
||
if (variantIndex < data.dnfVariantDesignatorsList.size()) {
|
||
isDnf = data.dnfVariantDesignatorsList[variantIndex].contains(designator);
|
||
}
|
||
|
||
m_database->setComponentVariantFitted(componentId, variantId, !isDnf);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Сохранение параметров проекта
|
||
void AltiumParser::saveProjectParameters(const ProjectData &data)
|
||
{
|
||
if (!m_database) return;
|
||
|
||
// Создаем плоский список всех свойств компонентов для обработки сложных строк
|
||
QList<ComponentProperty> allComponentProps;
|
||
for (const QList<ComponentProperty> &componentProps : data.componentsList) {
|
||
allComponentProps.append(componentProps);
|
||
}
|
||
|
||
for (int variantIndex = 0; variantIndex < data.prjParamsVariantList.size(); variantIndex++) {
|
||
const QList<QStringList> &variantParams = data.prjParamsVariantList[variantIndex];
|
||
QString variantName = (variantIndex < data.variantNames.size()) ? data.variantNames[variantIndex] : "No Variations";
|
||
|
||
for (const QStringList ¶m : variantParams) {
|
||
if (param.size() >= 2) {
|
||
QString name = param[0];
|
||
QString value = param[1];
|
||
|
||
// Обработка сложных строк вида ='prop1'+'prop2' или ="prop1"+"prop2"
|
||
value = makeComplexStringIfItIs(value, allComponentProps);
|
||
|
||
m_database->setProjectParam(name, value);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Сохранение данных PCB в базу данных
|
||
void AltiumParser::savePCBDataToDatabase(const ProjectData &data)
|
||
{
|
||
if (!m_database) {
|
||
return;
|
||
}
|
||
|
||
// Создаем структуру PCBData
|
||
PCBData pcbData;
|
||
pcbData.id = -1; // Новая запись
|
||
pcbData.layerCount = data.pcbLayerCount;
|
||
pcbData.dielMaterials = data.pcbDielMaterials;
|
||
|
||
// Сохраняем данные PCB
|
||
m_database->savePCBData(pcbData);
|
||
}
|
||
|
||
// Установка данных в модель
|
||
void AltiumParser::setupModelData(const ProjectData &data, ComponentModel *model)
|
||
{
|
||
// Устанавливаем данные в модель
|
||
m_componentsList = data.componentsList;
|
||
m_variantNamesList = data.variantNames;
|
||
|
||
// Создаем варианты
|
||
m_variantsList.clear();
|
||
for (const QString &variantName : data.variantNames) {
|
||
VariantInfo variant;
|
||
variant.name = variantName;
|
||
variant.dnfDesignators = QStringList();
|
||
variant.fittedDesignators = QStringList();
|
||
variant.componentVariants = QList<QList<ComponentProperty>>();
|
||
variant.parameters = QList<QMap<QString, QString>>();
|
||
m_variantsList.append(variant);
|
||
}
|
||
|
||
|
||
}
|
||
|
||
QString AltiumParser::findParamDesignator(QTextStream &reader, const QString ¶mNumber)
|
||
{
|
||
qint64 currentPos = reader.pos();
|
||
QString designator;
|
||
|
||
// Читаем следующие строки для поиска ParamDesignator
|
||
for (int i = 0; i < 10; i++) {
|
||
QString line = reader.readLine().trimmed();
|
||
if (line.startsWith("ParamDesignator" + paramNumber + "=")) {
|
||
designator = line.mid(15 + paramNumber.length() + 1);
|
||
break;
|
||
}
|
||
}
|
||
|
||
// Возвращаемся к исходной позиции
|
||
reader.seek(currentPos);
|
||
|
||
return designator;
|
||
}
|
||
|
||
QString AltiumParser::makeComplexStringIfItIs(const QString &text, const QList<ComponentProperty> &componentProps)
|
||
{
|
||
if (text.isEmpty() || text[0] != '=') {
|
||
return text;
|
||
}
|
||
|
||
QString result = text.mid(1); // Убираем '='
|
||
|
||
// Обрабатываем сложные строки вида ='prop1'+'prop2' или ="prop1"+"prop2"
|
||
QRegularExpression regex(R"(['"]([^'"]+)['"])");
|
||
QRegularExpressionMatchIterator it = regex.globalMatch(result);
|
||
|
||
while (it.hasNext()) {
|
||
QRegularExpressionMatch match = it.next();
|
||
QString propName = match.captured(1);
|
||
|
||
// Ищем значение свойства
|
||
QString propValue;
|
||
for (const ComponentProperty &prop : componentProps) {
|
||
if (prop.name == propName) {
|
||
propValue = prop.text;
|
||
break;
|
||
}
|
||
}
|
||
|
||
// Заменяем в результате
|
||
result.replace(match.captured(0), propValue);
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
QString AltiumParser::makeDesignatorForOrdering(const QString &designator)
|
||
{
|
||
// Извлекаем буквенную и числовую части
|
||
QRegularExpression regex(R"(([A-Za-z]+)(\d+))");
|
||
QRegularExpressionMatch match = regex.match(designator);
|
||
|
||
if (match.hasMatch()) {
|
||
QString letters = match.captured(1);
|
||
QString numbers = match.captured(2);
|
||
|
||
// Форматируем для правильной сортировки
|
||
return letters.toUpper() + numbers.rightJustified(5, '0');
|
||
}
|
||
|
||
return designator.toUpper();
|
||
}
|
||
|
||
QString AltiumParser::extractComponentDesignation(const QString &line)
|
||
{
|
||
Q_UNUSED(line)
|
||
// TODO: Реализовать извлечение обозначения компонента
|
||
return QString();
|
||
}
|
||
|
||
QString AltiumParser::extractComponentName(const QString &line)
|
||
{
|
||
Q_UNUSED(line)
|
||
// TODO: Реализовать извлечение наименования компонента
|
||
return QString();
|
||
}
|
||
|
||
QString AltiumParser::extractComponentValue(const QString &line)
|
||
{
|
||
Q_UNUSED(line)
|
||
// TODO: Реализовать извлечение номинала компонента
|
||
return QString();
|
||
}
|
||
|
||
QString AltiumParser::extractComponentFootprint(const QString &line)
|
||
{
|
||
Q_UNUSED(line)
|
||
// TODO: Реализовать извлечение корпуса компонента
|
||
return QString();
|
||
}
|
||
|
||
QString AltiumParser::extractComponentManufacturer(const QString &line)
|
||
{
|
||
Q_UNUSED(line)
|
||
// TODO: Реализовать извлечение производителя компонента
|
||
return QString();
|
||
}
|
||
|
||
QString AltiumParser::extractComponentPartNumber(const QString &line)
|
||
{
|
||
Q_UNUSED(line)
|
||
// TODO: Реализовать извлечение партномера компонента
|
||
return QString();
|
||
}
|
||
|
||
int AltiumParser::extractComponentQuantity(const QString &line)
|
||
{
|
||
Q_UNUSED(line)
|
||
// TODO: Реализовать извлечение количества компонента
|
||
return 1;
|
||
}
|
||
|
||
QString AltiumParser::lastError() const
|
||
{
|
||
return m_lastError;
|
||
}
|
||
|
||
void AltiumParser::clearError()
|
||
{
|
||
m_lastError.clear();
|
||
}
|
||
|
||
QString AltiumParser::getPcbDocFileName() const
|
||
{
|
||
return m_pcbDocFileName;
|
||
}
|
||
|
||
void AltiumParser::initializeRegex()
|
||
{
|
||
// Инициализация регулярных выражений для парсинга
|
||
m_designationRegex = QRegularExpression(R"(([A-Z][0-9]+))");
|
||
m_nameRegex = QRegularExpression(R"(([A-Za-zА-Яа-я\s]+))");
|
||
m_valueRegex = QRegularExpression(R"(([0-9]+[A-Za-z]*))");
|
||
m_footprintRegex = QRegularExpression(R"(([A-Za-z0-9_-]+))");
|
||
m_manufacturerRegex = QRegularExpression(R"(([A-Za-zА-Яа-я\s]+))");
|
||
m_partNumberRegex = QRegularExpression(R"(([A-Za-z0-9_-]+))");
|
||
m_quantityRegex = QRegularExpression(R"(([0-9]+))");
|
||
|
||
|
||
}
|
||
|
||
bool AltiumParser::parseSchLine(const QString &line, QMap<QString, QString> &component)
|
||
{
|
||
Q_UNUSED(line)
|
||
Q_UNUSED(component)
|
||
// TODO: Реализовать парсинг строки схемы
|
||
return false;
|
||
}
|
||
|
||
bool AltiumParser::parsePcbLine(const QString &line, QMap<QString, QString> &component)
|
||
{
|
||
Q_UNUSED(line)
|
||
Q_UNUSED(component)
|
||
// TODO: Реализовать парсинг строки печатной платы
|
||
return false;
|
||
}
|
||
|
||
bool AltiumParser::parseBomLine(const QString &line, QMap<QString, QString> &component)
|
||
{
|
||
Q_UNUSED(line)
|
||
Q_UNUSED(component)
|
||
// TODO: Реализовать парсинг строки BOM
|
||
return false;
|
||
}
|
||
|
||
QString AltiumParser::cleanString(const QString &str)
|
||
{
|
||
return str.trimmed();
|
||
}
|
||
|
||
bool AltiumParser::isValidComponent(const QMap<QString, QString> &component)
|
||
{
|
||
Q_UNUSED(component)
|
||
// TODO: Реализовать валидацию компонента
|
||
return true;
|
||
}
|
||
|
||
void AltiumParser::setError(const QString &error)
|
||
{
|
||
m_lastError = error;
|
||
emit parsingError(error);
|
||
}
|
||
|
||
DataBaseController* AltiumParser::getDatabase() const
|
||
{
|
||
return m_database;
|
||
}
|
||
|
||
void AltiumParser::setDatabase(DataBaseController *database)
|
||
{
|
||
m_database = database;
|
||
}
|
||
|
||
// Асинхронный парсинг
|
||
bool AltiumParser::parseFileAsync(const QString &filename, ComponentModel *model)
|
||
{
|
||
if (m_isParsingAsync) {
|
||
return false;
|
||
}
|
||
|
||
m_isParsingAsync = true;
|
||
emit parsingStarted();
|
||
emit parsingProgress(0);
|
||
|
||
// Запускаем парсинг в отдельном потоке
|
||
QFuture<bool> future = QtConcurrent::run([this, filename, model]() {
|
||
return parsePrjPcbFile(filename, model);
|
||
});
|
||
|
||
m_parseWatcher->setFuture(future);
|
||
return true;
|
||
}
|
||
|
||
bool AltiumParser::isParsingAsync() const
|
||
{
|
||
return m_isParsingAsync;
|
||
}
|
||
|
||
void AltiumParser::cancelParsing()
|
||
{
|
||
if (m_isParsingAsync && m_parseWatcher->isRunning()) {
|
||
|
||
m_parseWatcher->cancel();
|
||
m_isParsingAsync = false;
|
||
}
|
||
}
|
||
|
||
void AltiumParser::onParseFinished()
|
||
{
|
||
bool success = m_parseWatcher->result();
|
||
m_isParsingAsync = false;
|
||
|
||
|
||
|
||
if (success) {
|
||
emit parsingProgress(100);
|
||
emit parsingFinished(true);
|
||
} else {
|
||
emit parsingFinished(false);
|
||
emit parsingError(m_lastError);
|
||
}
|
||
}
|
||
|
||
void AltiumParser::onParseProgress(int progress)
|
||
{
|
||
emit parsingProgress(progress);
|
||
}
|
||
|
||
bool AltiumParser::parseSchDocFile(const QString &filename, QList<QList<ComponentProperty>> &componentsList)
|
||
{
|
||
QFile file(filename);
|
||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||
return false;
|
||
}
|
||
|
||
QTextStream reader(&file);
|
||
reader.setCodec(QTextCodec::codecForName("Windows-1251"));
|
||
|
||
QList<ComponentProperty> componentPropList;
|
||
QList<ComponentProperty> otherPropList;
|
||
bool isNoBom = false;
|
||
bool isFirstPartOfComponent = true;
|
||
bool isComponent = false;
|
||
int numberOfStrings = 0;
|
||
|
||
while (!reader.atEnd()) {
|
||
QString schStr = reader.readLine().trimmed();
|
||
QStringList schStrArray = schStr.split('|');
|
||
|
||
for (int i = 0; i < schStrArray.size() - 1; i++) {
|
||
if (isComponent == true) {
|
||
if (schStrArray[i].length() >= 23) {
|
||
if (schStrArray[i].left(23).toUpper() == "COMPONENTKINDVERSION2=5") {
|
||
isNoBom = true;
|
||
}
|
||
}
|
||
|
||
if (schStrArray[i].length() >= 15) {
|
||
if (schStrArray[i].left(14).toUpper() == "CURRENTPARTID=") {
|
||
if (schStrArray[i].left(15).toUpper() != "CURRENTPARTID=1" || schStrArray[i].length() > 15) {
|
||
isFirstPartOfComponent = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (schStrArray[i].length() > 5 && schStrArray[i + 1].length() > 5) {
|
||
if (schStrArray[i].left(5).toUpper() == "TEXT=" && schStrArray[i + 1].left(5).toUpper() == "NAME=") {
|
||
ComponentProperty prop;
|
||
prop.name = schStrArray[i + 1].mid(5);
|
||
prop.text = schStrArray[i].mid(5);
|
||
|
||
if (isNoBom == false) {
|
||
componentPropList.append(prop);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (schStrArray[i].length() >= 8) {
|
||
if (schStrArray[i].left(7).toUpper() == "HEADER=" || (schStrArray[i].toUpper() == "RECORD=1" && schStrArray[i].length() == 8)) {
|
||
if (isNoBom == false && componentPropList.size() > 0 && isFirstPartOfComponent == true) {
|
||
componentsList.append(componentPropList);
|
||
numberOfStrings++;
|
||
}
|
||
|
||
isNoBom = false;
|
||
isComponent = false;
|
||
isFirstPartOfComponent = true;
|
||
componentPropList.clear();
|
||
}
|
||
}
|
||
} else if (isComponent == false) {
|
||
// Запись полей основной надписи в базу данных проекта
|
||
if (schStrArray[i].length() > 5 && schStrArray[i + 1].length() > 5) {
|
||
if (schStrArray[i].left(5).toUpper() == "TEXT=" && schStrArray[i + 1].left(5).toUpper() == "NAME=") {
|
||
ComponentProperty prop;
|
||
prop.name = schStrArray[i + 1].mid(5);
|
||
prop.text = schStrArray[i].mid(5);
|
||
|
||
otherPropList.append(prop);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Ищем все записи компонента
|
||
if (schStrArray[i].length() == 8) {
|
||
if (schStrArray[i].left(8).toUpper() == "RECORD=1") {
|
||
isComponent = true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Работа с составными именами типа ='prop1'+'prop2'
|
||
for (int i = 0; i < otherPropList.size(); i++) {
|
||
otherPropList[i].text = makeComplexStringIfItIs(otherPropList[i].text, otherPropList);
|
||
}
|
||
|
||
qDebug() << "AltiumParser: Парсинг схемы завершен, найдено компонентов:" << numberOfStrings;
|
||
|
||
file.close();
|
||
return true;
|
||
}
|
||
|
||
bool AltiumParser::parsePcbDocFile(const QString &filename, QList<DielProperty> &dielMaterials, int &pcbLayersCount)
|
||
{
|
||
qDebug() << "AltiumParser PCB: Начинаем парсинг файла платы:" << filename;
|
||
|
||
QFile file(filename);
|
||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||
qDebug() << "AltiumParser PCB: Не удалось открыть файл платы:" << filename;
|
||
return false;
|
||
}
|
||
|
||
pcbLayersCount = 0;
|
||
dielMaterials.clear();
|
||
|
||
QTextStream in(&file);
|
||
in.setCodec(QTextCodec::codecForName("Windows-1251"));
|
||
|
||
qDebug() << "AltiumParser PCB: Начинаем чтение файла построчно...";
|
||
|
||
// Переменные для поиска LAYERSET
|
||
QString signalLayersetNumber;
|
||
QString layersetLayers;
|
||
|
||
while (!in.atEnd()) {
|
||
QString line = in.readLine();
|
||
QStringList parts = line.split('|');
|
||
|
||
for (int i = 0; i < parts.size() - 1; i++) {
|
||
QString part = parts[i];
|
||
|
||
// Ищем LAYERSETxNAME=&Signal Layers
|
||
if (part.startsWith("LAYERSET") && part.contains("NAME=&Signal Layers")) {
|
||
QRegularExpression layersetNameRegex(R"(LAYERSET(\d+)NAME=&Signal Layers)");
|
||
QRegularExpressionMatch match = layersetNameRegex.match(part);
|
||
if (match.hasMatch()) {
|
||
signalLayersetNumber = match.captured(1);
|
||
}
|
||
}
|
||
|
||
// Ищем соответствующий LAYERSETxLAYERS=
|
||
if (!signalLayersetNumber.isEmpty() && part.startsWith(QString("LAYERSET%1LAYERS=").arg(signalLayersetNumber))) {
|
||
layersetLayers = part.mid(QString("LAYERSET%1LAYERS=").arg(signalLayersetNumber).length());
|
||
|
||
// Сбрасываем счетчик слоев перед подсчетом
|
||
pcbLayersCount = 0;
|
||
|
||
// Подсчитываем слои, исключая MultiLayer
|
||
QStringList layers = layersetLayers.split(',');
|
||
for (const QString &layer : layers) {
|
||
if (layer.trimmed() != "MultiLayer") {
|
||
pcbLayersCount++;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Ищем записи V9_STACK_LAYER для подсчета слоев (старый метод как fallback)
|
||
if (part.startsWith("V9_STACK_LAYER") && part.contains("_NAME=")) {
|
||
// Извлекаем номер слоя из записи V9_STACK_LAYER{номер}_NAME=
|
||
QRegularExpression layerRegex(R"(V9_STACK_LAYER(\d+)_NAME=)");
|
||
QRegularExpressionMatch match = layerRegex.match(part);
|
||
if (match.hasMatch()) {
|
||
int layerNumber = match.captured(1).toInt();
|
||
if (layerNumber > pcbLayersCount) {
|
||
pcbLayersCount = layerNumber + 1; // +1 потому что нумерация с 0
|
||
}
|
||
}
|
||
}
|
||
|
||
// Ищем записи V9_STACK_LAYER с диэлектрическими свойствами
|
||
if (part.startsWith("V9_STACK_LAYER") && part.contains("_DIELTYPE=")) {
|
||
// Извлекаем номер слоя и тип диэлектрика
|
||
QRegularExpression dielTypeRegex(R"(V9_STACK_LAYER(\d+)_DIELTYPE=(\d+))");
|
||
QRegularExpressionMatch match = dielTypeRegex.match(part);
|
||
if (match.hasMatch()) {
|
||
QString layerNumber = match.captured(1);
|
||
int dielType = match.captured(2).toInt();
|
||
|
||
// Создаем новый материал
|
||
DielProperty material;
|
||
material.dielType = dielType;
|
||
material.layerNumber = layerNumber.toInt();
|
||
|
||
// Ищем толщину для этого слоя
|
||
QString heightKey = QString("V9_STACK_LAYER%1_DIELHEIGHT=").arg(layerNumber);
|
||
QString materialKey = QString("V9_STACK_LAYER%1_DIELMATERIAL=").arg(layerNumber);
|
||
|
||
// Ищем в текущей строке и следующих частях
|
||
for (int j = i; j < parts.size(); j++) {
|
||
QString searchPart = parts[j];
|
||
|
||
// Ищем толщину
|
||
if (searchPart.startsWith(heightKey)) {
|
||
QString heightStr = searchPart.mid(heightKey.length());
|
||
if (heightStr.endsWith("mil")) {
|
||
heightStr.chop(3);
|
||
}
|
||
bool ok;
|
||
double height = heightStr.toDouble(&ok);
|
||
if (ok) {
|
||
material.height = height * 0.0254; // Конвертация в мм
|
||
}
|
||
}
|
||
|
||
// Ищем материал
|
||
if (searchPart.startsWith(materialKey)) {
|
||
material.name = "DielMaterial";
|
||
material.value = searchPart.mid(materialKey.length());
|
||
|
||
// Добавляем материал в список, если у нас есть все данные
|
||
if (!material.value.isEmpty() && material.height > 0) {
|
||
dielMaterials.append(material);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Также ищем записи LAYER_V8_ для совместимости со старым форматом
|
||
if (part.startsWith("LAYER_V8_") && part.contains("_DIELTYPE=")) {
|
||
QRegularExpression layerV8Regex(R"(LAYER_V8_(\d+)_DIELTYPE=(\d+))");
|
||
QRegularExpressionMatch match = layerV8Regex.match(part);
|
||
if (match.hasMatch()) {
|
||
QString layerNumber = match.captured(1);
|
||
int dielType = match.captured(2).toInt();
|
||
|
||
DielProperty material;
|
||
material.dielType = dielType;
|
||
|
||
QString heightKey = QString("LAYER_V8_%1_DIELHEIGHT=").arg(layerNumber);
|
||
QString materialKey = QString("LAYER_V8_%1_DIELMATERIAL=").arg(layerNumber);
|
||
|
||
for (int j = i; j < parts.size(); j++) {
|
||
QString searchPart = parts[j];
|
||
|
||
if (searchPart.startsWith(heightKey)) {
|
||
QString heightStr = searchPart.mid(heightKey.length());
|
||
if (heightStr.endsWith("mil")) {
|
||
heightStr.chop(3);
|
||
}
|
||
bool ok;
|
||
double height = heightStr.toDouble(&ok);
|
||
if (ok) {
|
||
material.height = height * 0.0254;
|
||
}
|
||
}
|
||
|
||
if (searchPart.startsWith(materialKey)) {
|
||
material.name = "DielMaterial";
|
||
material.value = searchPart.mid(materialKey.length());
|
||
|
||
if (!material.value.isEmpty() && material.height > 0) {
|
||
dielMaterials.append(material);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
file.close();
|
||
|
||
// Если не нашли LAYERSET, используем старый метод подсчета
|
||
if (signalLayersetNumber.isEmpty()) {
|
||
qDebug() << "AltiumParser PCB: LAYERSET не найден, используем старый метод подсчета слоев";
|
||
// Убираем слой MultiLayer если он есть (для старого метода)
|
||
if (pcbLayersCount > 0) {
|
||
pcbLayersCount--;
|
||
}
|
||
}
|
||
|
||
// Итоговое логирование результатов парсинга
|
||
qDebug() << "AltiumParser PCB: === РЕЗУЛЬТАТЫ ПАРСИНГА PCB ===";
|
||
qDebug() << "AltiumParser PCB: Количество слоев:" << pcbLayersCount;
|
||
qDebug() << "AltiumParser PCB: Количество материалов:" << dielMaterials.size();
|
||
|
||
// Выводим найденные материалы
|
||
for (int i = 0; i < dielMaterials.size(); i++) {
|
||
const DielProperty &material = dielMaterials[i];
|
||
QString typeStr = (material.dielType == 1) ? "ядро" : (material.dielType == 2) ? "препрег" : "неизвестно";
|
||
qDebug() << "AltiumParser PCB: Материал" << i << ":" << material.name << "=" << material.value
|
||
<< "тип:" << typeStr << "толщина:" << material.height << "мм";
|
||
}
|
||
|
||
if (pcbLayersCount == 0) {
|
||
qDebug() << "AltiumParser PCB: ПРЕДУПРЕЖДЕНИЕ: Слои не найдены в файле!";
|
||
}
|
||
if (dielMaterials.isEmpty()) {
|
||
qDebug() << "AltiumParser PCB: ПРЕДУПРЕЖДЕНИЕ: Материалы не найдены в файле!";
|
||
}
|
||
|
||
qDebug() << "AltiumParser PCB: Парсинг платы завершен";
|
||
return true;
|
||
}
|
||
|