#include "altiumparser.h" #include "databasecontroller.h" #include "../model/componentmodel.h" #include #include #include #include #include #include #include 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(this); connect(m_parseWatcher, &QFutureWatcher::finished, this, &AltiumParser::onParseFinished); connect(m_parseWatcher, &QFutureWatcher::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()); } 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 &componentProps : data.componentsList) { QString designator; QMap 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> &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 &componentProps : variantComponents) { QString designator; QMap 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 allComponentProps; for (const QList &componentProps : data.componentsList) { allComponentProps.append(componentProps); } for (int variantIndex = 0; variantIndex < data.prjParamsVariantList.size(); variantIndex++) { const QList &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>(); variant.parameters = QList>(); 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 &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 &component) { Q_UNUSED(line) Q_UNUSED(component) // TODO: Реализовать парсинг строки схемы return false; } bool AltiumParser::parsePcbLine(const QString &line, QMap &component) { Q_UNUSED(line) Q_UNUSED(component) // TODO: Реализовать парсинг строки печатной платы return false; } bool AltiumParser::parseBomLine(const QString &line, QMap &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 &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 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> &componentsList) { QFile file(filename); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { return false; } QTextStream reader(&file); reader.setCodec(QTextCodec::codecForName("Windows-1251")); QList componentPropList; QList 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 &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; }