Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
389b028
Добавлен плагин RevitClassifierParameters
Jul 31, 2026
f2aaa61
Добавлены основные классы, выделена команда, VM и вид для заполнения …
Aug 3, 2026
1c0e3ad
Предварительно проработан GUI
Aug 3, 2026
2c44784
Изменена класс команды, добавлены биндинги
Aug 3, 2026
d3986cf
Добавлен функционал по чтению Excel файла классификатора
Aug 4, 2026
04005f3
Добавлено чтение информации про единицы измерения работ
Aug 4, 2026
666fe75
Внесены правки в класс чтения Классификатора для повышения стабильности
Aug 4, 2026
754c7ae
Доблавены всплывающие окна при чтении Классификатора
Aug 4, 2026
a427108
Добавлен функционал по заполнению параметров материалов
Aug 5, 2026
06cec91
Добавлен отчет по измененным материалам
Aug 5, 2026
2cde18d
Добавлен функионал по получению и выбору параметра фасада
Aug 5, 2026
140d532
Добавлен функионал по назначению типа фасада
Aug 7, 2026
c622782
Убрали словарь при назначении типа фасада
Aug 7, 2026
8cd4ed4
Убрали словарь при чтении файла Excel Классификатора
Aug 7, 2026
84e750a
Обновлен GUI, изменена ширина столбцов
Aug 7, 2026
f694953
Изменен GUI, добавлен вывод пути к файлу классификатора
Aug 11, 2026
b0dacf1
Добавлено отображение пути к файлу типов фасадов в GUI
Aug 11, 2026
8602736
Добавлено назначение заголовка диалогового окна при выборе файла типо…
Aug 11, 2026
6c79ed3
Выполнена локализация
Aug 11, 2026
7b046fa
Выделен базовый класс для ридеров Excel, рефакторинг
Aug 11, 2026
8a8cc10
Пути адаптированы под компанию
Aug 11, 2026
f5690c5
Изменен файл конфигурации сборки проекта через nuke
Aug 12, 2026
1ee5a4b
Изменено наименование файла с типами фасадов
Aug 12, 2026
83c3c5d
Переназван класс команды под АР, в дальнейшем появится КР
Aug 12, 2026
e53b869
Заменена библиотека по работе с Excel
Aug 26, 2026
5f27de4
Выполнена локализация строки Ошибка
Aug 26, 2026
3c59466
Добавлено наименование плагина
Aug 26, 2026
b216cad
Merge branch 'master' into DivinN/RevitClassifierParameters
Aug 26, 2026
fc95daa
Параметры материала заменены на SharedParamsConfig
Aug 26, 2026
6447600
Заменен параметр для типа фасада на параметр из SharedParamsConfig
Aug 31, 2026
c473518
Изменен метод хранения системных настроек
Sep 2, 2026
def773c
Изменен способ добавления библиотеки для чтения Excel
Sep 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .github/workflows/publish.RevitClassifierParameters.yml
Original file line number Diff line number Diff line change
@@ -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 }}
15 changes: 15 additions & 0 deletions .nuke/parameters.RevitClassifierParameters.json
Original file line number Diff line number Diff line change
@@ -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"
}
3 changes: 3 additions & 0 deletions RevitPlugins.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@
<Project Path="src/RevitClashDetective/RevitClashDetective.csproj">
<Platform Project="x64" />
</Project>
<Project Path="src/RevitClassifierParameters/RevitClassifierParameters.csproj">
<Platform Project="x64" />
</Project>
<Project Path="src/RevitCopingZones/RevitCopingZones.csproj">
<Platform Project="x64" />
</Project>
Expand Down
Original file line number Diff line number Diff line change
@@ -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<IHasLocalization>();
if(_hasLocalization is not null) {
return this;
}

var frameworkElement = serviceProvider.GetRootObject<FrameworkElement>();
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;
}
}
}
92 changes: 92 additions & 0 deletions src/RevitClassifierParameters/Models/ExcelReaderBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
using System.IO;

using ClosedXML.Excel;

using dosymep.SimpleServices;

namespace RevitClassifierParameters.Models;

/// <summary>
/// Базовый класс для чтения данных из Excel-файла (.xlsx) через ClosedXML.
/// Инкапсулирует открытие книги и чтение диапазона первого листа
/// в двумерный массив значений ячеек.
/// </summary>
/// <typeparam name="TResult">Тип результата разбора данных листа.</typeparam>
internal abstract class ExcelReaderBase<TResult> {
protected readonly ILocalizationService _localizationService;
protected readonly IMessageBoxService _messageBoxService;

protected ExcelReaderBase(
ILocalizationService localizationService,
IMessageBoxService messageBoxService) {
_localizationService = localizationService;
_messageBoxService = messageBoxService;
}

/// <summary>
/// Номер первой строки с данными (строки выше считаются заголовком).
/// </summary>
protected abstract int FirstDataRow { get; }

/// <summary>
/// Количество считываемых столбцов, начиная с первого.
/// </summary>
protected abstract int ColumnCount { get; }

/// <summary>
/// Ключ локализации сообщения о пустом листе/файле.
/// </summary>
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);
}

/// <summary>
/// Строит результат разбора из прочитанного двумерного массива значений ячеек.
/// Индексация массива начинается с 0 по обоим измерениям:
/// первое измерение — строки данных (0..rowCount-1),
/// второе — столбцы (0..ColumnCount-1).
/// </summary>
protected abstract TResult BuildResult(object[,] rows);

protected static string GetString(object value) {
return value?.ToString()?.Trim();
}

/// <summary>
/// Читает диапазон данных листа в двумерный массив с 0-based индексацией.
/// Размерности массива равны фактическому числу строк данных и <see cref="ColumnCount"/>.
/// </summary>
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();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using System.Collections.Generic;

using dosymep.SimpleServices;

namespace RevitClassifierParameters.Models.FacadeType;

/// <summary>
/// Читает Excel-файл с правилами заполнения типа фасада.
/// Ожидается три столбца:
/// 1 - характеристика функции (со скобками, например "(ФМ)");
/// 2 - сокращение основного материала (например "ОК");
/// 3 - значение для записи в параметр.
/// Первая строка — заголовок, данные начинаются со второй строки.
/// </summary>
internal class FacadeTypeExcelReader : ExcelReaderBase<List<FacadeTypeItem>> {
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<FacadeTypeItem> BuildResult(object[,] rows) {
var result = new List<FacadeTypeItem>();

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;
}

/// <summary>
/// Приводит ключевое поле правила (характеристику функции или сокращение материала)
/// к единому виду для последующего сравнения с распарсенным именем типоразмера стены.
/// </summary>
private static string Normalize(string value) {
return (value ?? string.Empty).Trim().ToUpperInvariant();
}
}
23 changes: 23 additions & 0 deletions src/RevitClassifierParameters/Models/FacadeType/FacadeTypeItem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace RevitClassifierParameters.Models.FacadeType;

/// <summary>
/// Строка правил заполнения типа фасада, прочитанная из Excel-файла.
/// </summary>
internal class FacadeTypeItem {
/// <summary>
/// Характеристика функции (столбец 1) хранится со скобками, например "(ФМ)".
/// Приводится к нормализованному виду (Trim + верхний регистр) при чтении из Excel.
/// </summary>
public string FunctionCharacteristic { get; set; }

/// <summary>
/// Сокращение основного материала (столбец 2), например "ОК".
/// Приводится к нормализованному виду (Trim + верхний регистр) при чтении из Excel.
/// </summary>
public string MaterialAbbreviation { get; set; }

/// <summary>
/// Значение (столбец 3), которое записывается в параметр стены.
/// </summary>
public string Value { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using System.Text.RegularExpressions;

namespace RevitClassifierParameters.Models.FacadeType;

/// <summary>
/// Результат разбора имени типоразмера стены.
/// </summary>
internal readonly struct FacadeTypeNameParts {
public FacadeTypeNameParts(string function, string material) {
Function = function;
Material = material;
}

/// <summary>
/// Характеристика функции (со скобками), например "(ФМ)".
/// </summary>
public string Function { get; }

/// <summary>
/// Сокращение основного материала, например "ОК".
/// </summary>
public string Material { get; }

/// <summary>
/// Успешно ли извлечены обе подстроки.
/// </summary>
public bool IsValid =>
!string.IsNullOrWhiteSpace(Function) && !string.IsNullOrWhiteSpace(Material);
}

/// <summary>
/// Разбирает имя типоразмера стены вида
/// "(ФМ) ОК-5 15 (АРМСТК ФДШ-10 ОК-5) RAL #### Цоколь" на две ключевые подстроки:
/// характеристику функции в первых скобках ("(ФМ)")
/// и сокращение основного материала после скобок до первого дефиса ("ОК").
/// </summary>
internal class FacadeTypeNameParser {
// Захватывает первые круглые скобки с содержимым и весь остаток строки после них.
private static readonly Regex _regex =
new(@"^\s*(?<function>\([^)]*\))\s*(?<rest>.*)$", RegexOptions.Compiled);

/// <summary>
/// Разбирает имя типоразмера стены.
/// </summary>
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();
}
}
Loading
Loading