diff --git a/.github/workflows/publish.RevitClassifierParameters.yml b/.github/workflows/publish.RevitClassifierParameters.yml
new file mode 100644
index 000000000..925325e6c
--- /dev/null
+++ b/.github/workflows/publish.RevitClassifierParameters.yml
@@ -0,0 +1,37 @@
+name: publish RevitClassifierParameters
+
+on:
+ workflow_dispatch:
+ pull_request:
+ types: [ closed, synchronize, review_requested ]
+ branches: [ main, master ]
+ paths:
+ - '**RevitClassifierParameters**.cs'
+ - '**RevitClassifierParameters**.xaml'
+
+env:
+ plugin-name: "RevitClassifierParameters"
+
+jobs:
+ build:
+ name: build
+ runs-on: windows-latest
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+
+ # Install the .NET workload
+ - name: Install .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 8.0.x
+
+ # Add DevExrepress package source: https://nuget.devexpress.com/
+ - name: Add DevExpress package source
+ run: dotnet nuget add source https://nuget.devexpress.com/api -n DXFeed -u DevExpress -p ${{ secrets.DEVEXPRESS_FEED_AUTHORIZATION_KEY }}
+
+ - name: Run './build.cmd '
+ run: ./build.cmd publish --profile ${{ env.plugin-name }} --pull-request-merged ${{ github.event.pull_request.merged }} --extensions-app-token ${{ secrets.EXTENSIONS_APP_TOKEN }} --revit-plugins-app-token ${{ secrets.REVIT_PLUGINS_APP_TOKEN }}
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.nuke/parameters.RevitClassifierParameters.json b/.nuke/parameters.RevitClassifierParameters.json
new file mode 100644
index 000000000..ef735f922
--- /dev/null
+++ b/.nuke/parameters.RevitClassifierParameters.json
@@ -0,0 +1,15 @@
+{
+ "$schema": "./build.schema.json",
+ "Solution": "RevitPlugins.slnx",
+ "PluginName": "RevitClassifierParameters",
+ "PublishDirectory": "01.BIM.extension\\BIM.tab\\bin",
+ "RevitVersions": [
+ "Rv2022",
+ "Rv2023",
+ "Rv2024"
+ ],
+ "IconUrl": "https://icons8.ru/icon/14624/библиотека",
+ "BundleName": "Параметры ВОР АР",
+ "BundleType": "InvokeButton",
+ "BundleOutput": "01.BIM.extension\\BIM.tab\\ВОР.panel\\Параметры ВОР.pulldown"
+}
diff --git a/RevitPlugins.slnx b/RevitPlugins.slnx
index 03e5e8634..37f8c6906 100644
--- a/RevitPlugins.slnx
+++ b/RevitPlugins.slnx
@@ -64,6 +64,9 @@
+
+
+
diff --git a/src/RevitClassifierParameters/MarkupExtensions/EnumToLocaleExtension.cs b/src/RevitClassifierParameters/MarkupExtensions/EnumToLocaleExtension.cs
new file mode 100644
index 000000000..cb00966c8
--- /dev/null
+++ b/src/RevitClassifierParameters/MarkupExtensions/EnumToLocaleExtension.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Data;
+using System.Windows.Markup;
+
+using dosymep.SimpleServices;
+using dosymep.WpfCore;
+
+namespace RevitClassifierParameters.MarkupExtensions;
+
+internal sealed class EnumToLocaleExtension : MarkupExtension, IValueConverter {
+ private IHasLocalization _hasLocalization;
+
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
+ if(_hasLocalization is null) {
+ return value;
+ }
+
+ string enumName = $"{value?.GetType().Name}.{value}";
+ return _hasLocalization.LocalizationService.GetLocalizedString(enumName);
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
+ return Binding.DoNothing;
+ }
+
+ public override object ProvideValue(IServiceProvider serviceProvider) {
+ _hasLocalization = serviceProvider.GetRootObject();
+ if(_hasLocalization is not null) {
+ return this;
+ }
+
+ var frameworkElement = serviceProvider.GetRootObject();
+ if(frameworkElement is not null) {
+ frameworkElement.Loaded += FrameworkElementOnLoaded;
+ }
+
+ return this;
+ }
+
+ private void FrameworkElementOnLoaded(object sender, RoutedEventArgs e) {
+ if(sender is IHasLocalization hasLocalization) {
+ _hasLocalization = hasLocalization;
+ }
+
+ if(sender is FrameworkElement frameworkElement) {
+ frameworkElement.Loaded -= FrameworkElementOnLoaded;
+ }
+ }
+}
diff --git a/src/RevitClassifierParameters/Models/ExcelReaderBase.cs b/src/RevitClassifierParameters/Models/ExcelReaderBase.cs
new file mode 100644
index 000000000..e5abf3ef9
--- /dev/null
+++ b/src/RevitClassifierParameters/Models/ExcelReaderBase.cs
@@ -0,0 +1,92 @@
+using System.IO;
+
+using ClosedXML.Excel;
+
+using dosymep.SimpleServices;
+
+namespace RevitClassifierParameters.Models;
+
+///
+/// Базовый класс для чтения данных из Excel-файла (.xlsx) через ClosedXML.
+/// Инкапсулирует открытие книги и чтение диапазона первого листа
+/// в двумерный массив значений ячеек.
+///
+/// Тип результата разбора данных листа.
+internal abstract class ExcelReaderBase {
+ protected readonly ILocalizationService _localizationService;
+ protected readonly IMessageBoxService _messageBoxService;
+
+ protected ExcelReaderBase(
+ ILocalizationService localizationService,
+ IMessageBoxService messageBoxService) {
+ _localizationService = localizationService;
+ _messageBoxService = messageBoxService;
+ }
+
+ ///
+ /// Номер первой строки с данными (строки выше считаются заголовком).
+ ///
+ protected abstract int FirstDataRow { get; }
+
+ ///
+ /// Количество считываемых столбцов, начиная с первого.
+ ///
+ protected abstract int ColumnCount { get; }
+
+ ///
+ /// Ключ локализации сообщения о пустом листе/файле.
+ ///
+ protected abstract string EmptySheetMessageKey { get; }
+
+ public TResult Read(string path) {
+ using var stream = File.OpenRead(path);
+ using var workbook = new XLWorkbook(stream);
+
+ var worksheet = workbook.Worksheet(1);
+
+ var lastUsedRow = worksheet.LastRowUsed(XLCellsUsedOptions.AllContents);
+ int lastRow = lastUsedRow?.RowNumber() ?? 0;
+
+ if(lastRow < FirstDataRow) {
+ _messageBoxService.Show(_localizationService.GetLocalizedString(EmptySheetMessageKey));
+ return default;
+ }
+
+ var values = ReadValues(worksheet, lastRow);
+ return BuildResult(values);
+ }
+
+ ///
+ /// Строит результат разбора из прочитанного двумерного массива значений ячеек.
+ /// Индексация массива начинается с 0 по обоим измерениям:
+ /// первое измерение — строки данных (0..rowCount-1),
+ /// второе — столбцы (0..ColumnCount-1).
+ ///
+ protected abstract TResult BuildResult(object[,] rows);
+
+ protected static string GetString(object value) {
+ return value?.ToString()?.Trim();
+ }
+
+ ///
+ /// Читает диапазон данных листа в двумерный массив с 0-based индексацией.
+ /// Размерности массива равны фактическому числу строк данных и .
+ ///
+ private object[,] ReadValues(IXLWorksheet worksheet, int lastRow) {
+ int rowCount = lastRow - FirstDataRow + 1;
+ var values = new object[rowCount, ColumnCount];
+
+ for(int r = 0; r < rowCount; r++) {
+ int sheetRow = FirstDataRow + r;
+ for(int c = 0; c < ColumnCount; c++) {
+ var cell = worksheet.Cell(sheetRow, c + 1);
+ values[r, c] = GetCellValue(cell);
+ }
+ }
+ return values;
+ }
+
+ private static string GetCellValue(IXLCell cell) {
+ return cell.IsEmpty() ? null : cell.GetString();
+ }
+}
diff --git a/src/RevitClassifierParameters/Models/FacadeType/FacadeTypeExcelReader.cs b/src/RevitClassifierParameters/Models/FacadeType/FacadeTypeExcelReader.cs
new file mode 100644
index 000000000..25555ad57
--- /dev/null
+++ b/src/RevitClassifierParameters/Models/FacadeType/FacadeTypeExcelReader.cs
@@ -0,0 +1,60 @@
+using System.Collections.Generic;
+
+using dosymep.SimpleServices;
+
+namespace RevitClassifierParameters.Models.FacadeType;
+
+///
+/// Читает Excel-файл с правилами заполнения типа фасада.
+/// Ожидается три столбца:
+/// 1 - характеристика функции (со скобками, например "(ФМ)");
+/// 2 - сокращение основного материала (например "ОК");
+/// 3 - значение для записи в параметр.
+/// Первая строка — заголовок, данные начинаются со второй строки.
+///
+internal class FacadeTypeExcelReader : ExcelReaderBase> {
+ public FacadeTypeExcelReader(
+ ILocalizationService localizationService,
+ IMessageBoxService messageBoxService)
+ : base(localizationService, messageBoxService) {
+ }
+
+ protected override int FirstDataRow => 2;
+
+ protected override int ColumnCount => 3;
+
+ protected override string EmptySheetMessageKey => "Reader.FacadeTypeFileEmpty";
+
+ protected override List BuildResult(object[,] rows) {
+ var result = new List();
+
+ int rowCount = rows.GetLength(0);
+
+ for(int row = 0; row < rowCount; row++) {
+ string function = GetString(rows[row, 0]);
+ string material = GetString(rows[row, 1]);
+ string value = GetString(rows[row, 2]);
+
+ if(string.IsNullOrWhiteSpace(function)
+ || string.IsNullOrWhiteSpace(material)
+ || string.IsNullOrWhiteSpace(value)) {
+ continue;
+ }
+
+ result.Add(new FacadeTypeItem {
+ FunctionCharacteristic = Normalize(function),
+ MaterialAbbreviation = Normalize(material),
+ Value = value
+ });
+ }
+ return result;
+ }
+
+ ///
+ /// Приводит ключевое поле правила (характеристику функции или сокращение материала)
+ /// к единому виду для последующего сравнения с распарсенным именем типоразмера стены.
+ ///
+ private static string Normalize(string value) {
+ return (value ?? string.Empty).Trim().ToUpperInvariant();
+ }
+}
diff --git a/src/RevitClassifierParameters/Models/FacadeType/FacadeTypeItem.cs b/src/RevitClassifierParameters/Models/FacadeType/FacadeTypeItem.cs
new file mode 100644
index 000000000..e9375bec1
--- /dev/null
+++ b/src/RevitClassifierParameters/Models/FacadeType/FacadeTypeItem.cs
@@ -0,0 +1,23 @@
+namespace RevitClassifierParameters.Models.FacadeType;
+
+///
+/// Строка правил заполнения типа фасада, прочитанная из Excel-файла.
+///
+internal class FacadeTypeItem {
+ ///
+ /// Характеристика функции (столбец 1) хранится со скобками, например "(ФМ)".
+ /// Приводится к нормализованному виду (Trim + верхний регистр) при чтении из Excel.
+ ///
+ public string FunctionCharacteristic { get; set; }
+
+ ///
+ /// Сокращение основного материала (столбец 2), например "ОК".
+ /// Приводится к нормализованному виду (Trim + верхний регистр) при чтении из Excel.
+ ///
+ public string MaterialAbbreviation { get; set; }
+
+ ///
+ /// Значение (столбец 3), которое записывается в параметр стены.
+ ///
+ public string Value { get; set; }
+}
diff --git a/src/RevitClassifierParameters/Models/FacadeType/FacadeTypeNameParser.cs b/src/RevitClassifierParameters/Models/FacadeType/FacadeTypeNameParser.cs
new file mode 100644
index 000000000..6fe025d12
--- /dev/null
+++ b/src/RevitClassifierParameters/Models/FacadeType/FacadeTypeNameParser.cs
@@ -0,0 +1,70 @@
+using System.Text.RegularExpressions;
+
+namespace RevitClassifierParameters.Models.FacadeType;
+
+///
+/// Результат разбора имени типоразмера стены.
+///
+internal readonly struct FacadeTypeNameParts {
+ public FacadeTypeNameParts(string function, string material) {
+ Function = function;
+ Material = material;
+ }
+
+ ///
+ /// Характеристика функции (со скобками), например "(ФМ)".
+ ///
+ public string Function { get; }
+
+ ///
+ /// Сокращение основного материала, например "ОК".
+ ///
+ public string Material { get; }
+
+ ///
+ /// Успешно ли извлечены обе подстроки.
+ ///
+ public bool IsValid =>
+ !string.IsNullOrWhiteSpace(Function) && !string.IsNullOrWhiteSpace(Material);
+}
+
+///
+/// Разбирает имя типоразмера стены вида
+/// "(ФМ) ОК-5 15 (АРМСТК ФДШ-10 ОК-5) RAL #### Цоколь" на две ключевые подстроки:
+/// характеристику функции в первых скобках ("(ФМ)")
+/// и сокращение основного материала после скобок до первого дефиса ("ОК").
+///
+internal class FacadeTypeNameParser {
+ // Захватывает первые круглые скобки с содержимым и весь остаток строки после них.
+ private static readonly Regex _regex =
+ new(@"^\s*(?\([^)]*\))\s*(?.*)$", RegexOptions.Compiled);
+
+ ///
+ /// Разбирает имя типоразмера стены.
+ ///
+ public FacadeTypeNameParts Parse(string typeName) {
+ if(string.IsNullOrWhiteSpace(typeName)) {
+ return default;
+ }
+
+ var match = _regex.Match(typeName);
+ if(!match.Success) {
+ return default;
+ }
+
+ string function = match.Groups["function"].Value.Trim();
+ string material = ExtractMaterial(match.Groups["rest"].Value);
+
+ return new FacadeTypeNameParts(function, material);
+ }
+
+ private static string ExtractMaterial(string rest) {
+ if(string.IsNullOrWhiteSpace(rest)) {
+ return string.Empty;
+ }
+
+ int dashIndex = rest.IndexOf('-');
+ string material = dashIndex >= 0 ? rest.Substring(0, dashIndex) : rest;
+ return material.Trim();
+ }
+}
diff --git a/src/RevitClassifierParameters/Models/FacadeType/FacadeTypeSetter.cs b/src/RevitClassifierParameters/Models/FacadeType/FacadeTypeSetter.cs
new file mode 100644
index 000000000..d97c5e39b
--- /dev/null
+++ b/src/RevitClassifierParameters/Models/FacadeType/FacadeTypeSetter.cs
@@ -0,0 +1,72 @@
+using System.Collections.Generic;
+using System.Linq;
+
+using Autodesk.Revit.DB;
+
+using dosymep.Revit;
+using dosymep.SimpleServices;
+
+namespace RevitClassifierParameters.Models.FacadeType;
+
+///
+/// Заполняет параметр типа фасада у стен проекта по правилам, прочитанным из Excel.
+/// Логика сопоставления имени типоразмера стены с правилами и записи значений
+/// сосредоточена в этом классе.
+///
+internal class FacadeTypeSetter {
+ private readonly RevitRepository _revitRepository;
+ private readonly ILocalizationService _localizationService;
+ private readonly FacadeTypeNameParser _facadeTypeNameParser = new();
+
+ public FacadeTypeSetter(RevitRepository revitRepository, ILocalizationService localizationService) {
+ _revitRepository = revitRepository;
+ _localizationService = localizationService;
+ }
+
+ ///
+ /// Перебирает стены проекта и, если имя типоразмера содержит на нужных местах
+ /// характеристику функции и сокращение основного материала из файла правил,
+ /// записывает соответствующее значение в параметр с именем .
+ ///
+ /// Имя параметра стены, в который записывается тип фасада.
+ /// Правила заполнения типа фасада, прочитанные из Excel.
+ public void SetFacadeType(string paramName, IReadOnlyList facadeTypes) {
+ if(string.IsNullOrEmpty(paramName) || facadeTypes is null || facadeTypes.Count == 0) {
+ return;
+ }
+
+ using var transaction = _revitRepository.Document.StartTransaction(
+ _localizationService.GetLocalizedString("Transaction.SetFacadeType"));
+
+ foreach(var wall in _revitRepository.GetWalls()) {
+ string typeName = _revitRepository.Document.GetElement(wall.GetTypeId())?.Name ?? string.Empty;
+ var parts = _facadeTypeNameParser.Parse(typeName);
+ if(!parts.IsValid) {
+ continue;
+ }
+
+ // Ключевые поля правил уже нормализованы при чтении из Excel,
+ // поэтому здесь нормализуем только подстроки, извлечённые из имени типоразмера стены.
+ string function = parts.Function.Trim().ToUpperInvariant();
+ string material = parts.Material.Trim().ToUpperInvariant();
+
+ var rule = facadeTypes.FirstOrDefault(x =>
+ x.FunctionCharacteristic == function
+ && x.MaterialAbbreviation == material);
+ if(rule is null) {
+ continue;
+ }
+
+ var param = wall.LookupParameter(paramName);
+ if(param == null || param.IsReadOnly || param.StorageType != StorageType.String) {
+ continue;
+ }
+
+ if(param.AsString() != rule.Value) {
+ param.Set(rule.Value);
+ }
+ }
+
+ transaction.Commit();
+ }
+}
diff --git a/src/RevitClassifierParameters/Models/MaterialClassifier/ClassifierExcelReader.cs b/src/RevitClassifierParameters/Models/MaterialClassifier/ClassifierExcelReader.cs
new file mode 100644
index 000000000..81de29b14
--- /dev/null
+++ b/src/RevitClassifierParameters/Models/MaterialClassifier/ClassifierExcelReader.cs
@@ -0,0 +1,98 @@
+using System.Collections.Generic;
+using System.Linq;
+
+using dosymep.SimpleServices;
+
+using RevitClassifierParameters.Models.Work;
+
+namespace RevitClassifierParameters.Models.MaterialClassifier;
+
+internal class ClassifierExcelReader : ExcelReaderBase> {
+ public ClassifierExcelReader(
+ ILocalizationService localizationService,
+ IMessageBoxService messageBoxService)
+ : base(localizationService, messageBoxService) {
+ }
+
+ protected override int FirstDataRow => 3;
+
+ protected override int ColumnCount => 3;
+
+ protected override string EmptySheetMessageKey => "Reader.ClassifierSheetEmpty";
+
+ protected override List BuildResult(object[,] rows) {
+ var result = new List();
+ var groups = new List();
+
+ int rowCount = rows.GetLength(0);
+
+ for(int row = 0; row < rowCount; row++) {
+ string code = GetString(rows[row, 0]);
+ if(string.IsNullOrWhiteSpace(code)) {
+ continue;
+ }
+
+ string name = GetString(rows[row, 1]);
+ string unit = GetString(rows[row, 2]);
+
+ if(string.IsNullOrWhiteSpace(unit)) {
+ AddGroup(result, groups, code, name);
+ } else {
+ AddWork(groups, code, name, unit);
+ }
+ }
+ return result;
+ }
+
+ private void AddGroup(
+ List roots,
+ List groups,
+ string code,
+ string name) {
+ var group = new WorkGroup {
+ Code = code,
+ Name = name
+ };
+
+ groups.Add(group);
+
+ var parent = FindParentGroup(groups, code);
+ if(parent != null) {
+ group.ParentWorkGroup = parent;
+ parent.ChildWorkGroups.Add(group);
+ } else {
+ roots.Add(group);
+ }
+ }
+
+ private void AddWork(
+ List groups,
+ string code,
+ string name,
+ string unit) {
+ var parent = FindParentGroup(groups, code);
+
+ parent?.ChildWorks.Add(new WorkItem {
+ Code = code,
+ Name = name,
+ Unit = unit,
+ ParentWorkGroup = parent
+ });
+ }
+
+ private static WorkGroup FindParentGroup(
+ List groups,
+ string code) {
+ string current = code;
+ int separatorIndex;
+
+ while((separatorIndex = current.LastIndexOf('.')) >= 0) {
+ current = current.Substring(0, separatorIndex);
+ var parent = groups.FirstOrDefault(g => g.Code == current);
+ if(parent != null) {
+ return parent;
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/RevitClassifierParameters/Models/MaterialClassifier/MaterialParamSetter.cs b/src/RevitClassifierParameters/Models/MaterialClassifier/MaterialParamSetter.cs
new file mode 100644
index 000000000..b2579bdc5
--- /dev/null
+++ b/src/RevitClassifierParameters/Models/MaterialClassifier/MaterialParamSetter.cs
@@ -0,0 +1,243 @@
+using System.Collections.Generic;
+using System.Linq;
+
+using Autodesk.Revit.DB;
+
+using dosymep.Bim4Everyone;
+using dosymep.Bim4Everyone.SharedParams;
+using dosymep.Revit;
+using dosymep.SimpleServices;
+
+using RevitClassifierParameters.Models.Work;
+
+namespace RevitClassifierParameters.Models.MaterialClassifier;
+
+internal class MaterialParamSetter {
+ private static readonly RevitParam _chapterParameter = SharedParamsConfig.Instance.MtrChapterTitle;
+ private static readonly RevitParam _workTitleParameter = SharedParamsConfig.Instance.MtrWorkTitle;
+ private static readonly RevitParam _unitParameter = SharedParamsConfig.Instance.MtrMeasurementUnit;
+ private static readonly RevitParam _calculationTypeParameter = SharedParamsConfig.Instance.MtrCountingType;
+
+ private static readonly Dictionary _calculationTypeDict = new() {
+ ["м"] = 1,
+ ["м²"] = 2,
+ ["м³"] = 3,
+ ["шт."] = 4
+ };
+
+ private readonly RevitRepository _revitRepository;
+ private readonly MaterialReportService _materialReportService;
+ private readonly ILocalizationService _localizationService;
+
+ public MaterialParamSetter(
+ RevitRepository revitRepository,
+ MaterialReportService materialReportService,
+ ILocalizationService localizationService) {
+ _revitRepository = revitRepository;
+ _materialReportService = materialReportService;
+ _localizationService = localizationService;
+ }
+
+ ///
+ /// Задает значения параметрам материалов
+ ///
+ /// Выбранные пользователем коды для обработки
+ /// Работы из классификатора
+ /// Материалы в проекте
+ /// Работа с архитектурными группами классификатора
+ public void SetParamValue(
+ HashSet activeCodes,
+ List classifierWorks,
+ List materialInPj,
+ bool forAr) {
+
+ // Сопоставляем материалы с работами Классификатора
+ var revitMaterials = MatchMaterials(activeCodes, materialInPj, classifierWorks);
+
+ SetClassifierParameters(revitMaterials, forAr);
+ }
+
+ ///
+ /// Сопоставляет материалы проекта с работами Классификатора по ключевой заметке.
+ /// Обрабатываются только материалы, чей код начинается с одного из выбранных кодов групп.
+ ///
+ private List MatchMaterials(
+ HashSet activeCodes,
+ List materialInPj,
+ List classifierWorks) {
+
+ var revitMaterials = new List();
+
+ foreach(var material in materialInPj) {
+ string keynote = material.GetParamValueOrDefault(BuiltInParameter.KEYNOTE_PARAM);
+
+ // Отсеиваем ситуации, когда у материала не указана Ключевая заметка (код работы)
+ if(string.IsNullOrEmpty(keynote)) {
+ _materialReportService.Add(MaterialReportStatus.NoWorkCode, string.Empty, material.Name);
+ continue;
+ }
+
+ // Обрабатываем только материалы, чей код принадлежит одному из выбранных кодов групп
+ if(!activeCodes.Any(code => CodeStartsWith(keynote, code))) {
+ continue;
+ }
+
+ // Отсеиваем ситуации, когда Классификатор не содержит указанный в материале код
+ var work = FindWork(classifierWorks, keynote);
+ if(work is null) {
+ _materialReportService.Add(MaterialReportStatus.ClassifierCodeNotFound, keynote, material.Name);
+ continue;
+ }
+
+ revitMaterials.Add(new RevitMaterial(keynote, material, work));
+ }
+
+ return revitMaterials;
+ }
+
+ ///
+ /// Ищет работу по коду, спускаясь по дереву классификатора.
+ /// Использует факт, что код работы включает код своей группы как префикс,
+ /// поэтому заходит только в ту ветку, чей код группы является префиксом искомого кода.
+ ///
+ private WorkItem FindWork(List workGroups, string code) {
+ if(workGroups is null) {
+ return null;
+ }
+
+ foreach(var workGroup in workGroups) {
+ // Код работы всегда включает код группы как префикс,
+ // поэтому неподходящие ветки пропускаем целиком
+ if(!CodeStartsWith(code, workGroup.Code)) {
+ continue;
+ }
+
+ var work = workGroup.ChildWorks.FirstOrDefault(w => w.Code == code);
+ if(work != null) {
+ return work;
+ }
+
+ var found = FindWork(workGroup.ChildWorkGroups, code);
+ if(found != null) {
+ return found;
+ }
+ }
+
+ return null;
+ }
+
+ ///
+ /// Проверяет, что код совпадает с префиксом или является его дочерним кодом
+ /// (совпадение по границе сегмента, разделённого точкой).
+ /// Пример: для префикса "г02.03" -> true для "г02.03" и "г02.03.01", false для "г02.030".
+ ///
+ private bool CodeStartsWith(string code, string prefix) {
+ return code == prefix || code.StartsWith(prefix + ".");
+ }
+
+ ///
+ /// Заполняет параметры Классификатора у материалов в одной транзакции.
+ ///
+ private void SetClassifierParameters(List revitMaterials, bool forAr) {
+ using var transaction = _revitRepository.Document.StartTransaction(
+ _localizationService.GetLocalizedString("Transaction.SetClassifierParameters"));
+ foreach(var revitMaterial in revitMaterials) {
+ var material = revitMaterial.Material;
+ try {
+ bool edited = false;
+ var work = revitMaterial.Work;
+ string chapterName = GetChapterName(work, forAr);
+
+ edited |= SetStringParam(material, _chapterParameter, chapterName);
+ edited |= SetStringParam(material, _workTitleParameter, work.Name);
+ edited |= SetStringParam(material, _unitParameter, work.Unit);
+ edited |= SetCalculationTypeParam(material, work.Unit);
+ edited |= SetStringParam(
+ material,
+ BuiltInParameter.ALL_MODEL_DESCRIPTION,
+ revitMaterial.MaterialDescription);
+
+ _materialReportService.Add(
+ edited ? MaterialReportStatus.Edited : MaterialReportStatus.NotEdited,
+ revitMaterial.Keynote,
+ material.Name);
+ } catch {
+ _materialReportService.Add(MaterialReportStatus.Error, revitMaterial.Keynote, material.Name);
+ }
+ }
+
+ transaction.Commit();
+ }
+
+ ///
+ /// Возвращает наименование главы для материала.
+ /// forAr = true -> наименование ближайшей главы (непосредственный родитель работы).
+ /// forAr = false -> наименование главы самого верхнего уровня
+ /// (поднимаемся по ParentWorkGroup, пока он не станет null).
+ ///
+ private string GetChapterName(WorkItem work, bool forAr) {
+ var chapter = work.ParentWorkGroup;
+ if(chapter is null) {
+ return string.Empty;
+ }
+
+ if(forAr) {
+ return chapter.Name;
+ }
+
+ while(chapter.ParentWorkGroup != null) {
+ chapter = chapter.ParentWorkGroup;
+ }
+ return chapter.Name;
+ }
+
+ ///
+ /// Устанавливает значение параметра "Тип подсчета" по единице измерения.
+ /// При валидной единице пишется число, иначе - локализованная строка ошибки.
+ /// Возвращает true, если параметр был изменён.
+ ///
+ private bool SetCalculationTypeParam(Material material, string unit) {
+ var param = material.GetParam(_calculationTypeParameter);
+
+ if(unit != null && _calculationTypeDict.TryGetValue(unit, out int value)) {
+ if(param.AsValueString() == value.ToString()) {
+ return false;
+ }
+ material.SetParamValue(_calculationTypeParameter, value);
+ return true;
+ }
+
+ string calculationTypeError = _localizationService.GetLocalizedString("CalculationType.Error");
+ if(param.AsValueString() == calculationTypeError) {
+ return false;
+ }
+ material.SetParamValue(_calculationTypeParameter, calculationTypeError);
+ return true;
+ }
+
+ ///
+ /// Устанавливает строковое значение параметра, только если оно отличается от текущего.
+ /// Возвращает true, если параметр был изменён.
+ ///
+ private bool SetStringParam(Material material, RevitParam revitParam, string value) {
+ var param = material.GetParam(revitParam);
+ if(param.AsValueString() == value) {
+ return false;
+ }
+ material.SetParamValue(revitParam, value ?? string.Empty);
+ return true;
+ }
+
+ ///
+ /// Устанавливает строковое значение системного параметра, только если оно отличается от текущего.
+ /// Возвращает true, если параметр был изменён.
+ ///
+ private bool SetStringParam(Material material, BuiltInParameter builtInParameter, string value) {
+ var param = material.GetParam(builtInParameter);
+ if(param.AsValueString() == value) {
+ return false;
+ }
+ material.SetParamValue(builtInParameter, value ?? string.Empty);
+ return true;
+ }
+}
diff --git a/src/RevitClassifierParameters/Models/MaterialClassifier/MaterialReportItem.cs b/src/RevitClassifierParameters/Models/MaterialClassifier/MaterialReportItem.cs
new file mode 100644
index 000000000..136123347
--- /dev/null
+++ b/src/RevitClassifierParameters/Models/MaterialClassifier/MaterialReportItem.cs
@@ -0,0 +1,64 @@
+namespace RevitClassifierParameters.Models.MaterialClassifier;
+
+///
+/// Статус обработки материала.
+///
+internal enum MaterialReportStatus {
+ ///
+ /// Параметры материала были изменены.
+ ///
+ Edited,
+
+ ///
+ /// Параметры материала не изменялись (значения уже актуальны).
+ ///
+ NotEdited,
+
+ ///
+ /// У материала не указан код работы (ключевая заметка).
+ ///
+ NoWorkCode,
+
+ ///
+ /// Код работы материала не найден в Классификаторе.
+ ///
+ ClassifierCodeNotFound,
+
+ ///
+ /// При записи параметров произошла ошибка.
+ ///
+ Error
+}
+
+///
+/// Запись отчёта по обработке одного материала.
+/// Аналог строки отчёта из Python-плагина: [Статус, Код работы, Имя материала].
+///
+internal class MaterialReportItem {
+ ///
+ /// Создает запись отчёта.
+ ///
+ /// Статус обработки материала.
+ /// Код работы (ключевая заметка материала).
+ /// Имя материала.
+ public MaterialReportItem(MaterialReportStatus status, string workCode, string materialName) {
+ Status = status;
+ WorkCode = workCode;
+ MaterialName = materialName;
+ }
+
+ ///
+ /// Статус обработки материала.
+ ///
+ public MaterialReportStatus Status { get; }
+
+ ///
+ /// Код работы (ключевая заметка материала).
+ ///
+ public string WorkCode { get; }
+
+ ///
+ /// Имя материала.
+ ///
+ public string MaterialName { get; }
+}
diff --git a/src/RevitClassifierParameters/Models/MaterialClassifier/MaterialReportService.cs b/src/RevitClassifierParameters/Models/MaterialClassifier/MaterialReportService.cs
new file mode 100644
index 000000000..a23a9d864
--- /dev/null
+++ b/src/RevitClassifierParameters/Models/MaterialClassifier/MaterialReportService.cs
@@ -0,0 +1,32 @@
+using System.Collections.Generic;
+
+namespace RevitClassifierParameters.Models.MaterialClassifier;
+
+///
+/// Сервис сбора отчёта по обработке материалов.
+///
+internal class MaterialReportService {
+ private readonly List _items = [];
+
+ ///
+ /// Собранные записи отчёта.
+ ///
+ public IReadOnlyList Items => _items;
+
+ ///
+ /// Добавляет запись в отчёт.
+ ///
+ /// Статус обработки материала.
+ /// Код работы (ключевая заметка материала).
+ /// Имя материала.
+ public void Add(MaterialReportStatus status, string workCode, string materialName) {
+ _items.Add(new MaterialReportItem(status, workCode, materialName));
+ }
+
+ ///
+ /// Очищает отчёт.
+ ///
+ public void Clear() {
+ _items.Clear();
+ }
+}
diff --git a/src/RevitClassifierParameters/Models/MaterialClassifier/RevitMaterial.cs b/src/RevitClassifierParameters/Models/MaterialClassifier/RevitMaterial.cs
new file mode 100644
index 000000000..d09178a70
--- /dev/null
+++ b/src/RevitClassifierParameters/Models/MaterialClassifier/RevitMaterial.cs
@@ -0,0 +1,66 @@
+using Autodesk.Revit.DB;
+
+using RevitClassifierParameters.Models.Work;
+
+namespace RevitClassifierParameters.Models.MaterialClassifier;
+
+///
+/// Класс-оболочка для хранения информации о материале Revit и связанной с ним работе из Классификатора.
+///
+internal class RevitMaterial {
+ private const char _mark = '_';
+
+ ///
+ /// Создает обёртку материала.
+ ///
+ /// Ключевая заметка материала (код работы).
+ /// Материал Revit.
+ /// Работа из Классификатора, параметры которой нужно назначить.
+ public RevitMaterial(string keynote, Material material, WorkItem work) {
+ Keynote = keynote;
+ Material = material;
+ Work = work;
+ MaterialDescription = GetMaterialDescription();
+ }
+
+ ///
+ /// Ключевая заметка материала (код работы).
+ ///
+ public string Keynote { get; }
+
+ ///
+ /// Материал Revit.
+ ///
+ public Material Material { get; }
+
+ ///
+ /// Работа из Классификатора, параметры которой нужно назначить.
+ ///
+ public WorkItem Work { get; }
+
+ ///
+ /// Описание материала для параметра "Материал: Описание".
+ ///
+ public string MaterialDescription { get; }
+
+ ///
+ /// Возвращает строку для заполнения параметра "Материал: Описание".
+ /// Строка содержится в имени материала между первым и вторым нижним подчеркиванием.
+ /// Пример: "г02.04.01.04_Бетон_Выше 0_Устройство монолитных ж/б пилонов" -> "Бетон".
+ ///
+ private string GetMaterialDescription() {
+ string materialName = Material.Name ?? string.Empty;
+
+ int firstIndex = materialName.IndexOf(_mark);
+ if(firstIndex < 0) {
+ return string.Empty;
+ }
+
+ int secondIndex = materialName.IndexOf(_mark, firstIndex + 1);
+ if(secondIndex < 0) {
+ return string.Empty;
+ }
+
+ return materialName.Substring(firstIndex + 1, secondIndex - firstIndex - 1).Trim();
+ }
+}
diff --git a/src/RevitClassifierParameters/Models/PluginConfig.cs b/src/RevitClassifierParameters/Models/PluginConfig.cs
new file mode 100644
index 000000000..88ff2a644
--- /dev/null
+++ b/src/RevitClassifierParameters/Models/PluginConfig.cs
@@ -0,0 +1,102 @@
+using System.IO;
+
+using dosymep.Bim4Everyone;
+using dosymep.Bim4Everyone.ProjectConfigs;
+
+using pyRevitLabs.Json;
+
+namespace RevitClassifierParameters.Models;
+
+///
+/// Класс конфигурации плагина.
+/// (Если не используется удалить)
+///
+internal class PluginConfig : ProjectConfig {
+ ///
+ /// Системное свойство конфигурации. (Не трогать)
+ ///
+ [JsonIgnore]
+ public override string ProjectConfigPath { get; set; }
+
+ ///
+ /// Системное свойство конфигурации. (Не трогать)
+ ///
+ [JsonIgnore]
+ public override IConfigSerializer Serializer { get; set; }
+
+ ///
+ /// Метод создания конфигурации плагина.
+ ///
+ ///
+ /// Сериализатор конфигурации.
+ /// Возвращает прочитанную конфигурацию плагина, либо созданный конфиг по умолчанию.
+ ///
+ public static PluginConfig GetPluginConfig(IConfigSerializer configSerializer) {
+ return new ProjectConfigBuilder()
+ .SetSerializer(configSerializer)
+ .SetPluginName(nameof(RevitClassifierParameters))
+ .SetRevitVersion(ModuleEnvironment.RevitVersion)
+ .SetProjectConfigName(nameof(PluginConfig) + ".json")
+ .Build();
+ }
+}
+
+///
+/// Настройки проекта.
+/// В настройках проекта обычно хранится выбор пользователя в основном окне плагина.
+///
+///
+/// Проектом по умолчанию является текст до первого нижнего подчеркивания.
+///
+/// https://github.com/dosymep/dosymep.Revit/blob/master/src/dosymep.Bim4Everyone/ProjectConfigs/ProjectConfig.cs#L102
+/// Если плагин работает без открытых проектов,
+/// то требуется данный класс удалять из проекта,
+/// как сделано в плагине RevitServerFolders
+/// https://github.com/Bim4Everyone/RevitPlugins/blob/master/src/RevitServerFolders/Models/PluginConfig.cs#L8
+///
+internal class RevitSettings : ProjectSettings {
+ ///
+ /// Наименование проекта. Системное свойство. (Не трогать)
+ ///
+ public override string ProjectName { get; set; }
+
+ public string ExcelClassifierPath { get; set; }
+ public string ExcelFacadeTypePath { get; set; }
+ public bool WorkWithFoundationCode { get; set; }
+ public bool WorkWithConstrBelowZeroCode { get; set; }
+ public bool WorkWithConstrAboveZeroCode { get; set; }
+ public bool WorkWithMasonryCode { get; set; }
+ public bool WorkWithRoofCode { get; set; }
+ public bool WorkWithFacadeCode { get; set; }
+ public bool WorkWithFacadeType { get; set; }
+ public string ParamNameForFacadeType { get; set; }
+}
+
+///
+/// Системная конфигурация плагина.
+/// Содержит стандартные данные, которые некорректно хранить в конфигурации проекта.
+///
+internal class SystemPluginConfig {
+ // Основная папка с ресурсами надстройки
+ private readonly string _mainFolder =
+ @"W:\Проектный институт\Отд.стандарт.BIM и RD\BIM-Ресурсы\5-Надстройки\Bim4Everyone\A101";
+
+ // Папка с файлом Классификатора
+ private readonly string _classifierDirectory =
+ @"W:\Проектный институт\Проектные Группы\Типовые ТЗ\BIM-стандарт A101";
+
+ ///
+ /// Стандартный путь к файлу правил заполнения типа фасада.
+ ///
+ public string FacadeTypeFilePath => Path.Combine(
+ _mainFolder,
+ ModuleEnvironment.RevitVersion,
+ "RevitClassifierParameters",
+ "Правила заполнения типа фасада.xlsx");
+
+ ///
+ /// Стандартная папка с файлом Классификатора,
+ /// открываемая по умолчанию в диалоге выбора файла.
+ ///
+ public string ClassifierDirectoryPath => _classifierDirectory;
+}
diff --git a/src/RevitClassifierParameters/Models/RevitRepository.cs b/src/RevitClassifierParameters/Models/RevitRepository.cs
new file mode 100644
index 000000000..8302806a5
--- /dev/null
+++ b/src/RevitClassifierParameters/Models/RevitRepository.cs
@@ -0,0 +1,110 @@
+using System.Collections.Generic;
+using System.Linq;
+
+using Autodesk.Revit.ApplicationServices;
+using Autodesk.Revit.DB;
+using Autodesk.Revit.UI;
+
+using dosymep.SimpleServices;
+
+namespace RevitClassifierParameters.Models;
+
+///
+/// Класс доступа к документу и приложению Revit.
+///
+///
+/// В случае если данный класс разрастается, рекомендуется его разделить на несколько.
+///
+internal class RevitRepository {
+ private readonly ILocalizationService _localizationService;
+ private readonly IMessageBoxService _messageBoxService;
+
+ ///
+ /// Создает экземпляр репозитория.
+ ///
+ /// Класс доступа к интерфейсу Revit.
+ public RevitRepository(
+ UIApplication uiApplication,
+ ILocalizationService localizationService,
+ IMessageBoxService messageBoxService) {
+ UIApplication = uiApplication;
+ _localizationService = localizationService;
+ _messageBoxService = messageBoxService;
+ }
+
+ ///
+ /// Класс доступа к интерфейсу Revit.
+ ///
+ public UIApplication UIApplication { get; }
+
+ ///
+ /// Класс доступа к интерфейсу документа Revit.
+ ///
+ public UIDocument ActiveUIDocument => UIApplication.ActiveUIDocument;
+
+ ///
+ /// Класс доступа к приложению Revit.
+ ///
+ public Application Application => UIApplication.Application;
+
+ ///
+ /// Класс доступа к документу Revit.
+ ///
+ public Document Document => ActiveUIDocument.Document;
+
+
+ public List GetElementMaterials() {
+ var materials = GetElementInPj()
+ .SelectMany(elem => elem.GetMaterialIds(false))
+ .Distinct()
+ .Select(id => Document.GetElement(id) as Material)
+ .Where(material => material != null)
+ .ToList();
+
+ if(materials.Count == 0) {
+ _messageBoxService.Show(_localizationService.GetLocalizedString("Repository.NoMaterialsForElements"));
+ }
+ return materials;
+ }
+
+ private List GetElementInPj() {
+ var elements = new FilteredElementCollector(Document)
+ .WhereElementIsNotElementType()
+ .ToElements()
+ .ToList();
+ if(elements.Count != 0) {
+ return elements;
+ }
+ _messageBoxService.Show(_localizationService.GetLocalizedString("Repository.NoElementsInProject"));
+ return [];
+ }
+
+ public List GetParametersForFacadeType() {
+ var wall = new FilteredElementCollector(Document)
+ .OfClass(typeof(Wall))
+ .WhereElementIsNotElementType()
+ .FirstOrDefault();
+
+ if(wall == null) {
+ return [];
+ }
+
+ return wall.Parameters
+ .Cast()
+ .Where(p => p.StorageType == StorageType.String)
+ .Where(p => !p.IsReadOnly)
+ .OrderBy(p => p.Definition.Name)
+ .ToList();
+ }
+
+ ///
+ /// Возвращает все экземпляры стен в проекте.
+ ///
+ public List GetWalls() {
+ return new FilteredElementCollector(Document)
+ .OfClass(typeof(Wall))
+ .WhereElementIsNotElementType()
+ .Cast()
+ .ToList();
+ }
+}
diff --git a/src/RevitClassifierParameters/Models/Work/WorkGroup.cs b/src/RevitClassifierParameters/Models/Work/WorkGroup.cs
new file mode 100644
index 000000000..eb012a391
--- /dev/null
+++ b/src/RevitClassifierParameters/Models/Work/WorkGroup.cs
@@ -0,0 +1,8 @@
+using System.Collections.Generic;
+
+namespace RevitClassifierParameters.Models.Work;
+
+internal class WorkGroup : WorkItem {
+ public List ChildWorkGroups { get; set; } = [];
+ public List ChildWorks { get; set; } = [];
+}
diff --git a/src/RevitClassifierParameters/Models/Work/WorkGroupCode.cs b/src/RevitClassifierParameters/Models/Work/WorkGroupCode.cs
new file mode 100644
index 000000000..9b95113bc
--- /dev/null
+++ b/src/RevitClassifierParameters/Models/Work/WorkGroupCode.cs
@@ -0,0 +1,10 @@
+namespace RevitClassifierParameters.Models.Work;
+
+internal class WorkGroupCode {
+ public string FoundationCode { get; set; } = "г02.02";
+ public string ConstrBelowZeroCode { get; set; } = "г02.03";
+ public string ConstrAboveZeroCode { get; set; } = "г02.04";
+ public string MasonryCode { get; set; } = "г02.05";
+ public string RoofCode { get; set; } = "г02.06";
+ public string FacadeCode { get; set; } = "г02.07";
+}
diff --git a/src/RevitClassifierParameters/Models/Work/WorkItem.cs b/src/RevitClassifierParameters/Models/Work/WorkItem.cs
new file mode 100644
index 000000000..af9661dff
--- /dev/null
+++ b/src/RevitClassifierParameters/Models/Work/WorkItem.cs
@@ -0,0 +1,8 @@
+namespace RevitClassifierParameters.Models.Work;
+
+internal class WorkItem {
+ public string Code { get; set; }
+ public string Name { get; set; }
+ public string Unit { get; set; }
+ public WorkGroup ParentWorkGroup { get; set; }
+}
diff --git a/src/RevitClassifierParameters/README.md b/src/RevitClassifierParameters/README.md
new file mode 100644
index 000000000..38f17c09e
--- /dev/null
+++ b/src/RevitClassifierParameters/README.md
@@ -0,0 +1,12 @@
+# RevitClassifierParameters (Параметры ВОР)
+Плагин предназначен для автоматизации процесса заполнения параметров ВОР в Revit.
+
+# Сборка проекта
+```
+nuke compile --profile RevitClassifierParameters
+```
+
+# Публикация проекта
+```
+nuke publish --profile RevitClassifierParameters
+```
diff --git a/src/RevitClassifierParameters/RevitArParameterClassifierCommand.cs b/src/RevitClassifierParameters/RevitArParameterClassifierCommand.cs
new file mode 100644
index 000000000..88c5ae330
--- /dev/null
+++ b/src/RevitClassifierParameters/RevitArParameterClassifierCommand.cs
@@ -0,0 +1,127 @@
+using System.Globalization;
+using System.Reflection;
+using System.Windows;
+
+using Autodesk.Revit.Attributes;
+using Autodesk.Revit.UI;
+
+using dosymep.Bim4Everyone;
+using dosymep.Bim4Everyone.ProjectConfigs;
+using dosymep.Bim4Everyone.SimpleServices;
+using dosymep.WpfCore.Ninject;
+using dosymep.WpfUI.Core.Ninject;
+
+using Ninject;
+
+using RevitClassifierParameters.Models;
+using RevitClassifierParameters.Models.FacadeType;
+using RevitClassifierParameters.Models.MaterialClassifier;
+using RevitClassifierParameters.Models.Work;
+using RevitClassifierParameters.ViewModels;
+using RevitClassifierParameters.Views;
+
+namespace RevitClassifierParameters;
+
+///
+/// Класс команды Revit плагина.
+///
+///
+/// В данном классе должна быть инициализация контейнера плагина и указание названия команды.
+///
+[Transaction(TransactionMode.Manual)]
+public class RevitArParameterClassifierCommand : BasePluginCommand {
+ ///
+ /// Инициализирует команду плагина.
+ ///
+ public RevitArParameterClassifierCommand() {
+ PluginName = "Параметры ВОР АР";
+ }
+
+ ///
+ /// Метод выполнения основного кода плагина.
+ ///
+ /// Интерфейс взаимодействия с Revit.
+ ///
+ /// В случаях, когда не используется конфигурация
+ /// или локализация требуется удалять их использование полностью во всем проекте.
+ ///
+ protected override void Execute(UIApplication uiApplication) {
+ // Создание контейнера зависимостей плагина с сервисами из платформы
+ using IKernel kernel = uiApplication.CreatePlatformServices();
+
+ // Настройка доступа к Revit
+ kernel.Bind()
+ .ToSelf()
+ .InSingletonScope();
+
+ // Настройка конфигурации плагина
+ kernel.Bind()
+ .ToMethod(c => PluginConfig.GetPluginConfig(c.Kernel.Get()));
+
+ kernel.Bind()
+ .ToSelf()
+ .InSingletonScope();
+
+ kernel.Bind()
+ .ToSelf()
+ .InSingletonScope();
+
+ kernel.Bind()
+ .ToSelf()
+ .InSingletonScope();
+
+ kernel.Bind()
+ .ToSelf()
+ .InSingletonScope();
+
+ kernel.Bind()
+ .ToSelf()
+ .InSingletonScope();
+
+ kernel.Bind()
+ .ToSelf()
+ .InSingletonScope();
+
+ // Системная конфигурация плагина
+ kernel.Bind()
+ .ToSelf()
+ .InSingletonScope();
+
+ // Настройка окна отчёта
+ kernel.Bind()
+ .ToSelf()
+ .InSingletonScope();
+
+ kernel.Bind()
+ .ToSelf()
+ .InSingletonScope()
+ .WithPropertyValue(nameof(Window.DataContext),
+ c => c.Kernel.Get());
+
+ // Используем сервис обновления тем для WPF UI
+ kernel.UseWpfUIThemeUpdater();
+
+ // Настройка запуска окна
+ kernel.BindMainWindow();
+
+ // Настройка локализации,
+ // получение имени сборки откуда брать текст
+ string assemblyName = Assembly.GetExecutingAssembly().GetName().Name;
+
+ // Настройка локализации,
+ // установка дефолтной локализации "ru-RU"
+ kernel.UseWpfLocalization(
+ $"/{assemblyName};component/assets/localization/language.xaml",
+ CultureInfo.GetCultureInfo("ru-RU"));
+
+ // Настройка сервиса окошек сообщений
+ kernel.UseWpfUIMessageBox();
+
+ // Сервис открытия диалогового окна для чтения файла Классификатора
+ kernel.UseWpfOpenFileDialog(
+ filter: "Excel files (*.xlsx)|*.xlsx|All files (*.*)|*.*");
+
+ // Вызывает стандартное уведомление
+ Notification(kernel.Get());
+ }
+}
diff --git a/src/RevitClassifierParameters/RevitClassifierParameters.csproj b/src/RevitClassifierParameters/RevitClassifierParameters.csproj
new file mode 100644
index 000000000..d48c6083e
--- /dev/null
+++ b/src/RevitClassifierParameters/RevitClassifierParameters.csproj
@@ -0,0 +1,7 @@
+
+
+ true
+ true
+ 12
+
+
diff --git a/src/RevitClassifierParameters/ViewModels/ArParameterClassifierVM.cs b/src/RevitClassifierParameters/ViewModels/ArParameterClassifierVM.cs
new file mode 100644
index 000000000..cfd6d9c9e
--- /dev/null
+++ b/src/RevitClassifierParameters/ViewModels/ArParameterClassifierVM.cs
@@ -0,0 +1,240 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.IO;
+using System.Linq;
+using System.Windows.Input;
+
+using Autodesk.Revit.DB;
+
+using dosymep.Bim4Everyone.SharedParams;
+using dosymep.SimpleServices;
+using dosymep.WPF.Commands;
+
+using RevitClassifierParameters.Models;
+using RevitClassifierParameters.Models.FacadeType;
+using RevitClassifierParameters.Models.MaterialClassifier;
+using RevitClassifierParameters.Models.Work;
+using RevitClassifierParameters.Views;
+
+namespace RevitClassifierParameters.ViewModels;
+
+internal class ArParameterClassifierVM : ParameterClassifierVM {
+ private readonly FacadeTypeSetter _facadeTypeSetter;
+
+ ///
+ /// Стандартный путь к файлу правил заполнения типа фасада.
+ ///
+ private string _excelFacadeTypePath = string.Empty;
+ ///
+ /// Стандартное наименование параметра стен на экземпляре, куда нужно заполнить тип фасада.
+ ///
+ private string _facadeTypeParamName = SharedParamsConfig.Instance.Grouping.Name;
+ private bool _workWithMasonryCode;
+ private bool _workWithRoofCode;
+ private bool _workWithFacadeCode;
+ private bool _workWithFacadeType;
+ private List _facadeTypes;
+ private Parameter _paramForFacadeType;
+ private ObservableCollection _paramsForFacadeType;
+
+ public ArParameterClassifierVM(
+ PluginConfig pluginConfig,
+ RevitRepository revitRepository,
+ ILocalizationService localizationService,
+ WorkGroupCode workGroupCode,
+ MaterialParamSetter materialParamSetter,
+ MaterialReportService materialReportService,
+ ClassifierExcelReader classifierExcelReader,
+ FacadeTypeExcelReader facadeTypeExcelReader,
+ FacadeTypeSetter facadeTypeSetter,
+ SystemPluginConfig systemPluginConfig,
+ ReportV reportV,
+ IOpenFileDialogService openFileDialogService,
+ IMessageBoxService messageBoxService) :
+ base(pluginConfig, revitRepository, localizationService, workGroupCode, materialParamSetter, materialReportService,
+ classifierExcelReader, facadeTypeExcelReader, systemPluginConfig, reportV, openFileDialogService,
+ messageBoxService) {
+
+ _facadeTypeSetter = facadeTypeSetter;
+
+ _excelFacadeTypePath = _systemPluginConfig.FacadeTypeFilePath;
+
+ ReadFacadeTypeExcelCommand = RelayCommand.Create(RereadFacadeTypeExcel);
+ }
+
+ public ICommand ReadFacadeTypeExcelCommand { get; }
+
+ public bool WorkWithMasonryCode {
+ get => _workWithMasonryCode;
+ set => RaiseAndSetIfChanged(ref _workWithMasonryCode, value);
+ }
+
+ public bool WorkWithRoofCode {
+ get => _workWithRoofCode;
+ set => RaiseAndSetIfChanged(ref _workWithRoofCode, value);
+ }
+
+ public bool WorkWithFacadeCode {
+ get => _workWithFacadeCode;
+ set => RaiseAndSetIfChanged(ref _workWithFacadeCode, value);
+ }
+
+ public bool WorkWithFacadeType {
+ get => _workWithFacadeType;
+ set => RaiseAndSetIfChanged(ref _workWithFacadeType, value);
+ }
+
+ public List FacadeTypes {
+ get => _facadeTypes;
+ private set => RaiseAndSetIfChanged(ref _facadeTypes, value);
+ }
+
+ public string ExcelFacadeTypePath {
+ get => _excelFacadeTypePath;
+ set => RaiseAndSetIfChanged(ref _excelFacadeTypePath, value);
+ }
+
+ public Parameter ParamForFacadeType {
+ get => _paramForFacadeType;
+ set => RaiseAndSetIfChanged(ref _paramForFacadeType, value);
+ }
+
+ public ObservableCollection ParamsForFacadeType {
+ get => _paramsForFacadeType;
+ private set => RaiseAndSetIfChanged(ref _paramsForFacadeType, value);
+ }
+
+ protected override void LoadView() {
+ LoadConfig();
+ ReadClassifierExcel();
+ GetMaterials();
+ GetParamsForFacadeType();
+ ReadFacadeTypeExcel();
+ }
+
+ ///
+ /// Получение параметров для заполнения типа фасада
+ ///
+ private void GetParamsForFacadeType() {
+ ParamsForFacadeType = new ObservableCollection(_revitRepository.GetParametersForFacadeType());
+ ParamForFacadeType = ParamsForFacadeType.FirstOrDefault(p => p.Definition.Name == _facadeTypeParamName);
+ }
+
+ ///
+ /// Считывает файл правил заполнения типа фасада.
+ /// Если путь по умолчанию/из конфига существует — читает без диалога, иначе открывает диалог выбора файла.
+ ///
+ private void RereadFacadeTypeExcel() {
+ OpenFileDialogService.Title = _localizationService.GetLocalizedString("MainWindow.SelectFacadeTypeFile");
+
+ if(OpenFileDialogService.ShowDialog()) {
+ // Если пользователь выбрал файл при выборе файла
+ ExcelFacadeTypePath = OpenFileDialogService.File.FullName;
+ } else {
+ // Если пользователь не выбрал файл
+ MessageBoxService.Show(_localizationService.GetLocalizedString("MainWindow.NoFacadeTypeFileSelected"));
+ return;
+ }
+
+ FacadeTypes = _facadeTypeExcelReader.Read(ExcelFacadeTypePath);
+ if(FacadeTypes is null || FacadeTypes.Count == 0) {
+ MessageBoxService.Show(_localizationService.GetLocalizedString("MainWindow.NoFacadeTypesFound"));
+ }
+ }
+
+ private void ReadFacadeTypeExcel() {
+ if(string.IsNullOrEmpty(ExcelFacadeTypePath) || !File.Exists(ExcelFacadeTypePath)) {
+ OpenFileDialogService.Title = _localizationService.GetLocalizedString("MainWindow.SelectFacadeTypeFile");
+
+ if(OpenFileDialogService.ShowDialog()) {
+ // Если пользователь выбрал файл при выборе файла
+ ExcelFacadeTypePath = OpenFileDialogService.File.FullName;
+ } else {
+ // Если пользователь не выбрал файл
+ MessageBoxService.Show(_localizationService.GetLocalizedString("MainWindow.NoFacadeTypeFileSelected"));
+ return;
+ }
+ }
+
+ FacadeTypes = _facadeTypeExcelReader.Read(ExcelFacadeTypePath);
+ if(FacadeTypes is null || FacadeTypes.Count == 0) {
+ MessageBoxService.Show(_localizationService.GetLocalizedString("MainWindow.NoFacadeTypesFound"));
+ }
+ }
+
+ protected override void AcceptView() {
+ SaveConfig();
+ var activeCodes = GetActiveCodes();
+ _materialParamSetter.SetParamValue(activeCodes, CurrentClassifierWorks, MaterialInPj, true);
+ if(WorkWithFacadeCode
+ && WorkWithFacadeType
+ && ParamForFacadeType != null
+ && FacadeTypes != null
+ && FacadeTypes.Count != 0) {
+ _facadeTypeSetter.SetFacadeType(ParamForFacadeType.Definition.Name, FacadeTypes);
+ }
+ ShowReport();
+ }
+
+ protected override bool CanAcceptView() {
+ // Если планируем работать с классификатором
+ if(WorkWithMasonryCode || WorkWithRoofCode || WorkWithFacadeType) {
+ if(string.IsNullOrEmpty(ExcelClassifierPath)) {
+ ErrorText = _localizationService.GetLocalizedString("MainWindow.ErrorNoClassifierFile");
+ return false;
+ }
+ if(CurrentClassifierWorks is null || CurrentClassifierWorks.Count == 0) {
+ ErrorText = _localizationService.GetLocalizedString("MainWindow.ErrorNoClassifierWorks");
+ return false;
+ }
+ }
+
+ // Если планируем работать с типами фасадов
+ if(WorkWithFacadeType) {
+ if(string.IsNullOrEmpty(ExcelFacadeTypePath)) {
+ ErrorText = _localizationService.GetLocalizedString("MainWindow.ErrorNoFacadeTypeFile");
+ return false;
+ }
+ if(FacadeTypes is null || FacadeTypes.Count == 0) {
+ ErrorText = _localizationService.GetLocalizedString("MainWindow.ErrorNoFacadeTypes");
+ return false;
+ }
+ if(ParamForFacadeType is null) {
+ ErrorText = _localizationService.GetLocalizedString("MainWindow.ErrorNoFacadeTypeParam");
+ return false;
+ }
+ }
+
+ ErrorText = string.Empty;
+ return true;
+ }
+
+ private void LoadConfig() {
+ var setting = _pluginConfig.GetSettings(_revitRepository.Document);
+
+ ExcelClassifierPath = setting?.ExcelClassifierPath ?? string.Empty;
+ WorkWithMasonryCode = setting?.WorkWithMasonryCode ?? true;
+ WorkWithRoofCode = setting?.WorkWithRoofCode ?? true;
+ WorkWithFacadeCode = setting?.WorkWithFacadeCode ?? true;
+ WorkWithFacadeType = setting?.WorkWithFacadeType ?? true;
+
+ // Для следующих есть стандартное значение, но пользователь может задать иное
+ _facadeTypeParamName = setting?.ParamNameForFacadeType ?? _facadeTypeParamName;
+ ExcelFacadeTypePath = setting?.ExcelFacadeTypePath ?? _excelFacadeTypePath;
+ }
+
+ private void SaveConfig() {
+ var setting = _pluginConfig.GetSettings(_revitRepository.Document)
+ ?? _pluginConfig.AddSettings(_revitRepository.Document);
+
+ setting.ExcelClassifierPath = ExcelClassifierPath;
+ setting.ExcelFacadeTypePath = ExcelFacadeTypePath;
+ setting.WorkWithMasonryCode = WorkWithMasonryCode;
+ setting.WorkWithRoofCode = WorkWithRoofCode;
+ setting.WorkWithFacadeCode = WorkWithFacadeCode;
+ setting.WorkWithFacadeType = WorkWithFacadeType;
+ setting.ParamNameForFacadeType = ParamForFacadeType?.Definition.Name ?? _facadeTypeParamName;
+
+ _pluginConfig.SaveProjectConfig();
+ }
+}
diff --git a/src/RevitClassifierParameters/ViewModels/ParameterClassifierVM.cs b/src/RevitClassifierParameters/ViewModels/ParameterClassifierVM.cs
new file mode 100644
index 000000000..1d4354908
--- /dev/null
+++ b/src/RevitClassifierParameters/ViewModels/ParameterClassifierVM.cs
@@ -0,0 +1,167 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Windows.Input;
+
+using Autodesk.Revit.DB;
+
+using dosymep.SimpleServices;
+using dosymep.WPF.Commands;
+using dosymep.WPF.ViewModels;
+
+using RevitClassifierParameters.Models;
+using RevitClassifierParameters.Models.FacadeType;
+using RevitClassifierParameters.Models.MaterialClassifier;
+using RevitClassifierParameters.Models.Work;
+using RevitClassifierParameters.Views;
+
+namespace RevitClassifierParameters.ViewModels;
+
+internal abstract class ParameterClassifierVM : BaseViewModel {
+ protected readonly PluginConfig _pluginConfig;
+ protected readonly RevitRepository _revitRepository;
+ protected readonly ILocalizationService _localizationService;
+ protected readonly WorkGroupCode _workGroupCode;
+ protected readonly MaterialParamSetter _materialParamSetter;
+ protected readonly MaterialReportService _materialReportService;
+ protected readonly ClassifierExcelReader _classifierExcelReader;
+ protected readonly FacadeTypeExcelReader _facadeTypeExcelReader;
+ protected readonly SystemPluginConfig _systemPluginConfig;
+ protected readonly ReportV _reportV;
+
+ private string _errorText;
+ private List _currentClassifierWorks;
+ private List _materialInPj;
+ protected string _excelClassifierPath;
+
+ protected ParameterClassifierVM(
+ PluginConfig pluginConfig,
+ RevitRepository revitRepository,
+ ILocalizationService localizationService,
+ WorkGroupCode workGroupCode,
+ MaterialParamSetter materialParamSetter,
+ MaterialReportService materialReportService,
+ ClassifierExcelReader classifierExcelReader,
+ FacadeTypeExcelReader facadeTypeExcelReader,
+ SystemPluginConfig systemPluginConfig,
+ ReportV reportV,
+ IOpenFileDialogService openFileDialogService,
+ IMessageBoxService messageBoxService) {
+
+ _pluginConfig = pluginConfig;
+ _revitRepository = revitRepository;
+ _localizationService = localizationService;
+
+ _workGroupCode = workGroupCode;
+ _materialParamSetter = materialParamSetter;
+ _materialReportService = materialReportService;
+ _classifierExcelReader = classifierExcelReader;
+ _facadeTypeExcelReader = facadeTypeExcelReader;
+ _systemPluginConfig = systemPluginConfig;
+ _reportV = reportV;
+
+ MessageBoxService = messageBoxService ?? throw new ArgumentNullException(nameof(messageBoxService));
+ OpenFileDialogService = openFileDialogService ?? throw new ArgumentNullException(nameof(openFileDialogService));
+
+ RereadClassifierExcelCommand = RelayCommand.Create(RereadClassifierExcel);
+ LoadViewCommand = RelayCommand.Create(LoadView);
+ AcceptViewCommand = RelayCommand.Create(AcceptView, CanAcceptView);
+ }
+
+ public ICommand RereadClassifierExcelCommand { get; }
+ public ICommand LoadViewCommand { get; }
+ public ICommand AcceptViewCommand { get; }
+
+ public IOpenFileDialogService OpenFileDialogService { get; }
+ public IMessageBoxService MessageBoxService { get; }
+
+ protected List CurrentClassifierWorks {
+ get => _currentClassifierWorks;
+ private set => RaiseAndSetIfChanged(ref _currentClassifierWorks, value);
+ }
+
+ protected List MaterialInPj {
+ get => _materialInPj;
+ set => RaiseAndSetIfChanged(ref _materialInPj, value);
+ }
+
+ public string ExcelClassifierPath {
+ get => _excelClassifierPath;
+ set => RaiseAndSetIfChanged(ref _excelClassifierPath, value);
+ }
+
+ public string ErrorText {
+ get => _errorText;
+ set => RaiseAndSetIfChanged(ref _errorText, value);
+ }
+
+ private void RereadClassifierExcel() {
+ string classifierDirectory = _systemPluginConfig.ClassifierDirectoryPath;
+ if(Directory.Exists(classifierDirectory)) {
+ OpenFileDialogService.InitialDirectory = classifierDirectory;
+ }
+
+ OpenFileDialogService.Title = _localizationService.GetLocalizedString("MainWindow.SelectClassifierFile");
+ if(OpenFileDialogService.ShowDialog()) {
+ // Если пользователь выбрал файл при выборе файла
+ ExcelClassifierPath = OpenFileDialogService.File.FullName;
+ } else {
+ // Если пользователь не выбрал файл
+ MessageBoxService.Show(_localizationService.GetLocalizedString("MainWindow.NoClassifierFileSelected"));
+ return;
+ }
+ CurrentClassifierWorks = _classifierExcelReader.Read(ExcelClassifierPath);
+ if(CurrentClassifierWorks is null || CurrentClassifierWorks.Count == 0) {
+ MessageBoxService.Show(_localizationService.GetLocalizedString("MainWindow.NoClassifierWorksFound"));
+ }
+ }
+
+ protected void ReadClassifierExcel() {
+ if(string.IsNullOrEmpty(ExcelClassifierPath) || !File.Exists(ExcelClassifierPath)) {
+ return;
+ }
+
+ CurrentClassifierWorks = _classifierExcelReader.Read(ExcelClassifierPath);
+ if(CurrentClassifierWorks is null || CurrentClassifierWorks.Count == 0) {
+ MessageBoxService.Show(_localizationService.GetLocalizedString("MainWindow.NoClassifierWorksFound"));
+ }
+ }
+
+ protected void GetMaterials() {
+ MaterialInPj = _revitRepository.GetElementMaterials();
+ if(MaterialInPj is null || MaterialInPj.Count == 0) {
+ MessageBoxService.Show(_localizationService.GetLocalizedString("MainWindow.NoMaterialsFound"));
+ }
+ }
+
+ protected abstract void LoadView();
+ protected abstract void AcceptView();
+ protected abstract bool CanAcceptView();
+
+ ///
+ /// Показывает окно отчёта, если по итогам обработки есть собранные записи.
+ ///
+ protected void ShowReport() {
+ if(_materialReportService.Items.Count == 0) {
+ return;
+ }
+
+ if(_reportV.DataContext is ReportVM reportVM) {
+ reportVM.UpdateReportData();
+ }
+ _reportV.ShowDialog();
+ }
+
+ protected HashSet GetActiveCodes() {
+ return GetType()
+ .GetProperties()
+ .Where(p => p.Name.StartsWith("WorkWith")
+ && p.PropertyType == typeof(bool)
+ && (bool) p.GetValue(this)!)
+ .Select(p => typeof(WorkGroupCode).GetProperty(p.Name.Replace("WorkWith", "")))
+ .Where(p => p != null)
+ .Select(p => p!.GetValue(_workGroupCode)!.ToString()!)
+ .ToHashSet();
+ }
+}
diff --git a/src/RevitClassifierParameters/ViewModels/ReportVM.cs b/src/RevitClassifierParameters/ViewModels/ReportVM.cs
new file mode 100644
index 000000000..16ee06a01
--- /dev/null
+++ b/src/RevitClassifierParameters/ViewModels/ReportVM.cs
@@ -0,0 +1,47 @@
+using System.Collections.ObjectModel;
+using System.Linq;
+
+using dosymep.WPF.ViewModels;
+
+using RevitClassifierParameters.Models.MaterialClassifier;
+
+namespace RevitClassifierParameters.ViewModels;
+
+internal class ReportVM : BaseViewModel {
+ private readonly MaterialReportService _materialReportService;
+
+ private ObservableCollection _reportItems = [];
+
+ public ReportVM(MaterialReportService materialReportService) {
+ _materialReportService = materialReportService;
+ }
+
+ public ObservableCollection ReportItems {
+ get => _reportItems;
+ set => RaiseAndSetIfChanged(ref _reportItems, value);
+ }
+
+ ///
+ /// Заполняет отчёт из собранных записей.
+ /// Порядок групп повторяет Python-плагин: ошибки, изменённые, без изменений,
+ /// без кода работы, ненайденный код. Внутри группы — сортировка по коду работы.
+ ///
+ public void UpdateReportData() {
+ var ordered = _materialReportService.Items
+ .OrderBy(GetStatusOrder)
+ .ThenBy(item => item.WorkCode);
+
+ ReportItems = new ObservableCollection(ordered);
+ }
+
+ private static int GetStatusOrder(MaterialReportItem item) {
+ return item.Status switch {
+ MaterialReportStatus.Error => 0,
+ MaterialReportStatus.Edited => 1,
+ MaterialReportStatus.NotEdited => 2,
+ MaterialReportStatus.NoWorkCode => 3,
+ MaterialReportStatus.ClassifierCodeNotFound => 4,
+ _ => 5
+ };
+ }
+}
diff --git a/src/RevitClassifierParameters/Views/ArParameterClassifierV.xaml b/src/RevitClassifierParameters/Views/ArParameterClassifierV.xaml
new file mode 100644
index 000000000..9800522ca
--- /dev/null
+++ b/src/RevitClassifierParameters/Views/ArParameterClassifierV.xaml
@@ -0,0 +1,410 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/RevitClassifierParameters/Views/ArParameterClassifierV.xaml.cs b/src/RevitClassifierParameters/Views/ArParameterClassifierV.xaml.cs
new file mode 100644
index 000000000..3941375c4
--- /dev/null
+++ b/src/RevitClassifierParameters/Views/ArParameterClassifierV.xaml.cs
@@ -0,0 +1,49 @@
+using System.Windows;
+
+using dosymep.SimpleServices;
+
+namespace RevitClassifierParameters.Views;
+
+///
+/// Класс главного окна плагина.
+///
+public partial class ArParameterClassifierV {
+ ///
+ /// Иницализирует главное окно плагина.
+ ///
+ public ArParameterClassifierV(
+ ILoggerService loggerService,
+ ISerializationService serializationService,
+ ILanguageService languageService, ILocalizationService localizationService,
+ IUIThemeService uiThemeService, IUIThemeUpdaterService themeUpdaterService)
+ : base(loggerService,
+ serializationService,
+ languageService, localizationService,
+ uiThemeService, themeUpdaterService) {
+ InitializeComponent();
+ }
+
+ ///
+ /// Наименование плагина.
+ ///
+ ///
+ /// Используется для сохранения положения окна.
+ ///
+ public override string PluginName => nameof(RevitClassifierParameters);
+
+ ///
+ /// Наименование файла конфигурации.
+ ///
+ ///
+ /// Используется для сохранения положения окна.
+ ///
+ public override string ProjectConfigName => nameof(ArParameterClassifierV);
+
+ private void ButtonOk_Click(object sender, RoutedEventArgs e) {
+ DialogResult = true;
+ }
+
+ private void ButtonCancel_Click(object sender, RoutedEventArgs e) {
+ DialogResult = false;
+ }
+}
diff --git a/src/RevitClassifierParameters/Views/CustomStyles.xaml b/src/RevitClassifierParameters/Views/CustomStyles.xaml
new file mode 100644
index 000000000..30bde8705
--- /dev/null
+++ b/src/RevitClassifierParameters/Views/CustomStyles.xaml
@@ -0,0 +1,333 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/RevitClassifierParameters/Views/ReportV.xaml b/src/RevitClassifierParameters/Views/ReportV.xaml
new file mode 100644
index 000000000..2b54e2964
--- /dev/null
+++ b/src/RevitClassifierParameters/Views/ReportV.xaml
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/RevitClassifierParameters/Views/ReportV.xaml.cs b/src/RevitClassifierParameters/Views/ReportV.xaml.cs
new file mode 100644
index 000000000..e1531d243
--- /dev/null
+++ b/src/RevitClassifierParameters/Views/ReportV.xaml.cs
@@ -0,0 +1,45 @@
+using System.Windows;
+
+using dosymep.SimpleServices;
+
+namespace RevitClassifierParameters.Views;
+
+///
+/// Класс окна отчёта плагина.
+///
+public partial class ReportV {
+ ///
+ /// Инициализирует окно отчёта плагина.
+ ///
+ public ReportV(
+ ILoggerService loggerService,
+ ISerializationService serializationService,
+ ILanguageService languageService, ILocalizationService localizationService,
+ IUIThemeService uiThemeService, IUIThemeUpdaterService themeUpdaterService)
+ : base(loggerService,
+ serializationService,
+ languageService, localizationService,
+ uiThemeService, themeUpdaterService) {
+ InitializeComponent();
+ }
+
+ ///
+ /// Наименование плагина.
+ ///
+ ///
+ /// Используется для сохранения положения окна.
+ ///
+ public override string PluginName => nameof(RevitClassifierParameters);
+
+ ///
+ /// Наименование файла конфигурации.
+ ///
+ ///
+ /// Используется для сохранения положения окна.
+ ///
+ public override string ProjectConfigName => nameof(ReportV);
+
+ private void ButtonForHide_Click(object sender, RoutedEventArgs e) {
+ Hide();
+ }
+}
diff --git a/src/RevitClassifierParameters/assets/Localization/Language.en-US.xaml b/src/RevitClassifierParameters/assets/Localization/Language.en-US.xaml
new file mode 100644
index 000000000..74295de2a
--- /dev/null
+++ b/src/RevitClassifierParameters/assets/Localization/Language.en-US.xaml
@@ -0,0 +1,66 @@
+
+
+ Parameters BQW AR
+
+ OK
+ Cancel
+ Select
+
+ Select classifier file
+ Select facade type file
+
+ File with classifier codes
+ Select an up-to-date excel file with classifier codes for the project
+ File path not set
+
+ Masonry
+ Processes materials with codes starting with г02.05
+
+ Roof
+ Processes materials with codes starting with г02.06
+
+ Facades
+ Processes materials with codes starting with г02.07
+
+ Fill in facade type
+ Fill in the facade type into the specified parameter
+ File with facade type filling rules
+ Parameter for facade type
+
+ Classifier file is not selected!
+ No works found in the Classifier!
+ No materials found!
+ Facade type rules file is not selected!
+ No facade types found in the file!
+
+ Classifier file is not selected
+ No works found in the Classifier
+ Facade type file is not selected
+ No facade types found in the file
+ Parameter for facade types is not selected
+
+ No materials found for elements in the project!
+ No elements found in the project!
+
+ Classifier sheet is empty or contains no data.
+ Facade type rules file is empty or contains no data.
+
+ Error
+
+ Filling classifier parameters
+ Filling facade type
+
+ EDITED
+ NOT CHANGED
+ NO WORK CODE
+ CODE NOT FOUND
+ WRITE ERROR
+
+ Plugin execution report
+ Status
+ Work code
+ Material name
+ Close
+
diff --git a/src/RevitClassifierParameters/assets/Localization/Language.ru-RU.xaml b/src/RevitClassifierParameters/assets/Localization/Language.ru-RU.xaml
new file mode 100644
index 000000000..48eabef49
--- /dev/null
+++ b/src/RevitClassifierParameters/assets/Localization/Language.ru-RU.xaml
@@ -0,0 +1,66 @@
+
+
+ Параметры ВОР АР
+
+ ОК
+ Отмена
+ Выбрать
+
+ Выберите файл классификатора
+ Выберите файл типов фасадов
+
+ Файл с кодами классификатора
+ Необходимо выбрать актуальный для проекта excel-файл с кодами классификатора
+ Путь к файлу не указан
+
+ Кладка
+ Учитывает материалы с кодами, которые начинаются на г02.05
+
+ Кровля
+ Учитывает материалы с кодами, которые начинаются на г02.06
+
+ Фасады
+ Учитывает материалы с кодами, которые начинаются на г02.07
+
+ Заполнить тип фасада
+ Заполнить тип фасада в заданный параметр
+ Файл с правилами заполнения типа фасада
+ Параметр для типа фасада
+
+ Не выбран файл Классификатора!
+ Не найдены работы в Классификаторе!
+ Не найдено материалов!
+ Не выбран файл правил заполнения типа фасада!
+ Не найдены типы фасадов в файле!
+
+ Не выбран файл классификатора
+ Не найдены работы в Классификаторе
+ Не выбран файл типов фасадов
+ Не найдены типы фасадов в файле
+ Не выбран параметр для типов фасадов
+
+ Не найдено материалов у элементов в проекте!
+ Не найдено элементов в проекте!
+
+ Лист классификатора пуст или не содержит данных.
+ Файл правил заполнения типа фасада пуст или не содержит данных.
+
+ Ошибка
+
+ Заполнение параметров классификатора
+ Заполнение типа фасада
+
+ ИЗМЕНЕН
+ БЕЗ ИЗМЕНЕНИЙ
+ НЕТ КОДА РАБОТЫ
+ НЕ НАЙДЕН КОД
+ ОШИБКА ПРИ ЗАПИСИ
+
+ Отчет работы плагина
+ Статус
+ Код работы
+ Имя материала
+ Закрыть
+