diff --git a/.github/workflows/publish.RevitPackageDocumentation.yml b/.github/workflows/publish.RevitPackageDocumentation.yml
new file mode 100644
index 000000000..829d1bbad
--- /dev/null
+++ b/.github/workflows/publish.RevitPackageDocumentation.yml
@@ -0,0 +1,33 @@
+name: publish RevitPackageDocumentation
+
+on:
+ workflow_dispatch:
+ pull_request:
+ types: [ closed, synchronize, review_requested ]
+ branches: [ main, master ]
+ paths:
+ - '**RevitPackageDocumentation**.cs'
+ - '**RevitPackageDocumentation**.xaml'
+
+env:
+ plugin-name: "RevitPackageDocumentation"
+
+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
+
+ - 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.RevitPackageDocumentation.json b/.nuke/parameters.RevitPackageDocumentation.json
new file mode 100644
index 000000000..19c80edb7
--- /dev/null
+++ b/.nuke/parameters.RevitPackageDocumentation.json
@@ -0,0 +1,15 @@
+{
+ "$schema": "./build.schema.json",
+ "Solution": "RevitPlugins.slnx",
+ "PluginName": "RevitPackageDocumentation",
+ "PublishDirectory": "03.KR.extension\\КР.tab\\bin",
+ "RevitVersions": [
+ "Rv2022",
+ "Rv2023",
+ "Rv2024"
+ ],
+ "IconUrl": "https://icons8.com/icon/RmqYpunlNguf/selective-highlighting",
+ "BundleName": "Документация комплекта",
+ "BundleType": "InvokeButton",
+ "BundleOutput": "03.KR.extension\\КР.tab\\Документация.panel"
+}
diff --git a/RevitPlugins.slnx b/RevitPlugins.slnx
index 6ff59a6f3..3c0d0f321 100644
--- a/RevitPlugins.slnx
+++ b/RevitPlugins.slnx
@@ -152,6 +152,9 @@
+
+
+
diff --git a/src/RevitPackageDocumentation/Models/ComponentTypeItem.cs b/src/RevitPackageDocumentation/Models/ComponentTypeItem.cs
new file mode 100644
index 000000000..8e7e9d2fb
--- /dev/null
+++ b/src/RevitPackageDocumentation/Models/ComponentTypeItem.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace RevitPackageDocumentation.Models;
+public class ComponentTypeItem {
+ public Type ComponentType { get; set; }
+ public string DisplayName { get; set; }
+
+ public ComponentTypeItem(Type type, string displayName) {
+ ComponentType = type;
+ DisplayName = displayName;
+ }
+}
diff --git a/src/RevitPackageDocumentation/Models/ConfigSerializer/PluginParamConverter.cs b/src/RevitPackageDocumentation/Models/ConfigSerializer/PluginParamConverter.cs
new file mode 100644
index 000000000..32fcd6983
--- /dev/null
+++ b/src/RevitPackageDocumentation/Models/ConfigSerializer/PluginParamConverter.cs
@@ -0,0 +1,55 @@
+using System;
+
+using dosymep.SimpleServices;
+
+using pyRevitLabs.Json;
+using pyRevitLabs.Json.Linq;
+
+namespace RevitPackageDocumentation.Models.ConfigSerializer;
+
+///
+/// Конвертер для полиморфной десериализации параметров плагина
+///
+public class PluginParamConverter : JsonConverter {
+ private readonly ILocalizationService _localizationService;
+
+ private const string _pluginParamTypeProperty = "PluginParamType";
+ private const string _stringParamType = "StringParam";
+ private const string _selectElemParamType = "SelectElem";
+
+ public PluginParamConverter(ILocalizationService localizationService) {
+ _localizationService = localizationService;
+ }
+
+ public override bool CanConvert(Type objectType) {
+ return objectType == typeof(PluginParamData);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
+ var jObject = JObject.Load(reader);
+ var paramType = jObject[_pluginParamTypeProperty]?.Value();
+
+ if(string.IsNullOrEmpty(paramType))
+ throw new JsonSerializationException(
+ $"{_localizationService.GetLocalizedString("MainViewModel.Property")} '{_pluginParamTypeProperty}' " +
+ $"{_localizationService.GetLocalizedString("MainViewModel.NotFoundInJSON")}");
+
+ try {
+ return paramType switch {
+ _stringParamType => jObject.ToObject(serializer),
+ _selectElemParamType => jObject.ToObject(serializer),
+ _ => throw new NotSupportedException(
+ $"{_localizationService.GetLocalizedString("MainViewModel.UnknownPluginParamType")}: {paramType}")
+ };
+ } catch(Exception ex) {
+ throw new JsonSerializationException(
+ $"{_localizationService.GetLocalizedString("MainViewModel.ErrorDeserializingType")} '{paramType}'", ex);
+ }
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
+ serializer.Serialize(writer, value);
+ }
+
+ public override bool CanWrite => true;
+}
diff --git a/src/RevitPackageDocumentation/Models/ConfigSerializer/SheetComponentConverter.cs b/src/RevitPackageDocumentation/Models/ConfigSerializer/SheetComponentConverter.cs
new file mode 100644
index 000000000..e53830d65
--- /dev/null
+++ b/src/RevitPackageDocumentation/Models/ConfigSerializer/SheetComponentConverter.cs
@@ -0,0 +1,65 @@
+using System;
+
+using dosymep.SimpleServices;
+
+using pyRevitLabs.Json;
+using pyRevitLabs.Json.Linq;
+
+namespace RevitPackageDocumentation.Models.ConfigSerializer;
+
+///
+/// Конвертер для полиморфной десериализации компонентов листа
+///
+public class SheetComponentConverter : JsonConverter {
+ private readonly ILocalizationService _localizationService;
+
+ private const string _componentTypeProperty = "ComponentType";
+ private const string _structuralPlanViewType = "PlanView";
+ private const string _structuralCalloutViewType = "CalloutView";
+ private const string _sectionViewType = "SectionView";
+ private const string _scheduleViewType = "ScheduleView";
+ private const string _textNoteType = "TextNote";
+ private const string _typicalAnnotationType = "TypicalAnnotation";
+ private const string _legendViewType = "LegendView";
+
+ public SheetComponentConverter(ILocalizationService localizationService) {
+ _localizationService = localizationService;
+ }
+
+ public override bool CanConvert(Type objectType) {
+ return objectType == typeof(SheetComponentData);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
+ var jObject = JObject.Load(reader);
+ var componentType = jObject[_componentTypeProperty]?.Value();
+
+ if(string.IsNullOrEmpty(componentType))
+ throw new JsonSerializationException(
+ $"{_localizationService.GetLocalizedString("MainViewModel.Property")} '{_componentTypeProperty}' " +
+ $"{_localizationService.GetLocalizedString("MainViewModel.NotFoundInJSON")}");
+
+ try {
+ return componentType switch {
+ _structuralPlanViewType => jObject.ToObject(serializer),
+ _structuralCalloutViewType => jObject.ToObject(serializer),
+ _sectionViewType => jObject.ToObject(serializer),
+ _scheduleViewType => jObject.ToObject(serializer),
+ _textNoteType => jObject.ToObject(serializer),
+ _typicalAnnotationType => jObject.ToObject(serializer),
+ _legendViewType => jObject.ToObject(serializer),
+ _ => throw new NotSupportedException(
+ $"{_localizationService.GetLocalizedString("MainViewModel.UnknownComponentType")}: {componentType}")
+ };
+ } catch(Exception ex) {
+ throw new JsonSerializationException(
+ $"{_localizationService.GetLocalizedString("MainViewModel.ErrorDeserializingType")} '{componentType}'", ex);
+ }
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
+ serializer.Serialize(writer, value);
+ }
+
+ public override bool CanWrite => true;
+}
diff --git a/src/RevitPackageDocumentation/Models/ConfigSerializer/SheetSetConfig.cs b/src/RevitPackageDocumentation/Models/ConfigSerializer/SheetSetConfig.cs
new file mode 100644
index 000000000..0d66a0e68
--- /dev/null
+++ b/src/RevitPackageDocumentation/Models/ConfigSerializer/SheetSetConfig.cs
@@ -0,0 +1,260 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+using Autodesk.Revit.DB;
+
+using dosymep.SimpleServices;
+
+using pyRevitLabs.Json;
+
+namespace RevitPackageDocumentation.Models.ConfigSerializer;
+
+public class SheetSetConfig {
+ private readonly ISheetSetSerializer _serializer;
+ private readonly ILocalizationService _localizationService;
+
+ public SheetSetConfig(ISheetSetSerializer sheetSetSerializer, ILocalizationService localizationService) {
+ _serializer = sheetSetSerializer;
+ _localizationService = localizationService;
+ }
+
+ ///
+ /// Загрузить конфигурацию из файла.
+ ///
+ public SheetSetData Import(string path) {
+ if(string.IsNullOrEmpty(path) || !File.Exists(path)) {
+ return new SheetSetData();
+ }
+
+ try {
+ string json = File.ReadAllText(path);
+ return _serializer.Deserialize(json);
+ } catch(JsonSerializationException ex) {
+ throw new InvalidOperationException(
+ $"{_localizationService.GetLocalizedString("MainViewModel.FileDeserializationError")} {path}", ex);
+ } catch(IOException ex) {
+ throw new InvalidOperationException(
+ $"{_localizationService.GetLocalizedString("MainViewModel.ErrorReadingFile")} {path}", ex);
+ }
+ }
+
+ ///
+ /// Сохранить конфигурацию в файл.
+ ///
+ public void Export(SheetSetData data, string path) {
+ if(string.IsNullOrEmpty(path))
+ throw new ArgumentException(
+ $"{_localizationService.GetLocalizedString("MainViewModel.SavePathIsNotCorrect")}", nameof(path));
+
+ if(data == null)
+ throw new ArgumentNullException(nameof(data));
+
+ try {
+ string json = _serializer.Serialize(data);
+ File.WriteAllText(path, json);
+ } catch(IOException ex) {
+ throw new InvalidOperationException(
+ $"{_localizationService.GetLocalizedString("MainViewModel.ErrorWritingFile")} {path}", ex);
+ }
+ }
+}
+
+
+///
+/// DTO комплекта листов
+///
+public class SheetSetData {
+ public string Name { get; set; } = "Новая конфигурация";
+ public List Sheets { get; set; } = [];
+ public List Params { get; set; } = [];
+}
+
+public abstract class ParamContainerModuleData {
+ public bool? IsModuleCheck { get; set; }
+ public string ModuleName { get; set; }
+ public string ModuleComment { get; set; }
+ public CustomParametersListData CustomParamsList { get; set; }
+}
+
+///
+/// DTO комплекта листа
+///
+public class SheetData : ParamContainerModuleData {
+ public string SheetNameFormula { get; set; }
+ public string SheetSize { get; set; }
+ public string SheetCoefficient { get; set; }
+ public string TitleBlockFamilyName { get; set; }
+ public string TitleBlockTypeName { get; set; }
+ public List Views { get; set; } = [];
+}
+
+public abstract class SheetComponentData : ParamContainerModuleData {
+ public abstract string ComponentType { get; }
+}
+
+///
+/// DTO модуля вида в плане несущих конструкций
+///
+public class PlanViewData : SheetComponentData {
+ public override string ComponentType => "PlanView";
+
+ public string ViewNameFormula { get; set; }
+ public string ViewFamilyTypeName { get; set; }
+ public FiltrationComboBoxFilterListData ViewFamilyTypeFilterValues { get; set; }
+ public string ViewTemplateName { get; set; }
+ public FiltrationComboBoxFilterListData ViewTemplateFilterValues { get; set; }
+ public string ViewportTypeName { get; set; }
+ public FiltrationComboBoxFilterListData ViewportTypeFilterValues { get; set; }
+ public string ViewCount { get; set; }
+ public string SelectedSelectElemParamName { get; set; }
+}
+
+///
+/// DTO модуля фрагмента плана несущих конструкций
+///
+public class CalloutViewData : SheetComponentData {
+ public override string ComponentType => "CalloutView";
+
+ public string ViewNameFormula { get; set; }
+ public string ViewFamilyTypeName { get; set; }
+ public FiltrationComboBoxFilterListData ViewFamilyTypeFilterValues { get; set; }
+ public string ViewTemplateName { get; set; }
+ public FiltrationComboBoxFilterListData ViewTemplateFilterValues { get; set; }
+ public string ViewportTypeName { get; set; }
+ public FiltrationComboBoxFilterListData ViewportTypeFilterValues { get; set; }
+ public string ViewCount { get; set; }
+ public string SelectedSelectElemParamName { get; set; }
+}
+
+
+///
+/// DTO модуля вида в сечения
+///
+public class SectionViewData : SheetComponentData {
+ public override string ComponentType => "SectionView";
+
+ public string ViewNameFormula { get; set; }
+ public string ViewCount { get; set; }
+ public string ViewFamilyTypeName { get; set; }
+ public FiltrationComboBoxFilterListData ViewFamilyTypeFilterValues { get; set; }
+ public string ViewTemplateName { get; set; }
+ public FiltrationComboBoxFilterListData ViewTemplateFilterValues { get; set; }
+ public string ViewportTypeName { get; set; }
+ public FiltrationComboBoxFilterListData ViewportTypeFilterValues { get; set; }
+ public string SelectedSelectElemParamName { get; set; }
+}
+
+///
+/// DTO модуля спецификации
+///
+public class ScheduleViewData : SheetComponentData {
+ public override string ComponentType => "ScheduleView";
+
+ public string ReferenceViewName { get; set; }
+ public FiltrationComboBoxFilterListData ReferenceViewFilterValues { get; set; }
+ public string ViewNameFormula { get; set; }
+ public string ViewCount { get; set; }
+ public string ViewColumn { get; set; }
+ public ScheduleFilterListData ScheduleFilterList { get; set; }
+}
+
+
+///
+/// DTO модуля текста
+///
+public class TextNoteData : SheetComponentData {
+ public override string ComponentType => "TextNote";
+
+ public string TextFormula { get; set; }
+ public string TextNoteTypeName { get; set; }
+ public FiltrationComboBoxFilterListData TextNoteTypeFilterValues { get; set; }
+ public string TextWidth { get; set; }
+}
+
+///
+/// DTO модуля типовой аннотации
+///
+public class TypicalAnnotationData : SheetComponentData {
+ public override string ComponentType => "TypicalAnnotation";
+
+ public string AnnotationTypeName { get; set; }
+ public FiltrationComboBoxFilterListData AnnotationTypeFilterValues { get; set; }
+}
+
+///
+/// DTO модуля легенды
+///
+public class LegendViewData : SheetComponentData {
+ public override string ComponentType => "LegendView";
+
+ public string ViewName { get; set; }
+ public FiltrationComboBoxFilterListData ViewFilterValues { get; set; }
+ public string ViewportTypeName { get; set; }
+ public FiltrationComboBoxFilterListData ViewportTypeFilterValues { get; set; }
+}
+
+///
+/// DTO списка фильтров спецификации
+///
+public class ScheduleFilterListData {
+ public List ScheduleFilterRules { get; set; }
+}
+
+///
+/// DTO фильтра спецификации
+///
+public class ScheduleFilterRuleData {
+ public string FieldName { get; set; }
+ public ScheduleFilterType FilterType { get; set; }
+ public string FilterValueFormula { get; set; }
+}
+
+///
+/// DTO списка дополнительных параметров
+///
+public class CustomParametersListData {
+ public List Params { get; set; }
+}
+
+///
+/// DTO дополнительного параметра
+///
+public class CustomParameterData {
+ public string ParamName { get; set; }
+ public string ParamValueFormula { get; set; }
+}
+
+///
+/// DTO списка фильтров для свойства
+///
+public class FiltrationComboBoxFilterListData {
+ public List ValueList { get; set; }
+}
+
+///
+/// DTO дополнительного параметра
+///
+public class FiltrationComboBoxFilterData {
+ public string ValueFormula { get; set; }
+}
+
+
+///
+/// DTO параметров
+///
+public abstract class PluginParamData {
+ public abstract string PluginParamType { get; }
+ public string ParamName { get; set; }
+ public string ParamComment { get; set; }
+}
+
+public class StringParamData : PluginParamData {
+ public override string PluginParamType => "StringParam";
+
+ public string StringValue { get; set; }
+}
+
+public class SelectElemParamData : PluginParamData {
+ public override string PluginParamType => "SelectElem";
+}
diff --git a/src/RevitPackageDocumentation/Models/ConfigSerializer/SheetSetDataFactory.cs b/src/RevitPackageDocumentation/Models/ConfigSerializer/SheetSetDataFactory.cs
new file mode 100644
index 000000000..0e59890b0
--- /dev/null
+++ b/src/RevitPackageDocumentation/Models/ConfigSerializer/SheetSetDataFactory.cs
@@ -0,0 +1,247 @@
+using System;
+using System.Linq;
+
+using Autodesk.Revit.DB;
+
+using dosymep.SimpleServices;
+
+using RevitPackageDocumentation.ViewModels.Configuration;
+using RevitPackageDocumentation.ViewModels.Configuration.Sheet;
+using RevitPackageDocumentation.ViewModels.Configuration.Sheet.SheetComponents;
+using RevitPackageDocumentation.ViewModels.Configuration.SheetSetParameters.Parameters;
+using RevitPackageDocumentation.ViewModels.FiltrationComboBoxVMs;
+
+namespace RevitPackageDocumentation.Models.ConfigSerializer;
+
+internal interface ISheetSetDataFactory {
+ SheetSetData CreateSheetSetData();
+ SheetSetData CreateSheetSetData(SheetSetVM vm);
+ SheetData CreateSheetData(SheetVM vm);
+ SheetComponentData CreateComponentData(SheetComponentVM vm);
+ SheetComponentData CreateComponentData(Type componentType);
+ PluginParamData CreatePluginParamData(PluginParamVM vm);
+ PluginParamData CreatePluginParamData(Type paramType);
+}
+
+internal class SheetSetDataFactory : ISheetSetDataFactory {
+ private readonly ILocalizationService _localizationService;
+
+ public SheetSetDataFactory(ILocalizationService localizationService) {
+ _localizationService = localizationService;
+ }
+
+ public SheetSetData CreateSheetSetData() {
+ return new SheetSetData();
+ }
+
+ public SheetSetData CreateSheetSetData(SheetSetVM vm) {
+ if(vm == null)
+ return CreateSheetSetData();
+
+ return new SheetSetData {
+ Name = vm.Name,
+ Sheets = vm.SheetList?.Select(CreateSheetData).ToList(),
+ Params = vm.SheetSetParams.Params?.Select(CreatePluginParamData).ToList()
+ };
+ }
+
+ public SheetData CreateSheetData(SheetVM vm) {
+ if(vm == null)
+ return new SheetData();
+
+ return new SheetData {
+ IsModuleCheck = vm.IsModuleCheck,
+ ModuleName = vm.ModuleName,
+ ModuleComment = vm.ModuleComment,
+ CustomParamsList = GetCustomParametersList(vm),
+
+ SheetNameFormula = vm.SheetNameFormula,
+ SheetSize = vm.SheetSize,
+ SheetCoefficient = vm.SheetCoefficient,
+ TitleBlockFamilyName = vm.TitleBlockFamily?.Name,
+ TitleBlockTypeName = vm.TitleBlockType?.Name,
+ Views = vm.SheetComponents?.Select(CreateComponentData).ToList(),
+ };
+ }
+
+ public SheetComponentData CreateComponentData(SheetComponentVM sheetComponentVM) {
+ return sheetComponentVM switch {
+ PlanViewVM vm => new PlanViewData {
+ IsModuleCheck = vm.IsModuleCheck,
+ ModuleName = vm.ModuleName,
+ ModuleComment = vm.ModuleComment,
+ CustomParamsList = GetCustomParametersList(vm),
+
+ ViewNameFormula = vm.ViewNameFormula,
+ ViewFamilyTypeName = vm.ViewFamilyType?.Name,
+ ViewFamilyTypeFilterValues = GetFiltrationComboBoxFilterList(vm.ViewFamilyTypeFilter),
+ ViewTemplateName = vm.ViewTemplate?.Name,
+ ViewTemplateFilterValues = GetFiltrationComboBoxFilterList(vm.ViewTemplateFilter),
+ ViewportTypeName = vm.ViewportType?.Name,
+ ViewportTypeFilterValues = GetFiltrationComboBoxFilterList(vm.ViewportTypeFilter),
+ ViewCount = vm.ViewCount,
+ SelectedSelectElemParamName = vm.SelectedSelectElemParam?.ParamName,
+ },
+
+ CalloutViewVM vm => new CalloutViewData {
+ IsModuleCheck = vm.IsModuleCheck,
+ ModuleName = vm.ModuleName,
+ ModuleComment = vm.ModuleComment,
+ CustomParamsList = GetCustomParametersList(vm),
+
+ ViewNameFormula = vm.ViewNameFormula,
+ ViewFamilyTypeName = vm.ViewFamilyType?.Name,
+ ViewFamilyTypeFilterValues = GetFiltrationComboBoxFilterList(vm.ViewFamilyTypeFilter),
+ ViewTemplateName = vm.ViewTemplate?.Name,
+ ViewTemplateFilterValues = GetFiltrationComboBoxFilterList(vm.ViewTemplateFilter),
+ ViewportTypeName = vm.ViewportType?.Name,
+ ViewportTypeFilterValues = GetFiltrationComboBoxFilterList(vm.ViewportTypeFilter),
+ ViewCount = vm.ViewCount,
+ SelectedSelectElemParamName = vm.SelectedSelectElemParam?.ParamName,
+ },
+
+ SectionViewVM vm => new SectionViewData {
+ IsModuleCheck = vm.IsModuleCheck,
+ ModuleName = vm.ModuleName,
+ ModuleComment = vm.ModuleComment,
+ CustomParamsList = GetCustomParametersList(vm),
+
+ ViewNameFormula = vm.ViewNameFormula,
+ ViewFamilyTypeName = vm.ViewFamilyType?.Name,
+ ViewFamilyTypeFilterValues = GetFiltrationComboBoxFilterList(vm.ViewFamilyTypeFilter),
+ ViewTemplateName = vm.ViewTemplate?.Name,
+ ViewTemplateFilterValues = GetFiltrationComboBoxFilterList(vm.ViewTemplateFilter),
+ ViewportTypeName = vm.ViewportType?.Name,
+ ViewportTypeFilterValues = GetFiltrationComboBoxFilterList(vm.ViewportTypeFilter),
+ ViewCount = vm.ViewCount,
+ SelectedSelectElemParamName = vm.SelectedSelectElemParam?.ParamName,
+ },
+
+ ScheduleViewVM vm => new ScheduleViewData {
+ IsModuleCheck = vm.IsModuleCheck,
+ ModuleName = vm.ModuleName,
+ ModuleComment = vm.ModuleComment,
+ CustomParamsList = GetCustomParametersList(vm),
+
+ ReferenceViewName = vm.ReferenceSpec?.Name,
+ ReferenceViewFilterValues = GetFiltrationComboBoxFilterList(vm.ReferenceSpecFilter),
+ ViewNameFormula = vm.ViewNameFormula,
+ ViewColumn = vm.ViewColumn,
+ ViewCount = vm.ViewCount,
+ ScheduleFilterList = GetScheduleFilterList(vm),
+ },
+
+ TextNoteVM vm => new TextNoteData {
+ IsModuleCheck = vm.IsModuleCheck,
+ ModuleName = vm.ModuleName,
+ ModuleComment = vm.ModuleComment,
+ CustomParamsList = GetCustomParametersList(vm),
+
+ TextFormula = vm.TextFormula,
+ TextNoteTypeName = vm.TextNoteType?.Name,
+ TextNoteTypeFilterValues = GetFiltrationComboBoxFilterList(vm.TextNoteTypeFilter),
+ TextWidth = vm.TextWidth,
+ },
+
+ TypicalAnnotationVM vm => new TypicalAnnotationData {
+ IsModuleCheck = vm.IsModuleCheck,
+ ModuleName = vm.ModuleName,
+ ModuleComment = vm.ModuleComment,
+ CustomParamsList = GetCustomParametersList(vm),
+
+ AnnotationTypeName =
+ string.Format("{0}: {1}",
+ vm.AnnotationType?.FamilyName ?? string.Empty,
+ vm.AnnotationType?.Name ?? string.Empty),
+ AnnotationTypeFilterValues = GetFiltrationComboBoxFilterList(vm.AnnotationTypeFilter),
+ },
+
+ LegendViewVM vm => new LegendViewData {
+ IsModuleCheck = vm.IsModuleCheck,
+ ModuleName = vm.ModuleName,
+ ModuleComment = vm.ModuleComment,
+ CustomParamsList = GetCustomParametersList(vm),
+
+ ViewName = vm.LegendView?.Name,
+ ViewFilterValues = GetFiltrationComboBoxFilterList(vm.LegendViewFilter),
+ ViewportTypeName = vm.ViewportType?.Name,
+ ViewportTypeFilterValues = GetFiltrationComboBoxFilterList(vm.ViewportTypeFilter),
+ },
+
+ _ => throw new NotSupportedException(
+ $"{_localizationService.GetLocalizedString("MainViewModel.Type")} '{sheetComponentVM?.GetType().Name}' " +
+ $"{_localizationService.GetLocalizedString("MainViewModel.NotSupported")}")
+ };
+ }
+
+ private CustomParametersListData GetCustomParametersList(BaseParamContainerVM vm) => new() {
+ Params = vm.CustomParamsList.Params
+ .Select(r => new CustomParameterData() {
+ ParamName = r.ParamName ?? string.Empty,
+ ParamValueFormula = r.ParamValueFormula ?? string.Empty,
+ })
+ .ToList()
+ };
+
+ private FiltrationComboBoxFilterListData GetFiltrationComboBoxFilterList(FiltrationComboBoxFilterListVM vm) => new() {
+ ValueList = vm.ValueList
+ .Select(r => new FiltrationComboBoxFilterData() {
+ ValueFormula = r.ValueFormula ?? string.Empty,
+ })
+ .ToList()
+ };
+
+ private ScheduleFilterListData GetScheduleFilterList(ScheduleViewVM vm) => new() {
+ ScheduleFilterRules = vm.ScheduleFilterList.ScheduleFilterRules
+ .Select(r => new ScheduleFilterRuleData() {
+ FieldName = r.SelectedSpecFieldName ?? string.Empty,
+ FilterType = r.SelectedFilterType?.FilterType ?? ScheduleFilterType.Equal,
+ FilterValueFormula = r.FilterValueFormula
+ })
+ .ToList()
+ };
+
+ public SheetComponentData CreateComponentData(Type componentType) {
+ return componentType switch {
+ Type t when t == typeof(PlanViewVM) => new PlanViewData(),
+ Type t when t == typeof(CalloutViewVM) => new CalloutViewData(),
+ Type t when t == typeof(SectionViewVM) => new SectionViewData(),
+ Type t when t == typeof(ScheduleViewVM) => new ScheduleViewData(),
+ Type t when t == typeof(TextNoteVM) => new TextNoteData(),
+ Type t when t == typeof(TypicalAnnotationVM) => new TypicalAnnotationData(),
+ Type t when t == typeof(LegendViewVM) => new LegendViewData(),
+ _ => throw new NotSupportedException($"{_localizationService.GetLocalizedString("MainViewModel.Type")} " +
+ $"'{componentType?.Name}' {_localizationService.GetLocalizedString("MainViewModel.NotSupported")}")
+ };
+ }
+
+ public PluginParamData CreatePluginParamData(PluginParamVM vm) {
+ if(vm == null)
+ return null;
+
+ return vm switch {
+ StringParamVM stringVm => new StringParamData {
+ ParamName = stringVm.ParamName,
+ ParamComment = stringVm.ParamComment,
+ StringValue = stringVm.StringValue
+ },
+ SelectElemParamVM selectVm => new SelectElemParamData {
+ ParamName = selectVm.ParamName,
+ ParamComment = selectVm.ParamComment
+ },
+ _ => throw new NotSupportedException(
+ $"{_localizationService.GetLocalizedString("MainViewModel.ParameterType")} '{vm?.GetType().Name}' " +
+ $"{_localizationService.GetLocalizedString("MainViewModel.NotSupported")}")
+ };
+ }
+
+ public PluginParamData CreatePluginParamData(Type paramType) {
+ return paramType switch {
+ Type t when t == typeof(StringParamVM) => new StringParamData(),
+ Type t when t == typeof(SelectElemParamVM) => new SelectElemParamData(),
+ _ => throw new NotSupportedException(
+ $"{_localizationService.GetLocalizedString("MainViewModel.ParameterType")} '{paramType?.Name}' " +
+ $"{_localizationService.GetLocalizedString("MainViewModel.NotSupported")}")
+ };
+ }
+}
diff --git a/src/RevitPackageDocumentation/Models/ConfigSerializer/SheetSetSerializer.cs b/src/RevitPackageDocumentation/Models/ConfigSerializer/SheetSetSerializer.cs
new file mode 100644
index 000000000..324181558
--- /dev/null
+++ b/src/RevitPackageDocumentation/Models/ConfigSerializer/SheetSetSerializer.cs
@@ -0,0 +1,31 @@
+using System.Collections.Generic;
+
+using pyRevitLabs.Json;
+
+namespace RevitPackageDocumentation.Models.ConfigSerializer;
+
+public interface ISheetSetSerializer {
+ string Serialize(T obj);
+ T Deserialize(string json);
+}
+
+public class SheetSetSerializer : ISheetSetSerializer {
+ private readonly JsonSerializerSettings _settings;
+
+ public SheetSetSerializer(IEnumerable converters) {
+ _settings = new JsonSerializerSettings {
+ Formatting = Formatting.Indented,
+ };
+ foreach(var converter in converters) {
+ _settings.Converters.Add(converter);
+ }
+ }
+
+ public string Serialize(T obj) {
+ return JsonConvert.SerializeObject(obj, _settings);
+ }
+
+ public T Deserialize(string json) {
+ return JsonConvert.DeserializeObject(json, _settings);
+ }
+}
diff --git a/src/RevitPackageDocumentation/Models/FloorSelectionFilter.cs b/src/RevitPackageDocumentation/Models/FloorSelectionFilter.cs
new file mode 100644
index 000000000..757c9916e
--- /dev/null
+++ b/src/RevitPackageDocumentation/Models/FloorSelectionFilter.cs
@@ -0,0 +1,13 @@
+using Autodesk.Revit.DB;
+using Autodesk.Revit.UI.Selection;
+
+namespace RevitPackageDocumentation.Models;
+internal class FloorSelectionFilter : ISelectionFilter {
+ public bool AllowElement(Element element) {
+ return element is Floor;
+ }
+
+ public bool AllowReference(Reference refer, XYZ point) {
+ return false;
+ }
+}
diff --git a/src/RevitPackageDocumentation/Models/PluginConfig.cs b/src/RevitPackageDocumentation/Models/PluginConfig.cs
new file mode 100644
index 000000000..97af478e4
--- /dev/null
+++ b/src/RevitPackageDocumentation/Models/PluginConfig.cs
@@ -0,0 +1,66 @@
+using dosymep.Bim4Everyone;
+using dosymep.Bim4Everyone.ProjectConfigs;
+using dosymep.Serializers;
+
+using pyRevitLabs.Json;
+
+namespace RevitPackageDocumentation.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(RevitPackageDocumentation))
+ .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 SheetSetDataPath { get; set; }
+}
diff --git a/src/RevitPackageDocumentation/Models/RevitElementPickerService.cs b/src/RevitPackageDocumentation/Models/RevitElementPickerService.cs
new file mode 100644
index 000000000..4e54490a4
--- /dev/null
+++ b/src/RevitPackageDocumentation/Models/RevitElementPickerService.cs
@@ -0,0 +1,48 @@
+using System;
+
+using Autodesk.Revit.DB;
+using Autodesk.Revit.UI.Selection;
+
+using dosymep.SimpleServices;
+
+using RevitPackageDocumentation.Views;
+
+namespace RevitPackageDocumentation.Models;
+public interface IRevitElementPickerService {
+ void PickElement(Action onSelected);
+}
+
+public class RevitElementPickerService : IRevitElementPickerService {
+ private readonly RevitRepository _revitRepository;
+ private readonly MainWindow _mainWindow;
+ private readonly ILocalizationService _localizationService;
+
+ private RevitElementPickerService(
+ RevitRepository revitRepository,
+ MainWindow mainWindow,
+ ILocalizationService localizationService) {
+ _revitRepository = revitRepository;
+ _mainWindow = mainWindow;
+ _localizationService = localizationService;
+ }
+
+ internal static RevitElementPickerService GetRevitElementPickerService(
+ RevitRepository revitRepository,
+ MainWindow mainWindow,
+ ILocalizationService localizationService) {
+
+ return new RevitElementPickerService(revitRepository, mainWindow, localizationService);
+ }
+
+ public void PickElement(Action onSelected) {
+ _mainWindow.Hide();
+ ISelectionFilter selectFilter = new FloorSelectionFilter();
+ var reference = _revitRepository.ActiveUIDocument.Selection.PickObject(
+ ObjectType.Element,
+ selectFilter,
+ _localizationService.GetLocalizedString("MainWindow.PickElement"));
+ var element = _revitRepository.Document.GetElement(reference);
+ onSelected?.Invoke(element);
+ _mainWindow.ShowDialog();
+ }
+}
diff --git a/src/RevitPackageDocumentation/Models/RevitRepository.cs b/src/RevitPackageDocumentation/Models/RevitRepository.cs
new file mode 100644
index 000000000..625175d45
--- /dev/null
+++ b/src/RevitPackageDocumentation/Models/RevitRepository.cs
@@ -0,0 +1,270 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using Autodesk.Revit.ApplicationServices;
+using Autodesk.Revit.DB;
+using Autodesk.Revit.UI;
+
+using dosymep.Revit;
+using dosymep.SimpleServices;
+
+using RevitPackageDocumentation.Models.ScheduleFilters;
+
+namespace RevitPackageDocumentation.Models;
+
+///
+/// Класс доступа к документу и приложению Revit.
+///
+///
+/// В случае если данный класс разрастается, рекомендуется его разделить на несколько.
+///
+internal class RevitRepository {
+ private readonly IMessageBoxService _messageBoxService;
+ private readonly ILocalizationService _localizationService;
+
+ ///
+ /// Создает экземпляр репозитория.
+ ///
+ /// Класс доступа к интерфейсу Revit.
+ public RevitRepository(
+ UIApplication uiApplication,
+ IMessageBoxService messageBoxService,
+ ILocalizationService localizationService) {
+ UIApplication = uiApplication;
+ _messageBoxService = messageBoxService;
+ _localizationService = localizationService;
+
+ PlanViewTypes = GetFloorPlanViewTypes();
+ SectionViewTypes = GetSectionViewTypes();
+ PlanViewTemplates = GetPlanViewTemplates();
+ SectionViewTemplates = GetSectionViewTemplates();
+ ViewportTypes = GetViewportTypes();
+ TextNoteTypes = GetTextNoteTypes();
+ GenericAnnotationTypes = GetGenericAnnotationTypes();
+ LegendsInProject = GetLegendsInProject();
+ TitleBlockFamilies = GetTitleBlockFamilies();
+ Sheets = GetSheets();
+ Views = GetViews();
+ Specs = GetSpecs();
+ FilterTypes = GetFilterTypes();
+ WorksetParamId = new ElementId(BuiltInParameter.ELEM_PARTITION_PARAM);
+ }
+
+ ///
+ /// Класс доступа к интерфейсу 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 PlanViewTypes { get; }
+ public List SectionViewTypes { get; }
+ public List PlanViewTemplates { get; }
+ public List SectionViewTemplates { get; }
+ public List ViewportTypes { get; }
+ public List TextNoteTypes { get; }
+ public List GenericAnnotationTypes { get; }
+ public List LegendsInProject { get; }
+ public List TitleBlockFamilies { get; }
+ public List Sheets { get; }
+ public List Views { get; }
+ public List Specs { get; }
+ public List FilterTypes { get; }
+ public ElementId WorksetParamId { get; }
+
+
+ ///
+ /// Возвращает список типоразмеров видов в плане в проекте
+ ///
+ private List GetFloorPlanViewTypes() => new FilteredElementCollector(Document)
+ .OfClass(typeof(ViewFamilyType))
+ .OfType()
+ .Where(a => ViewFamily.FloorPlan == a.ViewFamily)
+ .OrderBy(a => a.Name)
+ .ToList();
+
+ private List GetStructuralPlanViewTypes() => new FilteredElementCollector(Document)
+ .OfClass(typeof(ViewFamilyType))
+ .OfType()
+ .Where(a => ViewFamily.StructuralPlan == a.ViewFamily)
+ .OrderBy(a => a.Name)
+ .ToList();
+
+ ///
+ /// Возвращает список типоразмеров видов в разрезе в проекте
+ ///
+ private List GetSectionViewTypes() => new FilteredElementCollector(Document)
+ .OfClass(typeof(ViewFamilyType))
+ .OfType()
+ .Where(a => ViewFamily.Section == a.ViewFamily)
+ .OrderBy(a => a.Name)
+ .ToList();
+
+
+ ///
+ /// Возвращает список всех шаблонов планов в проекте
+ ///
+ public List GetPlanViewTemplates() => new FilteredElementCollector(Document)
+ .OfClass(typeof(ViewPlan))
+ .WhereElementIsNotElementType()
+ .OfType()
+ .Where(v => v.IsTemplate == true)
+ .OrderBy(a => a.Name)
+ .ToList();
+
+
+ ///
+ /// Возвращает список всех шаблонов сечений в проекте
+ ///
+ public List GetSectionViewTemplates() => new FilteredElementCollector(Document)
+ .OfClass(typeof(ViewSection))
+ .WhereElementIsNotElementType()
+ .OfType()
+ .Where(v => v.IsTemplate == true)
+ .OrderBy(a => a.Name)
+ .ToList();
+
+ ///
+ /// Возвращает список всех типов видовых экранов в проекте
+ ///
+ public List GetViewportTypes() {
+ var defaultElementType = Document.GetElement(
+ Document.GetDefaultElementTypeId(ElementTypeGroup.ViewportType)) as ElementType;
+
+ return new FilteredElementCollector(Document)
+ .OfClass(typeof(ElementType))
+ .OfType()
+ .Where(q => q.FamilyName == defaultElementType.FamilyName)
+ .OrderBy(a => a.Name)
+ .ToList();
+ }
+
+ ///
+ /// Возвращает список всех типов текста в проекте
+ ///
+ public List GetTextNoteTypes() => new FilteredElementCollector(Document)
+ .OfClass(typeof(TextNoteType))
+ .OfType()
+ .OrderBy(a => a.Name)
+ .ToList();
+
+ ///
+ /// Возвращает список типоразмеров типовых аннотаций в проекте
+ ///
+ public List GetGenericAnnotationTypes() => new FilteredElementCollector(Document)
+ .OfCategory(BuiltInCategory.OST_GenericAnnotation)
+ .WhereElementIsElementType()
+ .OfType()
+ .OrderBy(a => a.FamilyName)
+ .ThenBy(a => a.Name)
+ .ToList();
+
+ ///
+ /// Возвращает список всех легенд, присутствующих в проекте
+ ///
+ public List GetLegendsInProject() => new FilteredElementCollector(Document)
+ .OfClass(typeof(View))
+ .OfType()
+ .Where(view => view.ViewType == ViewType.Legend)
+ .OrderBy(a => a.Name)
+ .ToList();
+
+ ///
+ /// Возвращает список семейств рамок листа
+ ///
+ public List GetTitleBlockFamilies() => new FilteredElementCollector(Document)
+ .OfClass(typeof(Family))
+ .OfType()
+ .Where(f => f.FamilyCategory.GetBuiltInCategory() == BuiltInCategory.OST_TitleBlocks)
+ .OrderBy(a => a.Name)
+ .ToList();
+
+ public List GetSheets() => new FilteredElementCollector(Document)
+ .OfClass(typeof(ViewSheet))
+ .OfType()
+ .OrderBy(a => a.Name)
+ .ToList();
+
+ public List GetViews() => new FilteredElementCollector(Document)
+ .OfClass(typeof(View))
+ .OfType()
+ .OrderBy(a => a.Name)
+ .ToList();
+
+ public List GetSpecs() => new FilteredElementCollector(Document)
+ .OfClass(typeof(ViewSchedule))
+ .OfType()
+ .OrderBy(a => a.Name)
+ .ToList();
+
+ public List GetFilterTypes() => Enum.GetValues(typeof(ScheduleFilterType))
+ .Cast()
+ .Where(s => s != ScheduleFilterType.Invalid)
+ .Where(s => s != ScheduleFilterType.HasParameter)
+ .Where(s => s != ScheduleFilterType.IsAssociatedWithGlobalParameter)
+ .Where(s => s != ScheduleFilterType.IsNotAssociatedWithGlobalParameter)
+ .Select(s =>
+ new ScheduleTypeInfo(s, _localizationService.GetLocalizedString($"ScheduleFilterType.{s}") ?? s.ToString()))
+ .ToList();
+
+ public ViewSheet GetSheetByName(string sheetName) {
+ return Sheets
+ .FirstOrDefault(o => o.Name.Equals(sheetName));
+ }
+
+ public View GetViewByName(string viewName) {
+ return Views
+ .FirstOrDefault(o => o.Name.Equals(viewName));
+ }
+
+ internal ViewSchedule GetSpecByName(string viewName) {
+ return Specs
+ .FirstOrDefault(o => o.Name.Equals(viewName));
+ }
+
+
+ public FamilyInstance GetTitleBlocks(ViewSheet viewSheet) {
+ return new FilteredElementCollector(Document, viewSheet.Id)
+ .OfCategory(BuiltInCategory.OST_TitleBlocks)
+ .WhereElementIsNotElementType()
+ .FirstOrDefault() as FamilyInstance;
+ }
+
+ public List GetViewports(ViewSheet viewSheet) {
+ return viewSheet.GetAllViewports()
+ .Select(id => Document.GetElement(id) as Viewport)
+ .ToList();
+ }
+
+ public List GetScheduleSheetInstances(ViewSheet viewSheet) {
+ return new FilteredElementCollector(Document, viewSheet.Id)
+ .OfClass(typeof(ScheduleSheetInstance))
+ .WhereElementIsNotElementType()
+ .Cast()
+ .ToList();
+ }
+
+ public List GetTextNotes(ViewSheet viewSheet) {
+ return new FilteredElementCollector(Document, viewSheet.Id)
+ .OfClass(typeof(TextNote))
+ .WhereElementIsNotElementType()
+ .Cast()
+ .ToList();
+ }
+}
diff --git a/src/RevitPackageDocumentation/Models/ScheduleFilters/ScheduleFieldInfo.cs b/src/RevitPackageDocumentation/Models/ScheduleFilters/ScheduleFieldInfo.cs
new file mode 100644
index 000000000..6dc47d5d2
--- /dev/null
+++ b/src/RevitPackageDocumentation/Models/ScheduleFilters/ScheduleFieldInfo.cs
@@ -0,0 +1,12 @@
+using Autodesk.Revit.DB;
+
+namespace RevitPackageDocumentation.Models.ScheduleFilters;
+internal class ScheduleFieldInfo {
+ public ScheduleFieldInfo(ScheduleField field) {
+ Field = field;
+ FieldName = field.GetName();
+ }
+
+ public ScheduleField Field { get; set; }
+ public string FieldName { get; set; }
+}
diff --git a/src/RevitPackageDocumentation/Models/ScheduleFilters/ScheduleTypeInfo.cs b/src/RevitPackageDocumentation/Models/ScheduleFilters/ScheduleTypeInfo.cs
new file mode 100644
index 000000000..eba287dcd
--- /dev/null
+++ b/src/RevitPackageDocumentation/Models/ScheduleFilters/ScheduleTypeInfo.cs
@@ -0,0 +1,12 @@
+using Autodesk.Revit.DB;
+
+namespace RevitPackageDocumentation.Models.ScheduleFilters;
+public class ScheduleTypeInfo {
+ public ScheduleTypeInfo(ScheduleFilterType filterType, string name) {
+ FilterType = filterType;
+ Name = name;
+ }
+
+ public ScheduleFilterType FilterType { get; set; }
+ public string Name { get; set; }
+}
diff --git a/src/RevitPackageDocumentation/Models/StringParamSetService.cs b/src/RevitPackageDocumentation/Models/StringParamSetService.cs
new file mode 100644
index 000000000..bceef5c46
--- /dev/null
+++ b/src/RevitPackageDocumentation/Models/StringParamSetService.cs
@@ -0,0 +1,59 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.RegularExpressions;
+
+using RevitPackageDocumentation.ViewModels.Configuration.SheetSetParameters.Parameters;
+
+namespace RevitPackageDocumentation.Models;
+internal class StringParamSetService {
+ private readonly string _keyWord = "Formula";
+
+ public void SetAll(object instance, IEnumerable sheetSetParams) {
+ // Отбираем свойства, которые string, имеют в имени "Formula" и запускаем обновления значения у всех
+ instance.GetType().GetProperties()
+ .Where(p => p.PropertyType == typeof(string) && p.CanWrite && p.Name.Contains(_keyWord))
+ .ToList()
+ .ForEach(p => Set(instance, p.Name, sheetSetParams));
+ }
+
+ public void SetAll(object instance, IEnumerable sheetSetParams, StringParamVM stringParam) {
+ // Отбираем свойства, которые string, имеют в имени "Formula" и запускаем обновления значения у всех
+ instance.GetType().GetProperties()
+ .Where(p => p.PropertyType == typeof(string) && p.CanWrite && p.Name.Contains(_keyWord))
+ .Where(p => p.GetValue(instance) is string formula && formula.Contains($"{{{stringParam.ParamName}}}"))
+ .ToList()
+ .ForEach(p => Set(instance, p.Name, sheetSetParams));
+ }
+
+ public void Set(object instance, string formulaPropertyName, IEnumerable sheetSetParams) {
+ var propFormula = instance.GetType().GetProperty(formulaPropertyName);
+ var prop = instance.GetType().GetProperty(formulaPropertyName.Replace(_keyWord, ""));
+ if(prop is null) {
+ return;
+ }
+
+ string propFormulaValue = propFormula.GetValue(instance) as string;
+ prop.SetValue(instance, SetValue(propFormulaValue, sheetSetParams));
+ }
+
+
+ public string SetValue(string formula, IEnumerable sheetSetParams) {
+ // префикс_{ФОП_Блок СМР}_суффикс1_{ФОП_Секция СМР}_суффикс2
+ string tempValue = formula;
+
+ var regex = new Regex(@"{([^\}]+)}");
+ MatchCollection matches = regex.Matches(formula);
+
+ Regex regexForParam;
+ foreach(Match match in matches) {
+ string paramName = match.Value.Replace("{", "").Replace("}", "");
+
+ if(sheetSetParams.FirstOrDefault(p => p.ParamName == paramName) is not StringParamVM param) {
+ continue;
+ }
+ regexForParam = new Regex(match.Value);
+ tempValue = regexForParam.Replace(tempValue, param.StringValue, 1);
+ }
+ return tempValue;
+ }
+}
diff --git a/src/RevitPackageDocumentation/Models/UnitUtilsHelper.cs b/src/RevitPackageDocumentation/Models/UnitUtilsHelper.cs
new file mode 100644
index 000000000..953e50694
--- /dev/null
+++ b/src/RevitPackageDocumentation/Models/UnitUtilsHelper.cs
@@ -0,0 +1,27 @@
+using Autodesk.Revit.DB;
+
+namespace RevitPackageDocumentation.Models;
+public class UnitUtilsHelper {
+ public static double ConvertFromInternalValue(double value) {
+ // Перевод из внутренних единиц Revit
+
+#if REVIT_2021_OR_GREATER
+ var unitType = UnitTypeId.Millimeters;
+#else
+ DisplayUnitType unitType = DisplayUnitType.DUT_MILLIMETERS;
+#endif
+ return UnitUtils.ConvertFromInternalUnits(value, unitType);
+ }
+
+
+ public static double ConvertToInternalValue(double value) {
+ // Перевод во внутренние единицы Revit
+
+#if REVIT_2021_OR_GREATER
+ var unitType = UnitTypeId.Millimeters;
+#else
+ DisplayUnitType unitType = DisplayUnitType.DUT_MILLIMETERS;
+#endif
+ return UnitUtils.ConvertToInternalUnits(value, unitType);
+ }
+}
diff --git a/src/RevitPackageDocumentation/README.md b/src/RevitPackageDocumentation/README.md
new file mode 100644
index 000000000..3d5d6f8b6
--- /dev/null
+++ b/src/RevitPackageDocumentation/README.md
@@ -0,0 +1,12 @@
+# RevitPackageDocumentation (Документация комплекта)
+Проект предназначен для создания комплекта документации
+
+# Сборка проекта
+```
+nuke compile --profile RevitPackageDocumentation
+```
+
+# Публикация проекта
+```
+nuke publish --profile RevitPackageDocumentation
+```
diff --git a/src/RevitPackageDocumentation/RevitPackageDocumentation.csproj b/src/RevitPackageDocumentation/RevitPackageDocumentation.csproj
new file mode 100644
index 000000000..ae3efaefb
--- /dev/null
+++ b/src/RevitPackageDocumentation/RevitPackageDocumentation.csproj
@@ -0,0 +1,6 @@
+
+
+ true
+ 12
+
+
diff --git a/src/RevitPackageDocumentation/RevitPackageDocumentationCommand.cs b/src/RevitPackageDocumentation/RevitPackageDocumentationCommand.cs
new file mode 100644
index 000000000..236152e53
--- /dev/null
+++ b/src/RevitPackageDocumentation/RevitPackageDocumentationCommand.cs
@@ -0,0 +1,136 @@
+using System.Globalization;
+using System.Reflection;
+
+using Autodesk.Revit.Attributes;
+using Autodesk.Revit.UI;
+
+using dosymep.Bim4Everyone;
+using dosymep.Bim4Everyone.ProjectConfigs;
+using dosymep.Bim4Everyone.SimpleServices;
+using dosymep.SimpleServices;
+using dosymep.WpfCore.Ninject;
+using dosymep.WpfUI.Core.Ninject;
+
+using Ninject;
+
+using pyRevitLabs.Json;
+
+using RevitPackageDocumentation.Models;
+using RevitPackageDocumentation.Models.ConfigSerializer;
+using RevitPackageDocumentation.ViewModels;
+using RevitPackageDocumentation.ViewModels.Configuration;
+using RevitPackageDocumentation.Views;
+using RevitPackageDocumentation.Views.Extensions;
+
+namespace RevitPackageDocumentation;
+
+///
+/// Класс команды Revit плагина.
+///
+[Transaction(TransactionMode.Manual)]
+public class RevitPackageDocumentationCommand : BasePluginCommand {
+ ///
+ /// Инициализирует команду плагина.
+ ///
+ public RevitPackageDocumentationCommand() {
+ PluginName = "RevitPackageDocumentation";
+ }
+
+ ///
+ /// Метод выполнения основного кода плагина.
+ ///
+ /// Интерфейс взаимодействия с 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()
+ .ToMethod(c => RevitElementPickerService.GetRevitElementPickerService(
+ c.Kernel.Get(),
+ c.Kernel.Get(),
+ c.Kernel.Get()))
+ .InSingletonScope();
+
+ // Регистрация коллекции конвертеров JSON
+ kernel.Bind()
+ .To()
+ .InSingletonScope();
+
+ kernel.Bind()
+ .To()
+ .InSingletonScope();
+
+ // JSON сериализатор с получением всех зарегистрированных конвертеров
+ kernel.Bind()
+ .ToMethod(ctx => {
+ var converters = ctx.Kernel.GetAll();
+ return new SheetSetSerializer(converters);
+ })
+ .InSingletonScope();
+
+ // Настройка конфигурации комплекта листов
+ kernel.Bind()
+ .ToSelf()
+ .InSingletonScope();
+
+ // Фабрика по созданию VM из JSON DTO
+ kernel.Bind()
+ .To()
+ .InSingletonScope();
+
+ // Фабрика по созданию JSON DTO из VM
+ kernel.Bind()
+ .To()
+ .InSingletonScope();
+
+ // Сервис обновления свойств по параметрам конфигурации
+ kernel.Bind()
+ .ToSelf()
+ .InSingletonScope();
+
+ // Используем сервис обновления тем для WinUI
+ kernel.UseWpfUIThemeUpdater();
+
+ // Настройка запуска окна
+ kernel.BindMainWindow();
+
+ // Настройка локализации,
+ // получение имени сборки откуда брать текст
+ string assemblyName = Assembly.GetExecutingAssembly().GetName().Name;
+
+ // Настройка локализации,
+ // установка дефолтной локализации "ru-RU"
+ kernel.UseWpfLocalization(
+ $"/{assemblyName};component/assets/localization/language.xaml",
+ CultureInfo.GetCultureInfo("ru-RU"));
+
+ // Инициализируем extension для локализации ресурсов в файле ресурсов (без доступа к ресурсам окна)
+ var localizationService = kernel.Get();
+ LocalizedExtension.Init(localizationService);
+
+ // Настройка сервиса окошек сообщений
+ kernel.UseWpfUIMessageBox();
+
+ // Сервисы открытия диалогового окна сохранения/открытия файла JSON
+ kernel.UseWpfOpenFileDialog(
+ filter: "JSON files (*.json)|*.json|All files (*.*)|*.*",
+ title: localizationService.GetLocalizedString("MainViewModel.SelectConfigurationFile"));
+ kernel.UseWpfSaveFileDialog(
+ filter: "JSON files (*.json)|*.json",
+ title: localizationService.GetLocalizedString("MainViewModel.SaveConfiguration"),
+ defaultFileName: "config.json",
+ addExtension: true);
+
+ // Вызывает стандартное уведомление
+ Notification(kernel.Get());
+ }
+}
diff --git a/src/RevitPackageDocumentation/ViewModels/Configuration/CustomParameters/CustomParameterVM.cs b/src/RevitPackageDocumentation/ViewModels/Configuration/CustomParameters/CustomParameterVM.cs
new file mode 100644
index 000000000..e44c5e5e3
--- /dev/null
+++ b/src/RevitPackageDocumentation/ViewModels/Configuration/CustomParameters/CustomParameterVM.cs
@@ -0,0 +1,66 @@
+using System.ComponentModel.DataAnnotations;
+using System.Windows.Input;
+
+using dosymep.SimpleServices;
+using dosymep.WPF.Commands;
+
+using RevitPackageDocumentation.Models;
+using RevitPackageDocumentation.ViewModels.Configuration.SheetSetParameters.Parameters;
+using RevitPackageDocumentation.ViewModels.Validation;
+
+namespace RevitPackageDocumentation.ViewModels.Configuration.CustomParameters;
+internal class CustomParameterVM : ValidatableVM {
+ private string _paramValueFormula = string.Empty;
+ private string _paramName;
+ private string _paramValue = string.Empty;
+
+ public CustomParameterVM(
+ CustomParametersListVM customParamsList,
+ StringParamSetService stringParamSetService,
+ ILocalizationService localizationService)
+ : base(localizationService) {
+
+ CustomParamsList = customParamsList;
+ StrParamSetService = stringParamSetService;
+ ValidateAllProperties();
+
+ PropUpdateByFormulaCommand = RelayCommand.Create(PropUpdateByFormula);
+ }
+
+ public ICommand PropUpdateByFormulaCommand { get; }
+
+ public CustomParametersListVM CustomParamsList { get; }
+ public StringParamSetService StrParamSetService { get; }
+
+ [Required]
+ [RegularExpression(@"^[^\\\/:*?""<>|\[\];~]+$")]
+ public string ParamName {
+ get => _paramName;
+ set => RaiseAndSetIfChanged(ref _paramName, value);
+ }
+
+ public string ParamValueFormula {
+ get => _paramValueFormula;
+ set => RaiseAndSetIfChanged(ref _paramValueFormula, value);
+ }
+
+ public string ParamValue {
+ get => _paramValue;
+ set => RaiseAndSetIfChanged(ref _paramValue, value);
+ }
+
+ private void PropUpdateByFormula(string formulaPropertyName) {
+ StrParamSetService.Set(this, formulaPropertyName, CustomParamsList.SheetSetParams);
+ }
+
+ ///
+ /// В случае изменения имени параметра конфигурации нужно обновить свойства дополнительного параметра
+ ///
+ public void UpdateDueParamNameChange() {
+ StrParamSetService.SetAll(this, CustomParamsList.SheetSetParams);
+ }
+
+ public void UpdateDueParamValueChange(StringParamVM stringParam) {
+ StrParamSetService.SetAll(this, CustomParamsList.SheetSetParams, stringParam);
+ }
+}
diff --git a/src/RevitPackageDocumentation/ViewModels/Configuration/CustomParameters/CustomParametersListVM.cs b/src/RevitPackageDocumentation/ViewModels/Configuration/CustomParameters/CustomParametersListVM.cs
new file mode 100644
index 000000000..f2965e0a4
--- /dev/null
+++ b/src/RevitPackageDocumentation/ViewModels/Configuration/CustomParameters/CustomParametersListVM.cs
@@ -0,0 +1,53 @@
+using System.Collections.ObjectModel;
+using System.Windows.Input;
+
+using dosymep.SimpleServices;
+using dosymep.WPF.Commands;
+
+using RevitPackageDocumentation.Models;
+using RevitPackageDocumentation.ViewModels.Configuration.SheetSetParameters.Parameters;
+using RevitPackageDocumentation.ViewModels.Validation;
+using RevitPackageDocumentation.ViewModels.Validation.Attributes;
+
+namespace RevitPackageDocumentation.ViewModels.Configuration.CustomParameters;
+internal class CustomParametersListVM : ValidatableVM {
+ private readonly ILocalizationService _localizationService;
+ private ObservableCollection _params = [];
+
+ public CustomParametersListVM(
+ ObservableCollection sheetSetParams,
+ StringParamSetService stringParamSetService,
+ ILocalizationService localizationService)
+ : base(localizationService) {
+
+ SheetSetParams = sheetSetParams;
+ StrParamSetService = stringParamSetService;
+ _localizationService = localizationService;
+ ValidateAllProperties();
+
+ AddCustomParameterCommand = RelayCommand.Create(AddCustomParameter);
+ RemoveCustomParameterCommand = RelayCommand.Create(RemoveCustomParameter);
+ }
+
+ public ICommand AddCustomParameterCommand { get; }
+ public ICommand RemoveCustomParameterCommand { get; }
+
+ public ObservableCollection SheetSetParams { get; }
+ public StringParamSetService StrParamSetService { get; }
+
+
+ [ChildHasErrors]
+ public ObservableCollection Params {
+ get => _params;
+ set => RaiseAndSetIfChanged(ref _params, value);
+ }
+
+ private void AddCustomParameter() {
+ var param = new CustomParameterVM(this, StrParamSetService, _localizationService);
+ Params.Add(param);
+ }
+
+ private void RemoveCustomParameter(CustomParameterVM param) {
+ Params.Remove(param);
+ }
+}
diff --git a/src/RevitPackageDocumentation/ViewModels/Configuration/Sheet/BaseParamContainerVM.cs b/src/RevitPackageDocumentation/ViewModels/Configuration/Sheet/BaseParamContainerVM.cs
new file mode 100644
index 000000000..94f70e230
--- /dev/null
+++ b/src/RevitPackageDocumentation/ViewModels/Configuration/Sheet/BaseParamContainerVM.cs
@@ -0,0 +1,124 @@
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Windows.Input;
+
+using Autodesk.Revit.DB;
+
+using dosymep.Revit;
+using dosymep.SimpleServices;
+using dosymep.WPF.Commands;
+
+using RevitPackageDocumentation.Models;
+using RevitPackageDocumentation.ViewModels.Configuration.CustomParameters;
+using RevitPackageDocumentation.ViewModels.Configuration.Sheet.SheetComponents;
+using RevitPackageDocumentation.ViewModels.Configuration.SheetSetParameters.Parameters;
+using RevitPackageDocumentation.ViewModels.FiltrationComboBoxVMs;
+using RevitPackageDocumentation.ViewModels.Validation;
+using RevitPackageDocumentation.ViewModels.Validation.Attributes;
+
+namespace RevitPackageDocumentation.ViewModels.Configuration.Sheet;
+internal abstract class BaseParamContainerVM : ValidatableVM {
+ private CustomParametersListVM _customParamsList;
+ private readonly StringParamSetService _strParamSetService;
+
+ protected BaseParamContainerVM(
+ RevitRepository repository,
+ StringParamSetService stringParamSetService,
+ ObservableCollection sheetSetParams,
+ ILocalizationService localizationService)
+ : base(localizationService) {
+
+ Repository = repository;
+ _strParamSetService = stringParamSetService;
+ SheetSetParams = sheetSetParams;
+ CustomParamsList = new CustomParametersListVM(SheetSetParams, _strParamSetService, localizationService);
+
+ PropUpdateByFormulaCommand = RelayCommand.Create(PropUpdateByFormula);
+ }
+
+ public ICommand PropUpdateByFormulaCommand { get; }
+
+ public ObservableCollection SheetSetParams { get; }
+ public RevitRepository Repository { get; }
+
+ [ChildHasErrors(ErrorMessage = "Validation.ErrorInCustomParams")]
+ public CustomParametersListVM CustomParamsList {
+ get => _customParamsList;
+ set => RaiseAndSetIfChanged(ref _customParamsList, value);
+ }
+
+ private void PropUpdateByFormula(string formulaPropertyName) {
+ _strParamSetService.Set(this, formulaPropertyName, SheetSetParams);
+ }
+
+ ///
+ /// В случае изменения имени параметра конфигурации нужно обновить свойства компонента листа, а также
+ /// его дополнительные параметры
+ ///
+ public void UpdateDueParamNameChange() {
+ _strParamSetService.SetAll(this, SheetSetParams);
+ foreach(var parameter in CustomParamsList.Params) {
+ parameter.UpdateDueParamNameChange();
+ }
+
+ // Проходимся по наследникам и ищем свойства FiltrationComboBoxFilterListVM, в фильтрах вызываем метод обновления
+ this.GetType().GetProperties()
+ .Where(p => p.PropertyType == typeof(FiltrationComboBoxFilterListVM))
+ .Select(p => p.GetValue(this) as FiltrationComboBoxFilterListVM)
+ .Where(filterList => filterList != null)
+ .SelectMany(filterList => filterList.ValueList)
+ .ToList()
+ .ForEach(f => f.UpdateDueParamNameChange());
+
+ if(this is ScheduleViewVM scheduleViewVM) {
+ foreach(var rule in scheduleViewVM.ScheduleFilterList.ScheduleFilterRules) {
+ rule.UpdateDueParamNameChange();
+ }
+ }
+ }
+
+ public void UpdateDueParamValueChange(StringParamVM stringParam) {
+ if(stringParam.StringValue is null) {
+ return;
+ }
+ _strParamSetService.SetAll(this, SheetSetParams, stringParam);
+
+ foreach(var parameter in CustomParamsList.Params) {
+ parameter.UpdateDueParamValueChange(stringParam);
+ }
+
+ // Проходимся по наследникам и ищем свойства FiltrationComboBoxFilterListVM, в фильтрах вызываем метод обновления
+ this.GetType().GetProperties()
+ .Where(p => p.PropertyType == typeof(FiltrationComboBoxFilterListVM))
+ .Select(p => p.GetValue(this) as FiltrationComboBoxFilterListVM)
+ .Where(filterList => filterList != null)
+ .SelectMany(filterList => filterList.ValueList)
+ .ToList()
+ .ForEach(f => f.UpdateDueParamValueChange(stringParam));
+
+ if(this is ScheduleViewVM scheduleViewVM) {
+ foreach(var rule in scheduleViewVM.ScheduleFilterList.ScheduleFilterRules) {
+ rule.UpdateDueParamValueChange(stringParam);
+ }
+ }
+ }
+
+
+ public void SetCustomParams(Element element) {
+ if(element is null) {
+ return;
+ }
+ foreach(var param in CustomParamsList.Params) {
+ // Если параметр существует на экземпляре и он редактируемый
+ if(element.IsExistsParam(param.ParamName) && !element.GetParam(param.ParamName).IsReadOnly) {
+ element.SetParamValue(param.ParamName, param.ParamValue);
+ continue;
+ }
+ // Если параметр существует на типе и он редактируемый
+ var type = Repository.Document.GetElement(element.GetTypeId());
+ if(type != null && type.IsExistsParam(param.ParamName) && !element.GetParam(param.ParamName).IsReadOnly) {
+ type.SetParamValue(param.ParamName, param.ParamValue);
+ }
+ }
+ }
+}
diff --git a/src/RevitPackageDocumentation/ViewModels/Configuration/Sheet/ModuleVM.cs b/src/RevitPackageDocumentation/ViewModels/Configuration/Sheet/ModuleVM.cs
new file mode 100644
index 000000000..125de2bd0
--- /dev/null
+++ b/src/RevitPackageDocumentation/ViewModels/Configuration/Sheet/ModuleVM.cs
@@ -0,0 +1,52 @@
+using System.Collections.ObjectModel;
+
+using dosymep.SimpleServices;
+
+using RevitPackageDocumentation.Models;
+using RevitPackageDocumentation.ViewModels.Configuration.SheetSetParameters.Parameters;
+
+namespace RevitPackageDocumentation.ViewModels.Configuration.Sheet;
+internal abstract class ModuleVM : BaseParamContainerVM {
+ private bool _isModuleCheck = false;
+ private string _moduleName = string.Empty;
+ private string _moduleComment = string.Empty;
+ private string _moduleCode = string.Empty;
+ private string _moduleTypeName = string.Empty;
+
+ protected ModuleVM(
+ RevitRepository repository,
+ StringParamSetService stringParamSetService,
+ ObservableCollection sheetSetParams,
+ ILocalizationService localizationService)
+ : base(repository, stringParamSetService, sheetSetParams, localizationService) {
+ }
+
+ public bool IsModuleCheck {
+ get => _isModuleCheck;
+ set => RaiseAndSetIfChanged(ref _isModuleCheck, value);
+ }
+
+ public string ModuleName {
+ get => _moduleName;
+ set => RaiseAndSetIfChanged(ref _moduleName, value);
+ }
+
+ public string ModuleComment {
+ get => _moduleComment;
+ set => RaiseAndSetIfChanged(ref _moduleComment, value);
+ }
+
+ public string ModuleCode {
+ get => _moduleCode;
+ set => RaiseAndSetIfChanged(ref _moduleCode, value);
+ }
+
+ public string ModuleTypeName {
+ get => _moduleTypeName;
+ set => RaiseAndSetIfChanged(ref _moduleTypeName, value);
+ }
+
+ public abstract void CreateComponent();
+ public abstract bool CanCreateComponent();
+ public abstract void Process(bool processDependent = false);
+}
diff --git a/src/RevitPackageDocumentation/ViewModels/Configuration/Sheet/SheetComponents/CalloutViewVM.cs b/src/RevitPackageDocumentation/ViewModels/Configuration/Sheet/SheetComponents/CalloutViewVM.cs
new file mode 100644
index 000000000..f7ce52ff5
--- /dev/null
+++ b/src/RevitPackageDocumentation/ViewModels/Configuration/Sheet/SheetComponents/CalloutViewVM.cs
@@ -0,0 +1,245 @@
+using System.Collections.ObjectModel;
+using System.ComponentModel.DataAnnotations;
+
+using Autodesk.Revit.DB;
+
+using dosymep.Revit;
+using dosymep.SimpleServices;
+using dosymep.WPF.Commands;
+
+using RevitPackageDocumentation.Models;
+using RevitPackageDocumentation.ViewModels.Configuration.SheetSetParameters.Parameters;
+using RevitPackageDocumentation.ViewModels.FiltrationComboBoxVMs;
+using RevitPackageDocumentation.ViewModels.Validation.Attributes;
+
+namespace RevitPackageDocumentation.ViewModels.Configuration.Sheet.SheetComponents;
+internal class CalloutViewVM : SheetComponentVM {
+ // Ширина фрагмента
+ private readonly double _calloutWidth = UnitUtilsHelper.ConvertToInternalValue(4000);
+
+ // Высота фрагмента
+ private readonly double _calloutHeight = UnitUtilsHelper.ConvertToInternalValue(3000);
+
+ // Отступ между фрагментами в модели
+ private readonly double _viewsOffset = UnitUtilsHelper.ConvertToInternalValue(5000);
+
+ // Отступ между видовыми экранами на листе
+ private readonly double _viewportOffset = UnitUtilsHelper.ConvertToInternalValue(100);
+
+ private string _viewNameFormula = string.Empty;
+ private string _viewName;
+ private ViewFamilyType _viewFamilyType;
+ private ElementType _viewportType;
+ private ViewPlan _viewTemplate;
+ private string _viewCount;
+ private string _viewportNumber;
+ private SelectElemParamVM _selectedSelectElemParam;
+
+ private FiltrationComboBoxFilterListVM _viewFamilyTypeFilter;
+ private FiltrationComboBoxFilterListVM _viewportTypeFilter;
+ private FiltrationComboBoxFilterListVM _viewTemplateFilter;
+
+ public CalloutViewVM(
+ RevitRepository repository,
+ StringParamSetService stringParamSetService,
+ ObservableCollection sheetSetParams,
+ SheetVM sheetVM,
+ ILocalizationService localizationService)
+ : base(repository, stringParamSetService, sheetSetParams, sheetVM, localizationService) {
+ ValidateAllProperties();
+ CreateComponentCommand = RelayCommand.Create(CreateComponent, CanCreateComponent);
+ }
+
+ [Required(ErrorMessage = "Validation.ViewNameIsEmpty")]
+ [RegularExpression(@"^[^\\\/:*?""<>|\[\];~]+$", ErrorMessage = "Validation.ViewNameIsNotCorrect")]
+ public string ViewNameFormula {
+ get => _viewNameFormula;
+ set => RaiseAndSetIfChanged(ref _viewNameFormula, value);
+ }
+
+ public string ViewName {
+ get => _viewName;
+ set => RaiseAndSetIfChanged(ref _viewName, value);
+ }
+
+ [Required(ErrorMessage = "Validation.ViewFamilyTypeIsNull")]
+ public ViewFamilyType ViewFamilyType {
+ get => _viewFamilyType;
+ set => RaiseAndSetIfChanged(ref _viewFamilyType, value);
+ }
+
+ public FiltrationComboBoxFilterListVM ViewFamilyTypeFilter {
+ get => _viewFamilyTypeFilter;
+ set => RaiseAndSetIfChanged(ref _viewFamilyTypeFilter, value);
+ }
+
+ [Required(ErrorMessage = "Validation.ViewportTypeIsNull")]
+ public ElementType ViewportType {
+ get => _viewportType;
+ set => RaiseAndSetIfChanged(ref _viewportType, value);
+ }
+
+ public FiltrationComboBoxFilterListVM ViewportTypeFilter {
+ get => _viewportTypeFilter;
+ set => RaiseAndSetIfChanged(ref _viewportTypeFilter, value);
+ }
+
+ [Required(ErrorMessage = "Validation.ViewTemplateIsNull")]
+ public ViewPlan ViewTemplate {
+ get => _viewTemplate;
+ set => RaiseAndSetIfChanged(ref _viewTemplate, value);
+ }
+
+ public FiltrationComboBoxFilterListVM ViewTemplateFilter {
+ get => _viewTemplateFilter;
+ set => RaiseAndSetIfChanged(ref _viewTemplateFilter, value);
+ }
+
+ [PositiveInteger(ErrorMessage = "Validation.ViewCountIsNotCorrect")]
+ public string ViewCount {
+ get => _viewCount;
+ set => RaiseAndSetIfChanged(ref _viewCount, value);
+ }
+
+ public string ViewportNumber {
+ get => _viewportNumber;
+ set => RaiseAndSetIfChanged(ref _viewportNumber, value);
+ }
+
+ [Required(ErrorMessage = "Validation.SelectedSelectElemParamIsNull")]
+ [ChildHasErrors(ErrorMessage = "Validation.SelectElemParamSelectedElemIsNull")]
+ public SelectElemParamVM SelectedSelectElemParam {
+ get => _selectedSelectElemParam;
+ set => RaiseAndSetIfChanged(ref _selectedSelectElemParam, value);
+ }
+
+
+ public override bool CanCreateComponent() {
+ if(HasErrors) {
+ return false;
+ }
+
+ int index = Sheet.SheetComponents.IndexOf(this);
+ PlanViewVM parentView = null;
+ for(int i = index; i >= 0; i--) {
+ parentView = Sheet.SheetComponents[i] as PlanViewVM;
+
+ if(parentView != null && parentView.IsModuleCheck == true) {
+ break;
+ }
+ }
+ if(parentView is null) {
+ FirstError = LocalizationService.GetLocalizedString("Validation.HasNotPlanView");
+ return false;
+ }
+
+ FirstError = string.Empty;
+ return true;
+ }
+
+ public override void Process(bool processDependent = false) {
+ int.TryParse(ViewCount, out int viewCountAsInt);
+
+ for(int i = 1; i <= viewCountAsInt; i++) {
+ var view = Create(i);
+ var viewPort = Place(view, i);
+ SetCustomParams(viewPort);
+ }
+ }
+
+ public View Create(int number) {
+ string viewName = $"{ViewName}_{number}";
+ var view = Repository.GetViewByName(viewName);
+
+ if(view != null) {
+ return view;
+ }
+
+ try {
+ int index = Sheet.SheetComponents.IndexOf(this);
+ PlanViewVM parentView = default;
+
+ for(int i = index; i >= 0; i--) {
+ parentView = Sheet.SheetComponents[i] as PlanViewVM;
+
+ if(parentView != null) {
+ break;
+ }
+ }
+
+ if(parentView is null || parentView.ViewInstance is null) {
+ return null;
+ }
+
+ // Рассчитываем точки размещения фрагмента относительно центра опалубки
+ var selectedElem = SelectedSelectElemParam.SelectedElem;
+ var bbox = selectedElem.get_BoundingBox(null);
+
+ var calloutStart = (bbox.Min + bbox.Max) / 2 + new XYZ(_viewsOffset * (number - 1), -_viewsOffset, 0);
+ var calloutEnd = calloutStart + new XYZ(_calloutWidth, _calloutHeight, 0);
+
+ view = ViewSection.CreateCallout(
+ Repository.Document,
+ parentView.ViewInstance.Id,
+ ViewFamilyType.Id,
+ calloutStart,
+ calloutEnd);
+ view.Name = viewName;
+ view.ViewTemplateId = ViewTemplate.Id;
+
+ // Необходимо для перезагрузки габаритов видов перед их размещением, т.к. при назначении
+ // секущего диапазона, видимых категорий, шаблона вида могут изменяться габариты вида
+ Repository.Document.Regenerate();
+ } catch(System.Exception) { }
+ return view;
+ }
+
+ public Viewport Place(View view, int number) {
+ var sheetInstance = Sheet.SheetInstance;
+ if(sheetInstance != null
+ && view != null
+ && Viewport.CanAddViewToSheet(Repository.Document, sheetInstance.Id, view.Id)) {
+
+ // Получение габаритов рамки листа
+ if(Repository.GetTitleBlocks(sheetInstance) is not FamilyInstance titleBlock) {
+ return null;
+ }
+ var boundingBoxXYZ = titleBlock.get_BoundingBox(sheetInstance);
+ double titleBlockWidth = boundingBoxXYZ.Max.X - boundingBoxXYZ.Min.X;
+ double titleBlockHeight = boundingBoxXYZ.Max.Y - boundingBoxXYZ.Min.Y;
+
+ double titleBlockMinX = boundingBoxXYZ.Min.X;
+ double titleBlockMinY = boundingBoxXYZ.Min.Y;
+
+ int viewPortNumber = GetLastViewportNumber(0, 100) + 1;
+ var lastViewport = GetLastViewport(vp => vp.GetBoxCenter().Y < 0, true);
+
+ // Получение габаритов видового экрана
+ var viewPort = Viewport.Create(Repository.Document, sheetInstance.Id, view.Id, XYZ.Zero);
+ viewPort.ChangeTypeId(ViewportType.Id);
+
+ var viewportCenter = viewPort.GetBoxCenter();
+ var viewportOutline = viewPort.GetBoxOutline();
+ double viewportHalfWidth = viewportOutline.MaximumPoint.X - viewportCenter.X;
+ double viewportHalfHeight = viewportOutline.MaximumPoint.Y - viewportCenter.Y;
+
+ double correctPositionX = lastViewport is null
+ ? titleBlockMinX + viewportHalfWidth
+ : lastViewport.GetBoxOutline().MaximumPoint.X + viewportHalfWidth;
+
+ var correctPosition = new XYZ(
+ correctPositionX,
+ titleBlockMinY - viewportHalfHeight - _viewportOffset,
+ 0);
+
+ viewPort.SetBoxCenter(correctPosition);
+ viewPort.SetParamValue(BuiltInParameter.VIEWPORT_DETAIL_NUMBER, viewPortNumber.ToString());
+
+#if REVIT_2022_OR_GREATER
+ viewPort.LabelOffset = new XYZ(viewportHalfWidth * 0.9, viewportHalfHeight * 2, 0);
+#endif
+ return viewPort;
+ }
+ return null;
+ }
+}
diff --git a/src/RevitPackageDocumentation/ViewModels/Configuration/Sheet/SheetComponents/LegendViewVM.cs b/src/RevitPackageDocumentation/ViewModels/Configuration/Sheet/SheetComponents/LegendViewVM.cs
new file mode 100644
index 000000000..694b63704
--- /dev/null
+++ b/src/RevitPackageDocumentation/ViewModels/Configuration/Sheet/SheetComponents/LegendViewVM.cs
@@ -0,0 +1,79 @@
+using System.Collections.ObjectModel;
+using System.ComponentModel.DataAnnotations;
+
+using Autodesk.Revit.DB;
+
+using dosymep.SimpleServices;
+using dosymep.WPF.Commands;
+
+using RevitPackageDocumentation.Models;
+using RevitPackageDocumentation.ViewModels.Configuration.SheetSetParameters.Parameters;
+using RevitPackageDocumentation.ViewModels.FiltrationComboBoxVMs;
+
+namespace RevitPackageDocumentation.ViewModels.Configuration.Sheet.SheetComponents;
+internal class LegendViewVM : SheetComponentVM {
+ private View _legendView;
+ private ElementType _viewportType;
+
+ private FiltrationComboBoxFilterListVM _legendViewFilter;
+ private FiltrationComboBoxFilterListVM _viewportTypeFilter;
+
+ public LegendViewVM(
+ RevitRepository repository,
+ StringParamSetService stringParamSetService,
+ ObservableCollection sheetSetParams,
+ SheetVM sheetVM,
+ ILocalizationService localizationService)
+ : base(repository, stringParamSetService, sheetSetParams, sheetVM, localizationService) {
+ ValidateAllProperties();
+ CreateComponentCommand = RelayCommand.Create(CreateComponent, CanCreateComponent);
+ }
+
+ [Required(ErrorMessage = "Validation.LegendViewIsNull")]
+ public View LegendView {
+ get => _legendView;
+ set => RaiseAndSetIfChanged(ref _legendView, value);
+ }
+
+ public FiltrationComboBoxFilterListVM LegendViewFilter {
+ get => _legendViewFilter;
+ set => RaiseAndSetIfChanged(ref _legendViewFilter, value);
+ }
+
+ [Required(ErrorMessage = "Validation.ViewportTypeIsNull")]
+ public ElementType ViewportType {
+ get => _viewportType;
+ set => RaiseAndSetIfChanged(ref _viewportType, value);
+ }
+
+ public FiltrationComboBoxFilterListVM ViewportTypeFilter {
+ get => _viewportTypeFilter;
+ set => RaiseAndSetIfChanged(ref _viewportTypeFilter, value);
+ }
+
+ public override void Process(bool processDependent = false) {
+ var viewPort = Place();
+ SetCustomParams(viewPort);
+ }
+
+ public Viewport Place() {
+ // Проверяем можем ли разместить на листе легенду
+ if(!Viewport.CanAddViewToSheet(Repository.Document, Sheet.SheetInstance.Id, LegendView.Id)) {
+ return null;
+ }
+
+ // Размещаем легенду на листе
+ var position = new XYZ(
+ UnitUtilsHelper.ConvertToInternalValue(-100),
+ UnitUtilsHelper.ConvertToInternalValue(350),
+ 0);
+ var viewPort = Viewport.Create(Repository.Document,
+ Sheet.SheetInstance.Id,
+ LegendView.Id,
+ position);
+
+ // Задание правильного типа видового экрана
+ viewPort.ChangeTypeId(ViewportType.Id);
+ return viewPort;
+ }
+}
diff --git a/src/RevitPackageDocumentation/ViewModels/Configuration/Sheet/SheetComponents/PlanViewVM.cs b/src/RevitPackageDocumentation/ViewModels/Configuration/Sheet/SheetComponents/PlanViewVM.cs
new file mode 100644
index 000000000..f7b7ce83d
--- /dev/null
+++ b/src/RevitPackageDocumentation/ViewModels/Configuration/Sheet/SheetComponents/PlanViewVM.cs
@@ -0,0 +1,205 @@
+using System;
+using System.Collections.ObjectModel;
+using System.ComponentModel.DataAnnotations;
+
+using Autodesk.Revit.DB;
+
+using dosymep.Revit;
+using dosymep.SimpleServices;
+
+using RevitPackageDocumentation.Models;
+using RevitPackageDocumentation.ViewModels.Configuration.SheetSetParameters.Parameters;
+using RevitPackageDocumentation.ViewModels.FiltrationComboBoxVMs;
+using RevitPackageDocumentation.ViewModels.Validation.Attributes;
+
+namespace RevitPackageDocumentation.ViewModels.Configuration.Sheet.SheetComponents;
+internal class PlanViewVM : SheetComponentVM {
+ private string _viewNameFormula = string.Empty;
+ private string _viewName;
+ private ViewFamilyType _viewFamilyType;
+ private ElementType _viewportType;
+ private ViewPlan _viewTemplate;
+ private string _viewCount;
+ private SelectElemParamVM _selectedSelectElemParam;
+ private ViewPlan _viewInstance;
+
+ private FiltrationComboBoxFilterListVM _viewportTypeFilter;
+ private FiltrationComboBoxFilterListVM _viewFamilyTypeFilter;
+ private FiltrationComboBoxFilterListVM _viewTemplateFilter;
+
+ // Смещение по горизонтали в дюймах слева, для размещаемых компонентов листа требуемое, чтобы они попали на лист
+ private readonly double _titleBlockFrameLeftOffset = UnitUtilsHelper.ConvertToInternalValue(20);
+
+ // Смещение по вертикали в дюймах сверху, для размещаемых компонентов листа требуемое, чтобы они попали на лист
+ private readonly double _titleBlockFrameTopOffset = UnitUtilsHelper.ConvertToInternalValue(15);
+
+ public PlanViewVM(
+ RevitRepository repository,
+ StringParamSetService stringParamSetService,
+ ObservableCollection sheetSetParams,
+ SheetVM sheetVM,
+ ILocalizationService localizationService)
+ : base(repository, stringParamSetService, sheetSetParams, sheetVM, localizationService) {
+ ValidateAllProperties();
+ }
+
+ [Required(ErrorMessage = "Validation.ViewNameIsEmpty")]
+ [RegularExpression(@"^[^\\\/:*?""<>|\[\];~]+$", ErrorMessage = "Validation.ViewNameIsNotCorrect")]
+ public string ViewNameFormula {
+ get => _viewNameFormula;
+ set => RaiseAndSetIfChanged(ref _viewNameFormula, value);
+ }
+
+ public string ViewName {
+ get => _viewName;
+ set => RaiseAndSetIfChanged(ref _viewName, value);
+ }
+
+ [Required(ErrorMessage = "Validation.ViewFamilyTypeIsNull")]
+ public ViewFamilyType ViewFamilyType {
+ get => _viewFamilyType;
+ set => RaiseAndSetIfChanged(ref _viewFamilyType, value);
+ }
+
+ public FiltrationComboBoxFilterListVM ViewFamilyTypeFilter {
+ get => _viewFamilyTypeFilter;
+ set => RaiseAndSetIfChanged(ref _viewFamilyTypeFilter, value);
+ }
+
+ [Required(ErrorMessage = "Validation.ViewportTypeIsNull")]
+ public ElementType ViewportType {
+ get => _viewportType;
+ set => RaiseAndSetIfChanged(ref _viewportType, value);
+ }
+
+ public FiltrationComboBoxFilterListVM ViewportTypeFilter {
+ get => _viewportTypeFilter;
+ set => RaiseAndSetIfChanged(ref _viewportTypeFilter, value);
+ }
+
+ [Required(ErrorMessage = "Validation.ViewTemplateIsNull")]
+ public ViewPlan ViewTemplate {
+ get => _viewTemplate;
+ set => RaiseAndSetIfChanged(ref _viewTemplate, value);
+ }
+
+ public FiltrationComboBoxFilterListVM ViewTemplateFilter {
+ get => _viewTemplateFilter;
+ set => RaiseAndSetIfChanged(ref _viewTemplateFilter, value);
+ }
+
+ [PositiveInteger(ErrorMessage = "Validation.ViewCountIsNotCorrect")]
+ public string ViewCount {
+ get => _viewCount;
+ set => RaiseAndSetIfChanged(ref _viewCount, value);
+ }
+
+ [Required(ErrorMessage = "Validation.SelectedSelectElemParamIsNull")]
+ [ChildHasErrors(ErrorMessage = "Validation.SelectElemParamSelectedElemIsNull")]
+ public SelectElemParamVM SelectedSelectElemParam {
+ get => _selectedSelectElemParam;
+ set => RaiseAndSetIfChanged(ref _selectedSelectElemParam, value);
+ }
+
+ public ViewPlan ViewInstance {
+ get => _viewInstance;
+ set => RaiseAndSetIfChanged(ref _viewInstance, value);
+ }
+
+ public override void Process(bool processDependent = false) {
+ ViewInstance = Create();
+ var viewPort = Place(ViewInstance);
+ SetCustomParams(viewPort);
+ }
+
+ public ViewPlan Create() {
+ var view = Repository.GetViewByName(ViewName) as ViewPlan;
+
+ if(view is null) {
+ try {
+ var selectedElem = SelectedSelectElemParam.SelectedElem;
+ var levelId = selectedElem.LevelId;
+ if(levelId is null) {
+ return null;
+ }
+ double elementFromLevelOffset = selectedElem.GetParamValue(BuiltInParameter.FLOOR_HEIGHTABOVELEVEL_PARAM);
+
+ view = ViewPlan.Create(Repository.Document, ViewFamilyType.Id, levelId);
+ view.Name = ViewName;
+ view.ViewTemplateId = ViewTemplate.Id;
+
+ PlanViewRange viewRange = view.GetViewRange();
+ viewRange.SetOffset(PlanViewPlane.TopClipPlane,
+ elementFromLevelOffset + UnitUtilsHelper.ConvertToInternalValue(500));
+ viewRange.SetOffset(PlanViewPlane.CutPlane,
+ elementFromLevelOffset + UnitUtilsHelper.ConvertToInternalValue(200));
+ viewRange.SetOffset(PlanViewPlane.BottomClipPlane,
+ elementFromLevelOffset + UnitUtilsHelper.ConvertToInternalValue(-500));
+ viewRange.SetOffset(PlanViewPlane.ViewDepthPlane,
+ elementFromLevelOffset + UnitUtilsHelper.ConvertToInternalValue(-500));
+ view.SetViewRange(viewRange);
+
+ // Необходимо для перезагрузки габаритов видов перед их размещением, т.к. при назначении
+ // секущего диапазона, видимых категорий, шаблона вида могут изменяться габариты вида
+ Repository.Document.Regenerate();
+ } catch(Exception) { }
+ }
+ return view;
+ }
+
+ public Viewport Place(ViewPlan view) {
+ var sheetInstance = Sheet.SheetInstance;
+ if(sheetInstance != null
+ && view != null
+ && Viewport.CanAddViewToSheet(Repository.Document, sheetInstance.Id, view.Id)) {
+
+ // Получение габаритов рамки листа
+ if(Repository.GetTitleBlocks(sheetInstance) is not FamilyInstance titleBlock) {
+ return null;
+ }
+ var boundingBoxXYZ = titleBlock.get_BoundingBox(sheetInstance);
+ double titleBlockWidth = boundingBoxXYZ.Max.X - boundingBoxXYZ.Min.X;
+ double titleBlockHeight = boundingBoxXYZ.Max.Y - boundingBoxXYZ.Min.Y;
+
+ double titleBlockMinY = boundingBoxXYZ.Min.Y;
+ double titleBlockMinX = boundingBoxXYZ.Min.X;
+ double titleBlockMaxY = boundingBoxXYZ.Max.Y;
+
+ int viewPortNumber = GetLastViewportNumber(100) + 1;
+ var lastViewportInTitleBlock = GetLastViewport(vp => vp.GetBoxCenter().Y < titleBlockMaxY);
+
+ // Создание видового экрана
+ var viewPort = Viewport.Create(Repository.Document, sheetInstance.Id, view.Id, new XYZ(0, 0, 0));
+ viewPort.ChangeTypeId(ViewportType.Id);
+
+ var viewportCenter = viewPort.GetBoxCenter();
+ var viewportOutline = viewPort.GetBoxOutline();
+ double viewportHalfWidth = viewportOutline.MaximumPoint.X - viewportCenter.X;
+ double viewportHalfHeight = viewportOutline.MaximumPoint.Y - viewportCenter.Y;
+
+ double correctPositionX = titleBlockMinX + _titleBlockFrameLeftOffset + viewportHalfWidth;
+ double correctPositionY = titleBlockMaxY - _titleBlockFrameTopOffset - viewportHalfHeight;
+ if(lastViewportInTitleBlock is not null) {
+ correctPositionY = titleBlockMaxY + viewportHalfHeight;
+
+ var lastViewportAboveTitleBlock = GetLastViewport(vp => vp.GetBoxCenter().Y > titleBlockMaxY);
+ if(lastViewportAboveTitleBlock is not null) {
+ correctPositionX = lastViewportAboveTitleBlock.GetBoxOutline().MaximumPoint.X + viewportHalfWidth;
+ }
+ }
+ var correctPosition = new XYZ(
+ correctPositionX,
+ correctPositionY,
+ 0);
+
+ viewPort.SetBoxCenter(correctPosition);
+ viewPort.SetParamValue(BuiltInParameter.VIEWPORT_DETAIL_NUMBER, viewPortNumber.ToString());
+
+#if REVIT_2022_OR_GREATER
+ viewPort.LabelOffset = new XYZ(viewportHalfWidth * 0.9, viewportHalfHeight * 2, 0);
+#endif
+ return viewPort;
+ }
+ return null;
+ }
+}
diff --git a/src/RevitPackageDocumentation/ViewModels/Configuration/Sheet/SheetComponents/ScheduleViewVM.cs b/src/RevitPackageDocumentation/ViewModels/Configuration/Sheet/SheetComponents/ScheduleViewVM.cs
new file mode 100644
index 000000000..54234b1c2
--- /dev/null
+++ b/src/RevitPackageDocumentation/ViewModels/Configuration/Sheet/SheetComponents/ScheduleViewVM.cs
@@ -0,0 +1,311 @@
+using System.Collections.ObjectModel;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Windows.Input;
+
+using Autodesk.Revit.DB;
+
+using dosymep.SimpleServices;
+using dosymep.WPF.Commands;
+
+using RevitPackageDocumentation.Models;
+using RevitPackageDocumentation.ViewModels.Configuration.SheetSetParameters.Parameters;
+using RevitPackageDocumentation.ViewModels.FiltrationComboBoxVMs;
+using RevitPackageDocumentation.ViewModels.ScheduleFilters;
+using RevitPackageDocumentation.ViewModels.Validation.Attributes;
+
+namespace RevitPackageDocumentation.ViewModels.Configuration.Sheet.SheetComponents;
+internal class ScheduleViewVM : SheetComponentVM {
+ private string _viewNameFormula = string.Empty;
+ private string _viewName;
+ private string _viewColumn;
+ private string _viewCount;
+ private ViewSchedule _referenceSpec;
+ private ScheduleFilterListVM _scheduleFilterList;
+ private ScheduleSheetInstance _viewportInstance;
+
+ private FiltrationComboBoxFilterListVM _referenceSpecFilter;
+
+ // Смещение по горизонтали в футах, для размещаемых на листе спецификациях требуемое, чтобы они попали на лист
+ private readonly double _specViewportRightOffset = UnitUtilsHelper.ConvertToInternalValue(0.77);
+
+ // Смещение по вертикали в футах, для размещаемых компонентов листа требуемое, чтобы они попали на лист
+ private readonly double _specViewportTopOffset = UnitUtilsHelper.ConvertToInternalValue(12);
+
+ // Смещения по вертикали в футах, для размещаемых спецификаций
+ // требуемое, для их корректного взаимного размещения на листе (в случае наличия маленькой шапки спецификации)
+ private readonly double _scheduleTopOffsetSmall = UnitUtilsHelper.ConvertToInternalValue(2.117);
+
+ public ScheduleViewVM(
+ RevitRepository repository,
+ StringParamSetService stringParamSetService,
+ ObservableCollection sheetSetParams,
+ SheetVM sheetVM,
+ ILocalizationService localizationService)
+ : base(repository, stringParamSetService, sheetSetParams, sheetVM, localizationService) {
+ ScheduleFilterList = new ScheduleFilterListVM(this, stringParamSetService, localizationService);
+ ValidateAllProperties();
+
+ CreateComponentCommand = RelayCommand.Create(CreateComponent, CanCreateComponent);
+ SelectReferenceSpecCommand = RelayCommand.Create(SelectReferenceSpec);
+ }
+
+ public ICommand SelectReferenceSpecCommand { get; set; }
+
+ [Required(ErrorMessage = "Validation.ViewNameIsEmpty")]
+ [RegularExpression(@"^[^\\\/:*?""<>|\[\];~]+$", ErrorMessage = "Validation.ViewNameIsNotCorrect")]
+ public string ViewNameFormula {
+ get => _viewNameFormula;
+ set => RaiseAndSetIfChanged(ref _viewNameFormula, value);
+ }
+
+ public string ViewName {
+ get => _viewName;
+ set => RaiseAndSetIfChanged(ref _viewName, value);
+ }
+
+ [Required(ErrorMessage = "Validation.ReferenceSpecIsNull")]
+ public ViewSchedule ReferenceSpec {
+ get => _referenceSpec;
+ set => RaiseAndSetIfChanged(ref _referenceSpec, value);
+ }
+
+ public FiltrationComboBoxFilterListVM ReferenceSpecFilter {
+ get => _referenceSpecFilter;
+ set => RaiseAndSetIfChanged(ref _referenceSpecFilter, value);
+ }
+
+ [PositiveInteger(ErrorMessage = "Validation.ViewColumnIsNotCorrect")]
+ public string ViewColumn {
+ get => _viewColumn;
+ set => RaiseAndSetIfChanged(ref _viewColumn, value);
+ }
+
+ [PositiveInteger(ErrorMessage = "Validation.ViewCountIsNotCorrect")]
+ public string ViewCount {
+ get => _viewCount;
+ set => RaiseAndSetIfChanged(ref _viewCount, value);
+ }
+
+ [ChildHasErrors(ErrorMessage = "Validation.ScheduleFiltersIsNotCorrect")]
+ public ScheduleFilterListVM ScheduleFilterList {
+ get => _scheduleFilterList;
+ set => RaiseAndSetIfChanged(ref _scheduleFilterList, value);
+ }
+
+ public ScheduleSheetInstance ViewportInstance {
+ get => _viewportInstance;
+ set => RaiseAndSetIfChanged(ref _viewportInstance, value);
+ }
+
+ private void SelectReferenceSpec() {
+ ScheduleFilterList.SetSchedule(ReferenceSpec);
+ }
+
+ public override void Process(bool processDependent = false) {
+ var view = Create();
+ ViewportInstance = Place(view);
+ SetCustomParams(view);
+ }
+
+ public ViewSchedule Create() {
+ var view = Repository.GetSpecByName(ViewName);
+ if(view != null) {
+ return view;
+ }
+
+ if(ReferenceSpec is null || !ReferenceSpec.CanViewBeDuplicated(ViewDuplicateOption.Duplicate)) {
+ return null;
+ }
+
+ try {
+ var scheduleId = ReferenceSpec.Duplicate(ViewDuplicateOption.Duplicate);
+ view = Repository.Document.GetElement(scheduleId) as ViewSchedule;
+ if(view != null) {
+ view.Name = ViewName;
+ }
+
+ var definition = view.Definition;
+
+ // Удаляем каждый фильтр
+ ClearAllFilters(definition);
+
+ // Добавляем указанные пользователем фильтры
+ ApplyFilters(definition);
+ } catch(System.Exception) { }
+ return view;
+ }
+
+
+ public ScheduleSheetInstance Place(ViewSchedule view) {
+ var sheetInstance = Sheet.SheetInstance;
+ if(sheetInstance is null || view is null) {
+ return null;
+ }
+
+ // Если видовой экран спецификации уже есть не листе, то не размещаем повторно
+ if(Repository.GetScheduleSheetInstances(sheetInstance)
+ .FirstOrDefault(s => s.ScheduleId == view.Id) is ScheduleSheetInstance scheduleSheetInstance) {
+ return scheduleSheetInstance;
+ }
+
+ // Получение габаритов рамки листа
+ if(Repository.GetTitleBlocks(sheetInstance) is not FamilyInstance titleBlock) {
+ return null;
+ }
+ var titleBlockBB = titleBlock.get_BoundingBox(sheetInstance);
+ double titleBlockWidth = titleBlockBB.Max.X - titleBlockBB.Min.X;
+ double titleBlockHeight = titleBlockBB.Max.Y - titleBlockBB.Min.Y;
+
+ // Изначально размещаем в Zero
+ // Точка вставки у спеки в верхнем левом углу спеки
+ scheduleSheetInstance = ScheduleSheetInstance.Create(Repository.Document, sheetInstance.Id, view.Id, XYZ.Zero);
+
+ var viewportBB = scheduleSheetInstance.get_BoundingBox(sheetInstance);
+ double viewportWidth = viewportBB.Max.X - viewportBB.Min.X;
+ double viewportHeight = viewportBB.Max.Y - viewportBB.Min.Y;
+
+ // Значение по умолчанию = как для первой
+ XYZ ptForPlace = new XYZ(
+ -viewportWidth - _specViewportRightOffset,
+ titleBlockHeight - _specViewportTopOffset,
+ 0);
+
+ // Размещенные спеки на листе
+ var placedScheduleComponents = Sheet.SheetComponents
+ .OfType()
+ .Where(c => c.ViewportInstance is not null)
+ .ToList();
+
+ // Если на листе еще не было размещено спек
+ if(placedScheduleComponents.Count == 0) {
+ // Присваиваем новую точку
+ scheduleSheetInstance.Point = ptForPlace;
+ return scheduleSheetInstance;
+ }
+
+ // Размещенные спеки на листе из этой колонки
+ var currentColumn = placedScheduleComponents.Where(c => c.ViewColumn.Equals(ViewColumn)).ToList();
+
+ if(currentColumn.Count > 0) {
+ // Если в этой колонке есть спеки - min последней
+ var prevViewport = currentColumn
+ .Select(v => v.ViewportInstance)
+ .OrderBy(v => v.get_BoundingBox(sheetInstance).Min.Y)
+ .First();
+ ptForPlace = new XYZ(
+ prevViewport.Point.X,
+ prevViewport.get_BoundingBox(sheetInstance).Min.Y + _scheduleTopOffsetSmall,
+ 0);
+ } else {
+ // Если в этой колонки нет спек - х = min х предыдущей колонки
+ ptForPlace = new XYZ(placedScheduleComponents
+ .Select(c => c.ViewportInstance.get_BoundingBox(sheetInstance).Min)
+ .OrderBy(pt => pt.X)
+ .First()
+ .X - viewportWidth,
+ ptForPlace.Y,
+ ptForPlace.Z);
+ }
+ // Присваиваем новую точку
+ scheduleSheetInstance.Point = ptForPlace;
+ return scheduleSheetInstance;
+ }
+
+ private void ClearAllFilters(ScheduleDefinition definition) {
+ for(int i = definition.GetFilters().Count - 1; i >= 0; i--) {
+ definition.RemoveFilter(i);
+ }
+ }
+
+ private void ApplyFilters(ScheduleDefinition definition) {
+ foreach(var rule in ScheduleFilterList.ScheduleFilterRules) {
+ var filter = CreateFilter(definition, rule);
+ if(filter != null) {
+ definition.AddFilter(filter);
+ }
+ }
+ }
+
+ private ScheduleFilter CreateFilter(ScheduleDefinition definition, ScheduleFilterRuleVM rule) {
+ var filterType = rule.SelectedFilterType.FilterType;
+ if(filterType == ScheduleFilterType.HasValue
+ || filterType == ScheduleFilterType.HasNoValue
+ || filterType == ScheduleFilterType.HasParameter) {
+ // Создаем фильтр для поля по правилу имеет/не имеет значение/параметр
+ return CreateHasFilter(definition, rule);
+ }
+
+ // Пытаемся создать фильтр, где поле является параметром рабочего набора
+ var worksetFilter = CreateWorksetFilter(definition, rule);
+
+ if(worksetFilter is null) {
+ // Создаем фильтр для обычного поля
+ return CreateStandardFilter(definition, rule);
+ } else {
+ return worksetFilter;
+ }
+ }
+
+ private ScheduleFilter CreateWorksetFilter(ScheduleDefinition definition, ScheduleFilterRuleVM rule) {
+ var paramId = definition.GetField(rule.SelectedSpecField.Field.FieldId).ParameterId;
+
+ if(!AreIdsEqual(paramId, Repository.WorksetParamId)) {
+ return null;
+ }
+
+ // Получаем первый рабочий набор, который содержит нужную строку в имени
+ var workset = new FilteredWorksetCollector(Repository.Document)
+ .OfKind(WorksetKind.UserWorkset)
+ .OrderBy(w => w.Name)
+ .FirstOrDefault(w => w.Name.Contains(rule.FilterValue));
+
+ if(workset is null) {
+ return null;
+ }
+
+ return new ScheduleFilter(
+ rule.SelectedSpecField.Field.FieldId,
+ rule.SelectedFilterType.FilterType,
+ workset.Id.IntegerValue);
+ }
+
+ private ScheduleFilter CreateStandardFilter(ScheduleDefinition definition, ScheduleFilterRuleVM rule) {
+ var fieldId = rule.SelectedSpecField.Field.FieldId;
+ var filterType = rule.SelectedFilterType.FilterType;
+ string filterValue = rule.FilterValue;
+
+ if(definition.CanFilterBySubstring(fieldId)) {
+ return new ScheduleFilter(
+ fieldId,
+ filterType,
+ filterValue);
+ } else {
+ if(int.TryParse(filterValue, out int value)) {
+ return new ScheduleFilter(
+ fieldId,
+ filterType,
+ value);
+ }
+ }
+ return null;
+ }
+
+ private ScheduleFilter CreateHasFilter(ScheduleDefinition definition, ScheduleFilterRuleVM rule) {
+ var fieldId = rule.SelectedSpecField.Field.FieldId;
+ var filterType = rule.SelectedFilterType.FilterType;
+
+ if(definition.CanFilterByParameterExistence(fieldId) || definition.CanFilterByValuePresence(fieldId)) {
+ return new ScheduleFilter(fieldId, filterType);
+ }
+ return null;
+ }
+
+ private bool AreIdsEqual(ElementId id1, ElementId id2) {
+#if REVIT_2024_OR_GREATER
+ return id1.Value == id2.Value;
+#else
+ return id1.IntegerValue == id2.IntegerValue;
+#endif
+ }
+}
diff --git a/src/RevitPackageDocumentation/ViewModels/Configuration/Sheet/SheetComponents/SectionViewVM.cs b/src/RevitPackageDocumentation/ViewModels/Configuration/Sheet/SheetComponents/SectionViewVM.cs
new file mode 100644
index 000000000..c0baf07db
--- /dev/null
+++ b/src/RevitPackageDocumentation/ViewModels/Configuration/Sheet/SheetComponents/SectionViewVM.cs
@@ -0,0 +1,215 @@
+using System.Collections.ObjectModel;
+using System.ComponentModel.DataAnnotations;
+
+using Autodesk.Revit.DB;
+
+using dosymep.Bim4Everyone;
+using dosymep.Revit;
+using dosymep.SimpleServices;
+using dosymep.WPF.Commands;
+
+using RevitPackageDocumentation.Models;
+using RevitPackageDocumentation.ViewModels.Configuration.SheetSetParameters.Parameters;
+using RevitPackageDocumentation.ViewModels.FiltrationComboBoxVMs;
+using RevitPackageDocumentation.ViewModels.Validation.Attributes;
+
+namespace RevitPackageDocumentation.ViewModels.Configuration.Sheet.SheetComponents;
+internal class SectionViewVM : SheetComponentVM {
+ private string _viewNameFormula = string.Empty;
+ private string _viewName;
+ private ViewFamilyType _viewFamilyType;
+ private ElementType _viewportType;
+ private ViewSection _viewTemplate;
+ private string _viewCount;
+ private SelectElemParamVM _selectedSelectElemParam;
+
+ private FiltrationComboBoxFilterListVM _viewFamilyTypeFilter;
+ private FiltrationComboBoxFilterListVM _viewportTypeFilter;
+ private FiltrationComboBoxFilterListVM _viewTemplateFilter;
+
+ // Расстояние до дальней секущей плоскости сечения
+ private readonly double _viewDepth = UnitUtilsHelper.ConvertToInternalValue(3000);
+
+ // Ширина подрезки вида
+ private readonly double _viewWidth = UnitUtilsHelper.ConvertToInternalValue(3000);
+
+ // Высота подрезки вида
+ private readonly double _viewHeight = UnitUtilsHelper.ConvertToInternalValue(1500);
+
+ // Отступ между сечениями в модели
+ private readonly double _viewsOffset = UnitUtilsHelper.ConvertToInternalValue(1500);
+
+ // Отступ между видовыми экранами на листе
+ private readonly double _viewportOffset = UnitUtilsHelper.ConvertToInternalValue(200);
+
+ public SectionViewVM(
+ RevitRepository repository,
+ StringParamSetService stringParamSetService,
+ ObservableCollection sheetSetParams,
+ SheetVM sheetVM,
+ ILocalizationService localizationService)
+ : base(repository, stringParamSetService, sheetSetParams, sheetVM, localizationService) {
+ ValidateAllProperties();
+ CreateComponentCommand = RelayCommand.Create(CreateComponent, CanCreateComponent);
+ }
+
+ [Required(ErrorMessage = "Validation.ViewNameIsEmpty")]
+ [RegularExpression(@"^[^\\\/:*?""<>|\[\];~]+$", ErrorMessage = "Validation.ViewNameIsNotCorrect")]
+ public string ViewNameFormula {
+ get => _viewNameFormula;
+ set => RaiseAndSetIfChanged(ref _viewNameFormula, value);
+ }
+
+ public string ViewName {
+ get => _viewName;
+ set => RaiseAndSetIfChanged(ref _viewName, value);
+ }
+
+ [Required(ErrorMessage = "Validation.ViewFamilyTypeIsNull")]
+ public ViewFamilyType ViewFamilyType {
+ get => _viewFamilyType;
+ set => RaiseAndSetIfChanged(ref _viewFamilyType, value);
+ }
+
+ public FiltrationComboBoxFilterListVM ViewFamilyTypeFilter {
+ get => _viewFamilyTypeFilter;
+ set => RaiseAndSetIfChanged(ref _viewFamilyTypeFilter, value);
+ }
+
+ [Required(ErrorMessage = "Validation.ViewportTypeIsNull")]
+ public ElementType ViewportType {
+ get => _viewportType;
+ set => RaiseAndSetIfChanged(ref _viewportType, value);
+ }
+
+ public FiltrationComboBoxFilterListVM ViewportTypeFilter {
+ get => _viewportTypeFilter;
+ set => RaiseAndSetIfChanged(ref _viewportTypeFilter, value);
+ }
+
+ [Required(ErrorMessage = "Validation.ViewTemplateIsNull")]
+ public ViewSection ViewTemplate {
+ get => _viewTemplate;
+ set => RaiseAndSetIfChanged(ref _viewTemplate, value);
+ }
+
+ public FiltrationComboBoxFilterListVM ViewTemplateFilter {
+ get => _viewTemplateFilter;
+ set => RaiseAndSetIfChanged(ref _viewTemplateFilter, value);
+ }
+
+ [PositiveInteger(ErrorMessage = "Validation.ViewCountIsNotCorrect")]
+ public string ViewCount {
+ get => _viewCount;
+ set => RaiseAndSetIfChanged(ref _viewCount, value);
+ }
+
+ [Required(ErrorMessage = "Validation.SelectedSelectElemParamIsNull")]
+ [ChildHasErrors(ErrorMessage = "Validation.SelectElemParamSelectedElemIsNull")]
+ public SelectElemParamVM SelectedSelectElemParam {
+ get => _selectedSelectElemParam;
+ set => RaiseAndSetIfChanged(ref _selectedSelectElemParam, value);
+ }
+
+ public override void Process(bool processDependent = false) {
+ int.TryParse(ViewCount, out int viewCountAsInt);
+
+ for(int i = 1; i <= viewCountAsInt; i++) {
+ var view = Create(i);
+ var viewPort = Place(view, i);
+ SetCustomParams(viewPort);
+ }
+ }
+
+ public ViewSection Create(int number) {
+ var viewName = $"{ViewName}_{number}";
+ var view = Repository.GetViewByName(viewName) as ViewSection;
+
+ if(view != null) {
+ return view;
+ }
+
+ try {
+ var selectedElem = SelectedSelectElemParam.SelectedElem;
+ var bbox = selectedElem.get_BoundingBox(null);
+
+ // Ориентируем взгляд вдоль оси X (вправо вдоль Y, вверх вдоль Z)
+ var t = Transform.Identity;
+ t.Origin = (bbox.Min + bbox.Max) / 2 + new XYZ(_viewsOffset * (number - 1), 0, 0);
+ t.BasisX = XYZ.BasisY;
+ t.BasisY = XYZ.BasisZ;
+ t.BasisZ = XYZ.BasisX;
+
+ var sectionBox = new BoundingBoxXYZ {
+ Transform = t,
+ Min = new XYZ(
+ -_viewWidth / 2,
+ -_viewHeight / 2,
+ 0),
+ Max = new XYZ(
+ _viewWidth / 2,
+ _viewHeight / 2,
+ _viewDepth)
+ };
+
+ view = ViewSection.CreateSection(Repository.Document, ViewFamilyType.Id, sectionBox);
+ view.Name = viewName;
+ view.ViewTemplateId = ViewTemplate.Id;
+ view.SetParamValue(BuiltInParameter.SECTION_COARSER_SCALE_PULLDOWN_METRIC, 100);
+
+ // Необходимо для перезагрузки габаритов видов перед их размещением, т.к. при назначении
+ // секущего диапазона, видимых категорий, шаблона вида могут изменяться габариты вида
+ Repository.Document.Regenerate();
+ } catch(System.Exception) { }
+ return view;
+ }
+
+ public Viewport Place(ViewSection view, int number) {
+ var sheetInstance = Sheet.SheetInstance;
+ if(sheetInstance != null
+ && view != null
+ && Viewport.CanAddViewToSheet(Repository.Document, sheetInstance.Id, view.Id)) {
+
+ // Получение габаритов рамки листа
+ if(Repository.GetTitleBlocks(sheetInstance) is not FamilyInstance titleBlock) {
+ return null;
+ }
+ var boundingBoxXYZ = titleBlock.get_BoundingBox(sheetInstance);
+ double titleBlockWidth = boundingBoxXYZ.Max.X - boundingBoxXYZ.Min.X;
+ double titleBlockHeight = boundingBoxXYZ.Max.Y - boundingBoxXYZ.Min.Y;
+
+ double titleBlockMinX = boundingBoxXYZ.Min.X;
+ double titleBlockMinY = boundingBoxXYZ.Min.Y;
+
+ int viewPortNumber = GetLastViewportNumber(0, 100) + 1;
+ var lastViewport = GetLastViewport(vp => vp.GetBoxCenter().Y < 0);
+
+ // Создание видового экрана
+ var viewPort = Viewport.Create(Repository.Document, sheetInstance.Id, view.Id, XYZ.Zero);
+ viewPort.ChangeTypeId(ViewportType.Id);
+
+ var viewportCenter = viewPort.GetBoxCenter();
+ var viewportOutline = viewPort.GetBoxOutline();
+ double viewportHalfWidth = viewportOutline.MaximumPoint.X - viewportCenter.X;
+ double viewportHalfHeight = viewportOutline.MaximumPoint.Y - viewportCenter.Y;
+
+ double correctPositionX = lastViewport is null
+ ? titleBlockMinX + viewportHalfWidth
+ : lastViewport.GetBoxOutline().MaximumPoint.X + viewportHalfWidth;
+
+ var correctPosition = new XYZ(
+ correctPositionX,
+ titleBlockMinY - viewportHalfHeight,
+ 0);
+
+ viewPort.SetBoxCenter(correctPosition);
+ viewPort.SetParamValue(BuiltInParameter.VIEWPORT_DETAIL_NUMBER, viewPortNumber.ToString());
+
+#if REVIT_2022_OR_GREATER
+ viewPort.LabelOffset = new XYZ(viewportHalfWidth * 0.9, viewportHalfHeight * 2, 0);
+#endif
+ return viewPort;
+ }
+ return null;
+ }
+}
diff --git a/src/RevitPackageDocumentation/ViewModels/Configuration/Sheet/SheetComponents/SheetComponentVM.cs b/src/RevitPackageDocumentation/ViewModels/Configuration/Sheet/SheetComponents/SheetComponentVM.cs
new file mode 100644
index 000000000..4c281bfbc
--- /dev/null
+++ b/src/RevitPackageDocumentation/ViewModels/Configuration/Sheet/SheetComponents/SheetComponentVM.cs
@@ -0,0 +1,97 @@
+using System;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Linq;
+using System.Windows.Input;
+
+using Autodesk.Revit.DB;
+
+using dosymep.Revit;
+using dosymep.SimpleServices;
+using dosymep.WPF.Commands;
+
+using RevitPackageDocumentation.Models;
+using RevitPackageDocumentation.ViewModels.Configuration.SheetSetParameters.Parameters;
+
+namespace RevitPackageDocumentation.ViewModels.Configuration.Sheet.SheetComponents;
+internal abstract class SheetComponentVM : ModuleVM {
+ private readonly SheetVM _sheet;
+
+ protected SheetComponentVM(
+ RevitRepository repository,
+ StringParamSetService stringParamSetService,
+ ObservableCollection sheetSetParams,
+ SheetVM sheetVM,
+ ILocalizationService localizationService)
+ : base(repository, stringParamSetService, sheetSetParams, localizationService) {
+ _sheet = sheetVM;
+ LocalizationService = localizationService;
+
+ ModuleTypeName = LocalizationService.GetLocalizedString($"Type.{this.GetType().Name}");
+ CreateComponentCommand = RelayCommand.Create(CreateComponent, CanCreateComponent);
+
+ ErrorsChanged += OnErrorsChanged;
+ }
+
+ public ICommand CreateComponentCommand { get; set; }
+
+ protected ILocalizationService LocalizationService { get; }
+
+ public SheetVM Sheet => _sheet;
+
+ private void OnErrorsChanged(object sender, DataErrorsChangedEventArgs e) {
+ // Обновляем состояние команды через CommandManager
+ CommandManager.InvalidateRequerySuggested();
+ }
+
+ ///
+ /// Получает следующий номер видового экрана на листе
+ ///
+ protected int GetLastViewportNumber(int startNumber = int.MinValue, int endNumber = int.MaxValue) {
+ var viewports = Sheet.SheetInstance.GetAllViewports()
+ .Select(id => Repository.Document.GetElement(id) as Viewport)
+ .ToList();
+
+ int lastViewportNumber = startNumber;
+ foreach(var viewport in viewports) {
+ string viewportNumberAsStr = viewport.GetParamValue(BuiltInParameter.VIEWPORT_DETAIL_NUMBER);
+ // Если не число, то не влияет, т.к. плагин будет ставить число
+ if(int.TryParse(viewportNumberAsStr, out int viewportNumberAsInt)) {
+ if(viewportNumberAsInt > lastViewportNumber && viewportNumberAsInt < endNumber) {
+ lastViewportNumber = viewportNumberAsInt;
+ }
+ }
+ }
+ return lastViewportNumber;
+ }
+
+ protected Viewport GetLastViewport(Func viewportFilter = null, bool isCallout = false)
+ where TView : View {
+ return Repository.GetViewports(Sheet.SheetInstance)
+ .Select(viewport => new {
+ Viewport = viewport,
+ View = Repository.Document.GetElement(viewport.ViewId) as View,
+ Center = viewport.GetBoxCenter()
+ })
+ .Where(x => x.View is TView)
+ .Where(x => x.View.IsCallout == isCallout)
+ .Where(x => viewportFilter(x.Viewport))
+ .OrderByDescending(x => x.Center.X)
+ .FirstOrDefault()?.Viewport;
+ }
+
+ public override void CreateComponent() {
+ using var transaction = Repository.Document.StartTransaction(
+ LocalizationService.GetLocalizedString("MainWindow.Title"));
+
+ if(Sheet.SheetInstance is null) {
+ Sheet.Process(false);
+ }
+ Process();
+ transaction.Commit();
+ }
+
+ public override bool CanCreateComponent() {
+ return !HasErrors;
+ }
+}
diff --git a/src/RevitPackageDocumentation/ViewModels/Configuration/Sheet/SheetComponents/TextNoteVM.cs b/src/RevitPackageDocumentation/ViewModels/Configuration/Sheet/SheetComponents/TextNoteVM.cs
new file mode 100644
index 000000000..69f95ae25
--- /dev/null
+++ b/src/RevitPackageDocumentation/ViewModels/Configuration/Sheet/SheetComponents/TextNoteVM.cs
@@ -0,0 +1,90 @@
+using System.Collections.ObjectModel;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+
+using Autodesk.Revit.DB;
+
+using dosymep.SimpleServices;
+using dosymep.WPF.Commands;
+
+using RevitPackageDocumentation.Models;
+using RevitPackageDocumentation.ViewModels.Configuration.SheetSetParameters.Parameters;
+using RevitPackageDocumentation.ViewModels.FiltrationComboBoxVMs;
+using RevitPackageDocumentation.ViewModels.Validation.Attributes;
+
+namespace RevitPackageDocumentation.ViewModels.Configuration.Sheet.SheetComponents;
+internal class TextNoteVM : SheetComponentVM {
+ private string _textFormula = string.Empty;
+ private string _text;
+ private TextNoteType _textType;
+
+ private FiltrationComboBoxFilterListVM _textNoteTypeFilter;
+ private string _textWidth;
+
+ public TextNoteVM(
+ RevitRepository repository,
+ StringParamSetService stringParamSetService,
+ ObservableCollection sheetSetParams,
+ SheetVM sheetVM,
+ ILocalizationService localizationService)
+ : base(repository, stringParamSetService, sheetSetParams, sheetVM, localizationService) {
+ ValidateAllProperties();
+ CreateComponentCommand = RelayCommand.Create(CreateComponent, CanCreateComponent);
+ }
+
+ [Required(ErrorMessage = "Validation.TextNoteTypeIsNull")]
+ public TextNoteType TextNoteType {
+ get => _textType;
+ set => RaiseAndSetIfChanged(ref _textType, value);
+ }
+
+ public FiltrationComboBoxFilterListVM TextNoteTypeFilter {
+ get => _textNoteTypeFilter;
+ set => RaiseAndSetIfChanged(ref _textNoteTypeFilter, value);
+ }
+
+ [PositiveInteger(ErrorMessage = "Validation.TextWidthIsNotCorrect")]
+ public string TextWidth {
+ get => _textWidth;
+ set => RaiseAndSetIfChanged(ref _textWidth, value);
+ }
+
+ [Required(ErrorMessage = "Validation.TextIsEmpty")]
+ public string TextFormula {
+ get => _textFormula;
+ set => RaiseAndSetIfChanged(ref _textFormula, value);
+ }
+
+ public string Text {
+ get => _text;
+ set => RaiseAndSetIfChanged(ref _text, value);
+ }
+
+
+ public override void Process(bool processDependent = false) {
+ var textNote = Place();
+ SetCustomParams(textNote);
+ }
+
+ public TextNote Place() {
+ var sheetInstance = Sheet.SheetInstance;
+
+ // Если текстовое примечание с таким текстом уже существует на листе, то новую не ставим
+ if(Repository.GetTextNotes(sheetInstance)
+ .FirstOrDefault(t => t.Text.Replace("\r\n", "\n").Replace("\r", "\n").Trim()
+ == Text.Replace("\r\n", "\n").Replace("\r", "\n").Trim()) is TextNote textNote) {
+ return textNote;
+ }
+
+ var options = new TextNoteOptions(TextNoteType.Id);
+ var position = new XYZ(
+ UnitUtilsHelper.ConvertToInternalValue(-190),
+ UnitUtilsHelper.ConvertToInternalValue(170),
+ 0);
+ var textNoteInstance = TextNote.Create(Repository.Document, sheetInstance.Id, position, Text, options);
+
+ int textWidthAsInt = int.Parse(TextWidth);
+ textNoteInstance.Width = UnitUtilsHelper.ConvertToInternalValue(textWidthAsInt);
+ return textNoteInstance;
+ }
+}
diff --git a/src/RevitPackageDocumentation/ViewModels/Configuration/Sheet/SheetComponents/TypicalAnnotationVM.cs b/src/RevitPackageDocumentation/ViewModels/Configuration/Sheet/SheetComponents/TypicalAnnotationVM.cs
new file mode 100644
index 000000000..f4ee5be01
--- /dev/null
+++ b/src/RevitPackageDocumentation/ViewModels/Configuration/Sheet/SheetComponents/TypicalAnnotationVM.cs
@@ -0,0 +1,53 @@
+using System.Collections.ObjectModel;
+using System.ComponentModel.DataAnnotations;
+
+using Autodesk.Revit.DB;
+
+using dosymep.SimpleServices;
+using dosymep.WPF.Commands;
+
+using RevitPackageDocumentation.Models;
+using RevitPackageDocumentation.ViewModels.Configuration.SheetSetParameters.Parameters;
+using RevitPackageDocumentation.ViewModels.FiltrationComboBoxVMs;
+
+namespace RevitPackageDocumentation.ViewModels.Configuration.Sheet.SheetComponents;
+internal class TypicalAnnotationVM : SheetComponentVM {
+ private FamilySymbol _annotationType;
+ private FiltrationComboBoxFilterListVM _annotationTypeFilter;
+
+ public TypicalAnnotationVM(
+ RevitRepository repository,
+ StringParamSetService stringParamSetService,
+ ObservableCollection sheetSetParams,
+ SheetVM sheetVM,
+ ILocalizationService localizationService)
+ : base(repository, stringParamSetService, sheetSetParams, sheetVM, localizationService) {
+ ValidateAllProperties();
+
+ CreateComponentCommand = RelayCommand.Create(CreateComponent, CanCreateComponent);
+ }
+
+ [Required(ErrorMessage = "Validation.AnnotationTypeIsNull")]
+ public FamilySymbol AnnotationType {
+ get => _annotationType;
+ set => RaiseAndSetIfChanged(ref _annotationType, value);
+ }
+
+ public FiltrationComboBoxFilterListVM AnnotationTypeFilter {
+ get => _annotationTypeFilter;
+ set => RaiseAndSetIfChanged(ref _annotationTypeFilter, value);
+ }
+
+ public override void Process(bool processDependent = false) {
+ var instance = Place();
+ SetCustomParams(instance);
+ }
+
+ public FamilyInstance Place() {
+ var position = new XYZ(
+ UnitUtilsHelper.ConvertToInternalValue(-100),
+ UnitUtilsHelper.ConvertToInternalValue(250),
+ 0);
+ return Repository.Document.Create.NewFamilyInstance(position, AnnotationType, Sheet.SheetInstance);
+ }
+}
diff --git a/src/RevitPackageDocumentation/ViewModels/Configuration/Sheet/SheetVM.cs b/src/RevitPackageDocumentation/ViewModels/Configuration/Sheet/SheetVM.cs
new file mode 100644
index 000000000..24dbb6b7c
--- /dev/null
+++ b/src/RevitPackageDocumentation/ViewModels/Configuration/Sheet/SheetVM.cs
@@ -0,0 +1,207 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Windows.Input;
+
+using Autodesk.Revit.DB;
+
+using dosymep.Revit;
+using dosymep.SimpleServices;
+using dosymep.WPF.Commands;
+
+using RevitPackageDocumentation.Models;
+using RevitPackageDocumentation.Models.ConfigSerializer;
+using RevitPackageDocumentation.ViewModels.Configuration.Sheet.SheetComponents;
+using RevitPackageDocumentation.ViewModels.Configuration.SheetSetParameters.Parameters;
+using RevitPackageDocumentation.ViewModels.Validation.Attributes;
+
+namespace RevitPackageDocumentation.ViewModels.Configuration.Sheet;
+internal class SheetVM : ModuleVM {
+ private readonly ILocalizationService _localizationService;
+ private readonly IMessageBoxService _messageBoxService;
+ private readonly ISheetSetVMFactory _sheetSetVMFactory;
+ private readonly ISheetSetDataFactory _sheetSetDataFactory;
+ private readonly string _sheetCoefficientParamName = "А";
+ private readonly string _sheetSizeParamName = "х";
+
+ private SheetSetVM _sheetSet;
+ private string _sheetNameFormula = string.Empty;
+ private string _sheetName;
+ private string _sheetSize;
+ private string _sheetCoefficient;
+ private Family _titleBlockFamily;
+ private FamilySymbol _titleBlockType;
+ private ObservableCollection _sheetComponents = [];
+ private List _titleBlockTypes;
+ private ViewSheet _sheetInstance;
+
+ public SheetVM(
+ RevitRepository repository,
+ StringParamSetService stringParamSetService,
+ ObservableCollection sheetSetParams,
+ SheetSetVM sheetSetVM,
+ ILocalizationService localizationService,
+ IMessageBoxService messageBoxService,
+ ISheetSetVMFactory sheetSetVMFactory,
+ ISheetSetDataFactory sheetSetDataFactory)
+ : base(repository, stringParamSetService, sheetSetParams, localizationService) {
+
+ SheetSet = sheetSetVM;
+ _localizationService = localizationService;
+ _messageBoxService = messageBoxService;
+ _sheetSetVMFactory = sheetSetVMFactory;
+ _sheetSetDataFactory = sheetSetDataFactory;
+
+ ValidateAllProperties();
+
+ SelectTitleBlockFamilyCommand = RelayCommand.Create(SelectTitleBlockFamily);
+ CreateSheetCommand = RelayCommand.Create(CreateComponent, CanCreateComponent);
+
+ AddComponentCommand = RelayCommand.Create(AddComponent);
+ RemoveComponentCommand = RelayCommand.Create(RemoveComponent);
+ }
+
+ public ICommand SelectTitleBlockFamilyCommand { get; }
+ public ICommand CreateSheetCommand { get; }
+
+ public ICommand AddComponentCommand { get; }
+ public ICommand RemoveComponentCommand { get; }
+
+ public SheetSetVM SheetSet {
+ get => _sheetSet;
+ set => RaiseAndSetIfChanged(ref _sheetSet, value);
+ }
+
+ [Required(ErrorMessage = "Validation.SheetNameIsEmpty")]
+ [RegularExpression(@"^[^\\\/:*?""<>|\[\];~]+$", ErrorMessage = "Validation.SheetNameIsNotCorrect")]
+ public string SheetNameFormula {
+ get => _sheetNameFormula;
+ set => RaiseAndSetIfChanged(ref _sheetNameFormula, value);
+ }
+
+ public string SheetName {
+ get => _sheetName;
+ set => RaiseAndSetIfChanged(ref _sheetName, value);
+ }
+
+ [Required(ErrorMessage = "Validation.SheetSizeIsEmpty")]
+ [RegularExpression(@"^-?\d+$", ErrorMessage = "Validation.SheetSizeIsNotCorrect")]
+ public string SheetSize {
+ get => _sheetSize;
+ set => RaiseAndSetIfChanged(ref _sheetSize, value);
+ }
+
+ [Required(ErrorMessage = "Validation.SheetCoefficientIsEmpty")]
+ [RegularExpression(@"^-?\d+$", ErrorMessage = "Validation.SheetCoefficientIsNotCorrect")]
+ public string SheetCoefficient {
+ get => _sheetCoefficient;
+ set => RaiseAndSetIfChanged(ref _sheetCoefficient, value);
+ }
+
+ public List TitleBlockTypes {
+ get => _titleBlockTypes;
+ set => RaiseAndSetIfChanged(ref _titleBlockTypes, value);
+ }
+
+ [Required(ErrorMessage = "Validation.TitleBlockFamilyIsNull")]
+ public Family TitleBlockFamily {
+ get => _titleBlockFamily;
+ set => RaiseAndSetIfChanged(ref _titleBlockFamily, value);
+ }
+
+ [Required(ErrorMessage = "Validation.TitleBlockTypeIsNull")]
+ public FamilySymbol TitleBlockType {
+ get => _titleBlockType;
+ set => RaiseAndSetIfChanged(ref _titleBlockType, value);
+ }
+
+ [ChildHasErrors(ErrorMessage = "Validation.ErrorInSheetComponents")]
+ public ObservableCollection SheetComponents {
+ get => _sheetComponents;
+ set => RaiseAndSetIfChanged(ref _sheetComponents, value);
+ }
+
+ public ViewSheet SheetInstance {
+ get => _sheetInstance;
+ set => RaiseAndSetIfChanged(ref _sheetInstance, value);
+ }
+
+ private void SelectTitleBlockFamily() {
+ TitleBlockType = null;
+ SetTitleBlockTypes(TitleBlockFamily);
+ }
+
+ public void SetTitleBlockTypes(Family titleBlockFamily) {
+ TitleBlockTypes = titleBlockFamily
+ ?.GetFamilySymbolIds()
+ ?.Select(id => Repository.Document.GetElement(id) as FamilySymbol)
+ ?.ToList();
+ }
+
+ internal void RemoveComponent(SheetComponentVM sheetComponent) {
+ if(sheetComponent != null && SheetComponents.Contains(sheetComponent)) {
+ SheetComponents.Remove(sheetComponent);
+ }
+ }
+
+ private void AddComponent(ComponentTypeItem selectedComponentType) {
+ if(selectedComponentType?.ComponentType == null)
+ return;
+
+ try {
+ var componentData = _sheetSetDataFactory.CreateComponentData(selectedComponentType.ComponentType);
+ if(componentData == null)
+ return;
+
+ var component = _sheetSetVMFactory.CreateComponentVM(SheetSet, this, componentData);
+ component.IsModuleCheck = true;
+ SheetComponents.Add(component);
+ } catch(Exception) {
+ _messageBoxService.Show("An error occurred while adding the component!", "Error");
+ }
+ }
+
+ public override void CreateComponent() {
+ using var transaction = Repository.Document.StartTransaction(
+ _localizationService.GetLocalizedString("MainWindow.Title"));
+
+ Process(true);
+ transaction.Commit();
+ }
+
+ public override bool CanCreateComponent() {
+ return !HasErrors;
+ }
+
+ public override void Process(bool processDependent) {
+ SheetInstance = null;
+ SheetInstance = Repository.GetSheetByName(SheetName);
+
+ if(SheetInstance is null) {
+ try {
+ SheetInstance = ViewSheet.Create(Repository.Document, TitleBlockType.Id);
+ SheetInstance.Name = SheetName;
+
+ var titleBlock = Repository.GetTitleBlocks(SheetInstance);
+
+ double.TryParse(SheetSize, out double sheetSize);
+ titleBlock.LookupParameter(_sheetSizeParamName).Set(sheetSize);
+
+ double.TryParse(SheetCoefficient, out double sheetCoefficient);
+ titleBlock.LookupParameter(_sheetCoefficientParamName).Set(sheetCoefficient);
+
+ SetCustomParams(SheetInstance);
+
+ Repository.Document.Regenerate();
+ } catch(Exception) { }
+ }
+
+ if(processDependent) {
+ foreach(var component in SheetComponents.Where(c => c.IsModuleCheck).ToList()) {
+ component.Process();
+ }
+ }
+ }
+}
diff --git a/src/RevitPackageDocumentation/ViewModels/Configuration/SheetSetParameters/Parameters/PluginParamVM.cs b/src/RevitPackageDocumentation/ViewModels/Configuration/SheetSetParameters/Parameters/PluginParamVM.cs
new file mode 100644
index 000000000..e6e30ecb5
--- /dev/null
+++ b/src/RevitPackageDocumentation/ViewModels/Configuration/SheetSetParameters/Parameters/PluginParamVM.cs
@@ -0,0 +1,55 @@
+using System.ComponentModel.DataAnnotations;
+using System.Windows.Input;
+
+using dosymep.SimpleServices;
+using dosymep.WPF.Commands;
+
+using RevitPackageDocumentation.ViewModels.Validation;
+
+namespace RevitPackageDocumentation.ViewModels.Configuration.SheetSetParameters.Parameters;
+internal abstract class PluginParamVM : ValidatableVM {
+ private string _paramName;
+ private string _paramComment;
+
+ protected PluginParamVM(
+ SheetSetParametersListVM sheetSetParamsList,
+ string paramName,
+ string paramComment,
+ ILocalizationService localizationService)
+ : base(localizationService) {
+
+ SheetSetParamsList = sheetSetParamsList;
+ ParamName = paramName ?? string.Empty;
+ ParamComment = paramComment ?? string.Empty;
+
+ ParamNameChangeCommand = RelayCommand.Create(ParamNameChange);
+ ParamValueChangeCommand = RelayCommand.Create(ParamValueChange);
+ }
+
+ public ICommand ParamNameChangeCommand { get; }
+ public ICommand ParamValueChangeCommand { get; }
+
+ public SheetSetParametersListVM SheetSetParamsList { get; }
+
+
+ [Required(ErrorMessage = "Validation.ParamNameIsEmpty")]
+ public string ParamName {
+ get => _paramName;
+ set => RaiseAndSetIfChanged(ref _paramName, value);
+ }
+
+ public string ParamComment {
+ get => _paramComment;
+ set => RaiseAndSetIfChanged(ref _paramComment, value);
+ }
+
+ private void ParamNameChange(PluginParamVM pluginParam) {
+ if(!HasErrors) {
+ SheetSetParamsList.SheetSet.UpdateDueParamNameChange(pluginParam);
+ }
+ }
+
+ private void ParamValueChange(PluginParamVM pluginParam) {
+ SheetSetParamsList.SheetSet.UpdateDueParamValueChange(pluginParam);
+ }
+}
diff --git a/src/RevitPackageDocumentation/ViewModels/Configuration/SheetSetParameters/Parameters/SelectElemParamVM.cs b/src/RevitPackageDocumentation/ViewModels/Configuration/SheetSetParameters/Parameters/SelectElemParamVM.cs
new file mode 100644
index 000000000..ec89fb76c
--- /dev/null
+++ b/src/RevitPackageDocumentation/ViewModels/Configuration/SheetSetParameters/Parameters/SelectElemParamVM.cs
@@ -0,0 +1,25 @@
+using System.ComponentModel.DataAnnotations;
+
+using Autodesk.Revit.DB;
+
+using dosymep.SimpleServices;
+
+namespace RevitPackageDocumentation.ViewModels.Configuration.SheetSetParameters.Parameters;
+internal class SelectElemParamVM : PluginParamVM {
+ private Element _selectedElem;
+
+ public SelectElemParamVM(
+ SheetSetParametersListVM sheetSetParamsList,
+ string paramName,
+ string paramComment,
+ ILocalizationService localizationService)
+ : base(sheetSetParamsList, paramName, paramComment, localizationService) {
+ ValidateAllProperties();
+ }
+
+ [Required]
+ public Element SelectedElem {
+ get => _selectedElem;
+ set => RaiseAndSetIfChanged(ref _selectedElem, value);
+ }
+}
diff --git a/src/RevitPackageDocumentation/ViewModels/Configuration/SheetSetParameters/Parameters/StringParamVM.cs b/src/RevitPackageDocumentation/ViewModels/Configuration/SheetSetParameters/Parameters/StringParamVM.cs
new file mode 100644
index 000000000..a62ad9ca2
--- /dev/null
+++ b/src/RevitPackageDocumentation/ViewModels/Configuration/SheetSetParameters/Parameters/StringParamVM.cs
@@ -0,0 +1,22 @@
+using dosymep.SimpleServices;
+
+namespace RevitPackageDocumentation.ViewModels.Configuration.SheetSetParameters.Parameters;
+internal class StringParamVM : PluginParamVM {
+ private string _stringValue;
+
+ public StringParamVM(
+ SheetSetParametersListVM sheetSetParamsList,
+ string paramName,
+ string paramComment,
+ string stringValue,
+ ILocalizationService localizationService)
+ : base(sheetSetParamsList, paramName, paramComment, localizationService) {
+ StringValue = stringValue ?? string.Empty;
+ ValidateAllProperties();
+ }
+
+ public string StringValue {
+ get => _stringValue;
+ set => RaiseAndSetIfChanged(ref _stringValue, value);
+ }
+}
diff --git a/src/RevitPackageDocumentation/ViewModels/Configuration/SheetSetParameters/SheetSetParametersListVM.cs b/src/RevitPackageDocumentation/ViewModels/Configuration/SheetSetParameters/SheetSetParametersListVM.cs
new file mode 100644
index 000000000..6de5fe343
--- /dev/null
+++ b/src/RevitPackageDocumentation/ViewModels/Configuration/SheetSetParameters/SheetSetParametersListVM.cs
@@ -0,0 +1,106 @@
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Windows.Input;
+
+using dosymep.SimpleServices;
+using dosymep.WPF.Commands;
+
+using RevitPackageDocumentation.Models;
+using RevitPackageDocumentation.Models.ConfigSerializer;
+using RevitPackageDocumentation.ViewModels.Configuration.SheetSetParameters.Parameters;
+using RevitPackageDocumentation.ViewModels.Validation;
+using RevitPackageDocumentation.ViewModels.Validation.Attributes;
+
+namespace RevitPackageDocumentation.ViewModels.Configuration.SheetSetParameters;
+internal class SheetSetParametersListVM : ValidatableVM {
+ private readonly IMessageBoxService _messageBoxService;
+ private readonly ISheetSetVMFactory _sheetSetVMFactory;
+ private readonly ISheetSetDataFactory _sheetSetDataFactory;
+
+ private ObservableCollection _params = [];
+ private ObservableCollection _selectElemParams = [];
+
+ public SheetSetParametersListVM(
+ SheetSetVM sheetSet,
+ IMessageBoxService messageBoxService,
+ ISheetSetVMFactory sheetSetVMFactory,
+ ISheetSetDataFactory sheetSetDataFactory,
+ ILocalizationService localizationService)
+ : base(localizationService) {
+ SheetSet = sheetSet;
+ _messageBoxService = messageBoxService;
+ _sheetSetVMFactory = sheetSetVMFactory;
+ _sheetSetDataFactory = sheetSetDataFactory;
+ ValidateAllProperties();
+
+ AddSheetSetParamCommand = RelayCommand.Create(AddSheetSetParam);
+ RemoveSheetSetParamCommand = RelayCommand.Create(RemoveSheetSetParam);
+ }
+
+ public ICommand AddSheetSetParamCommand { get; }
+ public ICommand RemoveSheetSetParamCommand { get; }
+
+ public SheetSetVM SheetSet { get; }
+
+
+ [ChildHasErrors(ErrorMessage = "Validation.ErrorInSheetSetParams")]
+ public ObservableCollection Params {
+ get => _params;
+ set => RaiseAndSetIfChanged(ref _params, value);
+ }
+
+ public ObservableCollection SelectElemParams {
+ get => _selectElemParams;
+ set => RaiseAndSetIfChanged(ref _selectElemParams, value);
+ }
+
+ private void AddSheetSetParam(ComponentTypeItem selectedSheetSetParamType) {
+ if(selectedSheetSetParamType?.ComponentType == null)
+ return;
+
+ try {
+ var paramData = _sheetSetDataFactory.CreatePluginParamData(selectedSheetSetParamType.ComponentType);
+ if(paramData == null)
+ return;
+
+ AddSheetSetParam(paramData);
+ } catch(System.Exception) {
+ _messageBoxService.Show("An error occurred while adding the parameter!", "Error");
+ }
+ }
+
+ public void AddSheetSetParam(PluginParamData paramData) {
+ var parameter = _sheetSetVMFactory.CreateParamVM(this, paramData);
+ Params.Add(parameter);
+ if(parameter is SelectElemParamVM selectParam) {
+ SelectElemParams.Add(selectParam);
+ }
+ }
+
+
+ private void RemoveSheetSetParam(PluginParamVM pluginParam) {
+ // Удаляем из списка параметров выбора
+ if(pluginParam != null && SelectElemParams.Contains(pluginParam) && pluginParam is SelectElemParamVM selectParam) {
+ // Проходимся по каждому компоненту, и если в них есть параметр SelectedSelectElemParam
+ // и его значение pluginParam, то ставим null (у не выбранных листов он сам не сбрасывает)
+ SheetSet.SheetList
+ .SelectMany(s => s.SheetComponents)
+ .Where(component => {
+ var prop = component.GetType().GetProperty("SelectedSelectElemParam");
+ return prop != null && prop.GetValue(component) == selectParam;
+ })
+ .ToList()
+ .ForEach(component => {
+ var prop = component.GetType().GetProperty("SelectedSelectElemParam");
+ prop?.SetValue(component, null);
+ });
+
+ SelectElemParams.Remove(selectParam);
+ }
+ // Удаляем из общего списка параметров
+ if(pluginParam != null && Params.Contains(pluginParam)) {
+ Params.Remove(pluginParam);
+ }
+ SheetSet.ValidateAllSheets();
+ }
+}
diff --git a/src/RevitPackageDocumentation/ViewModels/Configuration/SheetSetVM.cs b/src/RevitPackageDocumentation/ViewModels/Configuration/SheetSetVM.cs
new file mode 100644
index 000000000..93c224014
--- /dev/null
+++ b/src/RevitPackageDocumentation/ViewModels/Configuration/SheetSetVM.cs
@@ -0,0 +1,157 @@
+using System.Collections.ObjectModel;
+using System.Windows.Input;
+
+using dosymep.SimpleServices;
+using dosymep.WPF.Commands;
+using dosymep.WPF.ViewModels;
+
+using RevitPackageDocumentation.Models;
+using RevitPackageDocumentation.Models.ConfigSerializer;
+using RevitPackageDocumentation.ViewModels.Configuration.Sheet;
+using RevitPackageDocumentation.ViewModels.Configuration.SheetSetParameters;
+using RevitPackageDocumentation.ViewModels.Configuration.SheetSetParameters.Parameters;
+
+namespace RevitPackageDocumentation.ViewModels.Configuration;
+internal class SheetSetVM : BaseViewModel {
+ private readonly RevitRepository _revitRepository;
+ private readonly ILocalizationService _localizationService;
+ private readonly IMessageBoxService _messageBoxService;
+ private readonly ISheetSetVMFactory _sheetSetVMFactory;
+ private readonly ISheetSetDataFactory _sheetSetDataFactory;
+ private readonly StringParamSetService _stringParamSetService;
+
+ private string _name;
+ private ObservableCollection _sheetList = [];
+ private SheetVM _selectedSheet;
+ private SheetSetParametersListVM _sheetSetParams;
+
+ public SheetSetVM(
+ RevitRepository revitRepository,
+ ILocalizationService localizationService,
+ IMessageBoxService messageBoxService,
+ ISheetSetVMFactory sheetSetVMFactory,
+ ISheetSetDataFactory sheetSetDataFactory,
+ StringParamSetService stringParamSetService) {
+
+ _revitRepository = revitRepository;
+ _localizationService = localizationService;
+ _messageBoxService = messageBoxService;
+ _sheetSetVMFactory = sheetSetVMFactory;
+ _sheetSetDataFactory = sheetSetDataFactory;
+ _stringParamSetService = stringParamSetService;
+
+ AddSheetCommand = RelayCommand.Create(AddSheet);
+ RemoveSheetCommand = RelayCommand.Create(RemoveSheet);
+ }
+
+ public ICommand AddSheetCommand { get; }
+ public ICommand RemoveSheetCommand { get; }
+
+ public string Name {
+ get => _name;
+ set => RaiseAndSetIfChanged(ref _name, value);
+ }
+
+ public ObservableCollection SheetList {
+ get => _sheetList;
+ set => RaiseAndSetIfChanged(ref _sheetList, value);
+ }
+
+ public SheetVM SelectedSheet {
+ get => _selectedSheet;
+ set => RaiseAndSetIfChanged(ref _selectedSheet, value);
+ }
+
+ public SheetSetParametersListVM SheetSetParams {
+ get => _sheetSetParams;
+ set => RaiseAndSetIfChanged(ref _sheetSetParams, value);
+ }
+
+ public void ValidateAllSheets() {
+ foreach(var sheet in SheetList) {
+ foreach(var component in sheet.SheetComponents) {
+ component.CanCreateComponent();
+ }
+ sheet.CanCreateComponent();
+ }
+ }
+
+ internal void AddSheet() {
+ SheetList.Add(
+ new SheetVM(
+ _revitRepository,
+ _stringParamSetService,
+ SheetSetParams.Params,
+ this,
+ _localizationService,
+ _messageBoxService,
+ _sheetSetVMFactory,
+ _sheetSetDataFactory) {
+ IsModuleCheck = true,
+ ModuleName = "Новый лист",
+ SheetSize = "1",
+ SheetCoefficient = "1",
+ });
+ }
+
+ internal void RemoveSheet(SheetVM sheet) {
+ if(sheet != null && SheetList.Contains(sheet)) {
+ if(SelectedSheet == sheet) {
+ SelectedSheet = null;
+ }
+ SheetList.Remove(sheet);
+ }
+ }
+
+ public void UpdateOnInitialization() {
+ foreach(var sheet in SheetList) {
+ sheet.UpdateDueParamNameChange();
+ foreach(var component in sheet.SheetComponents) {
+ component.UpdateDueParamNameChange();
+ }
+ }
+ }
+
+ ///
+ /// В случае изменения имени параметра нужно обойти все листы и их компоненты, и обновить привязки
+ ///
+ public void UpdateDueParamNameChange(PluginParamVM pluginParam) {
+ if(pluginParam is not StringParamVM stringParam) {
+ return;
+ }
+ foreach(var sheet in SheetList) {
+ sheet.UpdateDueParamNameChange();
+ foreach(var component in sheet.SheetComponents) {
+ component.UpdateDueParamNameChange();
+ }
+ }
+ }
+
+ public void UpdateDueParamValueChange(PluginParamVM pluginParam) {
+ if(pluginParam is SelectElemParamVM selectElemParam) {
+ foreach(var sheet in SheetList) {
+ sheet.ValidateAllProperties();
+ foreach(var component in sheet.SheetComponents) {
+ component.ValidateAllProperties();
+ }
+ }
+ return;
+ }
+
+ if(pluginParam is not StringParamVM stringParam) {
+ foreach(var sheet in SheetList) {
+ sheet.CanCreateComponent();
+ foreach(var component in sheet.SheetComponents) {
+ component.CanCreateComponent();
+ }
+ }
+ return;
+ }
+ foreach(var sheet in SheetList) {
+ sheet.UpdateDueParamValueChange(stringParam);
+ foreach(var component in sheet.SheetComponents) {
+ component.UpdateDueParamValueChange(stringParam);
+ }
+ }
+ }
+}
diff --git a/src/RevitPackageDocumentation/ViewModels/Configuration/SheetSetVMFactory.cs b/src/RevitPackageDocumentation/ViewModels/Configuration/SheetSetVMFactory.cs
new file mode 100644
index 000000000..e8b6a9bd2
--- /dev/null
+++ b/src/RevitPackageDocumentation/ViewModels/Configuration/SheetSetVMFactory.cs
@@ -0,0 +1,373 @@
+using System;
+using System.Collections.ObjectModel;
+using System.Data;
+using System.Linq;
+
+using Autodesk.Revit.DB;
+
+using dosymep.SimpleServices;
+
+using RevitPackageDocumentation.Models;
+using RevitPackageDocumentation.Models.ConfigSerializer;
+using RevitPackageDocumentation.ViewModels.Configuration.CustomParameters;
+using RevitPackageDocumentation.ViewModels.Configuration.Sheet;
+using RevitPackageDocumentation.ViewModels.Configuration.Sheet.SheetComponents;
+using RevitPackageDocumentation.ViewModels.Configuration.SheetSetParameters;
+using RevitPackageDocumentation.ViewModels.Configuration.SheetSetParameters.Parameters;
+using RevitPackageDocumentation.ViewModels.FiltrationComboBoxVMs;
+using RevitPackageDocumentation.ViewModels.ScheduleFilters;
+
+namespace RevitPackageDocumentation.ViewModels.Configuration;
+
+internal interface ISheetSetVMFactory {
+ SheetSetVM CreateSheetSetVM(SheetSetData data);
+ SheetVM CreateSheetVM(SheetSetVM sheetSetVM, SheetData data);
+ SheetComponentVM CreateComponentVM(SheetSetVM sheetSetVM, SheetVM sheetVM, SheetComponentData data);
+ PluginParamVM CreateParamVM(SheetSetParametersListVM sheetSetParamsList, PluginParamData data);
+}
+
+internal class SheetSetVMFactory : ISheetSetVMFactory {
+ private readonly RevitRepository _revitRepository;
+ private readonly ILocalizationService _localizationService;
+ private readonly IMessageBoxService _messageBoxService;
+ private readonly ISheetSetDataFactory _sheetSetDataFactory;
+ private readonly StringParamSetService _stringParamSetService;
+
+ public SheetSetVMFactory(
+ RevitRepository revitRepository,
+ ILocalizationService localizationService,
+ IMessageBoxService messageBoxService,
+ ISheetSetDataFactory sheetSetDataFactory,
+ StringParamSetService stringParamSetService) {
+
+ _revitRepository = revitRepository;
+ _localizationService = localizationService;
+ _messageBoxService = messageBoxService;
+ _sheetSetDataFactory = sheetSetDataFactory;
+ _stringParamSetService = stringParamSetService;
+ }
+
+ public SheetSetVM CreateSheetSetVM(SheetSetData data) {
+ if(data == null)
+ throw new ArgumentNullException(nameof(data));
+
+ var sheetSetVM = new SheetSetVM(
+ _revitRepository,
+ _localizationService,
+ _messageBoxService,
+ this,
+ _sheetSetDataFactory,
+ _stringParamSetService) {
+ Name = data.Name
+ };
+
+ sheetSetVM.SheetSetParams = new SheetSetParametersListVM(
+ sheetSetVM,
+ _messageBoxService,
+ this,
+ _sheetSetDataFactory,
+ _localizationService);
+ foreach(var paramData in data.Params) {
+ sheetSetVM.SheetSetParams.AddSheetSetParam(paramData);
+ }
+
+ foreach(var sheetData in data.Sheets) {
+ var sheetVM = CreateSheetVM(sheetSetVM, sheetData);
+ sheetSetVM.SheetList.Add(sheetVM);
+ }
+ sheetSetVM.UpdateOnInitialization();
+ return sheetSetVM;
+ }
+
+ public SheetVM CreateSheetVM(SheetSetVM sheetSetVM, SheetData data) {
+ if(data == null)
+ throw new ArgumentNullException(nameof(data));
+
+ var titleBlockFamily = _revitRepository.TitleBlockFamilies?.FirstOrDefault(v => v.Name.Equals(data.TitleBlockFamilyName));
+
+ var titleBlockTypes = titleBlockFamily
+ ?.GetFamilySymbolIds()
+ ?.Select(id => _revitRepository.Document.GetElement(id) as FamilySymbol)
+ ?.ToList();
+
+ var titleBlockType = titleBlockTypes?.FirstOrDefault(v => v.Name.Equals(data.TitleBlockTypeName));
+
+ var sheetVM = new SheetVM(
+ _revitRepository,
+ _stringParamSetService,
+ sheetSetVM.SheetSetParams.Params,
+ sheetSetVM,
+ _localizationService,
+ _messageBoxService,
+ this,
+ _sheetSetDataFactory) {
+
+ IsModuleCheck = data.IsModuleCheck ?? false,
+ ModuleName = data.ModuleName ?? string.Empty,
+ ModuleComment = data.ModuleComment ?? string.Empty,
+ ModuleCode = "123",
+
+ SheetNameFormula = data.SheetNameFormula ?? string.Empty,
+ SheetName = data.SheetNameFormula ?? string.Empty,
+ SheetSize = data.SheetSize ?? string.Empty,
+ SheetCoefficient = data.SheetCoefficient ?? string.Empty,
+
+ TitleBlockFamily = titleBlockFamily,
+ TitleBlockTypes = titleBlockTypes,
+ TitleBlockType = titleBlockType,
+ };
+ // Добавляем список дополнительных параметров
+ SetCustomParametersList(sheetVM, data, sheetSetVM.SheetSetParams.Params);
+
+ foreach(var componentData in data.Views) {
+ var componentVM = CreateComponentVM(sheetSetVM, sheetVM, componentData);
+ sheetVM.SheetComponents.Add(componentVM);
+ }
+ return sheetVM;
+ }
+
+ public SheetComponentVM CreateComponentVM(SheetSetVM sheetSetVM, SheetVM sheetVM, SheetComponentData sheetComponentData) {
+ return sheetComponentData switch {
+ PlanViewData data => CreatePlanViewVM(sheetSetVM, sheetVM, data),
+ CalloutViewData data => CreateCalloutViewVM(sheetSetVM, sheetVM, data),
+ SectionViewData data => CreateSectionViewVM(sheetSetVM, sheetVM, data),
+ ScheduleViewData data => CreateScheduleViewVM(sheetSetVM, sheetVM, data),
+ TextNoteData data => CreateTextNoteVM(sheetSetVM, sheetVM, data),
+ TypicalAnnotationData data => CreateTypicalAnnotationVM(sheetSetVM, sheetVM, data),
+ LegendViewData data => CreateLegendViewVM(sheetSetVM, sheetVM, data),
+ _ => throw new NotSupportedException($"Тип '{sheetComponentData?.GetType().Name}' не поддерживается")
+ };
+ }
+
+ private PlanViewVM CreatePlanViewVM(SheetSetVM sheetSetVM, SheetVM sheetVM, PlanViewData data) {
+ var sheetComponentVM = new PlanViewVM(
+ _revitRepository, _stringParamSetService, sheetSetVM.SheetSetParams.Params, sheetVM, _localizationService) {
+ IsModuleCheck = data.IsModuleCheck ?? false,
+ ModuleName = data.ModuleName ?? string.Empty,
+ ModuleComment = data.ModuleComment ?? string.Empty,
+ ModuleCode = "123",
+
+ ViewNameFormula = data.ViewNameFormula ?? string.Empty,
+ ViewName = data.ViewNameFormula ?? string.Empty,
+ ViewFamilyType = _revitRepository.PlanViewTypes.FirstOrDefault(v => v.Name.Equals(data.ViewFamilyTypeName)),
+ ViewFamilyTypeFilter = GetFilterList(data.ViewFamilyTypeFilterValues, sheetSetVM.SheetSetParams.Params),
+
+ ViewTemplate = _revitRepository.PlanViewTemplates.FirstOrDefault(v => v.Name.Equals(data.ViewTemplateName)),
+ ViewTemplateFilter = GetFilterList(data.ViewTemplateFilterValues, sheetSetVM.SheetSetParams.Params),
+ ViewportType = _revitRepository.ViewportTypes.FirstOrDefault(v => v.Name.Equals(data.ViewportTypeName)),
+ ViewportTypeFilter = GetFilterList(data.ViewportTypeFilterValues, sheetSetVM.SheetSetParams.Params),
+ ViewCount = data.ViewCount ?? "1",
+ SelectedSelectElemParam = sheetVM.SheetSet.SheetSetParams.SelectElemParams
+ .FirstOrDefault(p => p.ParamName == data.SelectedSelectElemParamName && p is SelectElemParamVM)
+ };
+
+ // Добавляем список дополнительных параметров
+ SetCustomParametersList(sheetComponentVM, data, sheetSetVM.SheetSetParams.Params);
+ return sheetComponentVM;
+ }
+
+ private CalloutViewVM CreateCalloutViewVM(SheetSetVM sheetSetVM, SheetVM sheetVM, CalloutViewData data) {
+ var sheetComponentVM = new CalloutViewVM(
+ _revitRepository, _stringParamSetService, sheetSetVM.SheetSetParams.Params, sheetVM, _localizationService) {
+ IsModuleCheck = data.IsModuleCheck ?? false,
+ ModuleName = data.ModuleName ?? string.Empty,
+ ModuleComment = data.ModuleComment ?? string.Empty,
+ ModuleCode = "05",
+
+ ViewNameFormula = data.ViewNameFormula ?? string.Empty,
+ ViewName = data.ViewNameFormula ?? string.Empty,
+ ViewFamilyType = _revitRepository.PlanViewTypes.FirstOrDefault(v => v.Name.Equals(data.ViewFamilyTypeName)),
+ ViewFamilyTypeFilter = GetFilterList(data.ViewFamilyTypeFilterValues, sheetSetVM.SheetSetParams.Params),
+
+ ViewTemplate = _revitRepository.PlanViewTemplates.FirstOrDefault(v => v.Name.Equals(data.ViewTemplateName)),
+ ViewTemplateFilter = GetFilterList(data.ViewTemplateFilterValues, sheetSetVM.SheetSetParams.Params),
+ ViewportType = _revitRepository.ViewportTypes.FirstOrDefault(v => v.Name.Equals(data.ViewportTypeName)),
+ ViewportTypeFilter = GetFilterList(data.ViewportTypeFilterValues, sheetSetVM.SheetSetParams.Params),
+ ViewCount = data.ViewCount ?? "1",
+ SelectedSelectElemParam = sheetVM.SheetSet.SheetSetParams.SelectElemParams
+ .FirstOrDefault(p => p.ParamName == data.SelectedSelectElemParamName && p is SelectElemParamVM)
+ };
+
+ // Добавляем список дополнительных параметров
+ SetCustomParametersList(sheetComponentVM, data, sheetSetVM.SheetSetParams.Params);
+ return sheetComponentVM;
+ }
+
+ private SectionViewVM CreateSectionViewVM(SheetSetVM sheetSetVM, SheetVM sheetVM, SectionViewData data) {
+ var sheetComponentVM = new SectionViewVM(
+ _revitRepository, _stringParamSetService, sheetSetVM.SheetSetParams.Params, sheetVM, _localizationService) {
+ IsModuleCheck = data.IsModuleCheck ?? false,
+ ModuleName = data.ModuleName ?? string.Empty,
+ ModuleComment = data.ModuleComment ?? string.Empty,
+ ModuleCode = "789",
+
+ ViewNameFormula = data.ViewNameFormula ?? string.Empty,
+ ViewName = data.ViewNameFormula ?? string.Empty,
+ ViewFamilyType = _revitRepository.SectionViewTypes.FirstOrDefault(v => v.Name.Equals(data.ViewFamilyTypeName)),
+ ViewFamilyTypeFilter = GetFilterList(data.ViewFamilyTypeFilterValues, sheetSetVM.SheetSetParams.Params),
+
+ ViewTemplate = _revitRepository.SectionViewTemplates.FirstOrDefault(v => v.Name.Equals(data.ViewTemplateName)),
+ ViewTemplateFilter = GetFilterList(data.ViewTemplateFilterValues, sheetSetVM.SheetSetParams.Params),
+ ViewportType = _revitRepository.ViewportTypes.FirstOrDefault(v => v.Name.Equals(data.ViewportTypeName)),
+ ViewportTypeFilter = GetFilterList(data.ViewportTypeFilterValues, sheetSetVM.SheetSetParams.Params),
+ ViewCount = data.ViewCount ?? "1",
+ SelectedSelectElemParam = sheetVM.SheetSet.SheetSetParams.SelectElemParams
+ .FirstOrDefault(p => p.ParamName == data.SelectedSelectElemParamName && p is SelectElemParamVM)
+ };
+
+ // Добавляем список дополнительных параметров
+ SetCustomParametersList(sheetComponentVM, data, sheetSetVM.SheetSetParams.Params);
+ return sheetComponentVM;
+ }
+
+ private ScheduleViewVM CreateScheduleViewVM(SheetSetVM sheetSetVM, SheetVM sheetVM, ScheduleViewData data) {
+ var referenceSpec = _revitRepository.GetSpecByName(data.ReferenceViewName);
+
+ var scheduleViewVM = new ScheduleViewVM(
+ _revitRepository, _stringParamSetService, sheetSetVM.SheetSetParams.Params, sheetVM, _localizationService) {
+ IsModuleCheck = data.IsModuleCheck ?? false,
+ ModuleName = data.ModuleName ?? string.Empty,
+ ModuleComment = data.ModuleComment ?? string.Empty,
+ ModuleCode = "456",
+
+ ReferenceSpec = referenceSpec,
+ ReferenceSpecFilter = GetFilterList(data.ReferenceViewFilterValues, sheetSetVM.SheetSetParams.Params),
+ ViewNameFormula = data.ViewNameFormula ?? string.Empty,
+ ViewName = data.ViewNameFormula ?? string.Empty,
+ ViewColumn = data.ViewColumn ?? "1",
+ ViewCount = data.ViewCount ?? "1",
+ };
+
+ // Добавляем список фильтров
+ SetScheduleFilterList(scheduleViewVM, data, referenceSpec);
+
+ // Добавляем список дополнительных параметров
+ SetCustomParametersList(scheduleViewVM, data, sheetSetVM.SheetSetParams.Params);
+ return scheduleViewVM;
+ }
+
+ private TextNoteVM CreateTextNoteVM(SheetSetVM sheetSetVM, SheetVM sheetVM, TextNoteData data) {
+ var sheetComponentVM = new TextNoteVM(
+ _revitRepository, _stringParamSetService, sheetSetVM.SheetSetParams.Params, sheetVM, _localizationService) {
+ IsModuleCheck = data.IsModuleCheck ?? false,
+ ModuleName = data.ModuleName ?? string.Empty,
+ ModuleComment = data.ModuleComment ?? string.Empty,
+ ModuleCode = "01",
+
+ TextFormula = data.TextFormula ?? string.Empty,
+ Text = data.TextFormula ?? string.Empty,
+ TextNoteType = _revitRepository.TextNoteTypes.FirstOrDefault(v => v.Name.Equals(data.TextNoteTypeName)),
+ TextNoteTypeFilter = GetFilterList(data.TextNoteTypeFilterValues, sheetSetVM.SheetSetParams.Params),
+ TextWidth = data.TextWidth ?? "180",
+ };
+
+ // Добавляем список дополнительных параметров
+ SetCustomParametersList(sheetComponentVM, data, sheetSetVM.SheetSetParams.Params);
+ return sheetComponentVM;
+ }
+
+ private TypicalAnnotationVM CreateTypicalAnnotationVM(SheetSetVM sheetSetVM, SheetVM sheetVM, TypicalAnnotationData data) {
+ var sheetComponentVM = new TypicalAnnotationVM(
+ _revitRepository, _stringParamSetService, sheetSetVM.SheetSetParams.Params, sheetVM, _localizationService) {
+ IsModuleCheck = data.IsModuleCheck ?? false,
+ ModuleName = data.ModuleName ?? string.Empty,
+ ModuleComment = data.ModuleComment ?? string.Empty,
+ ModuleCode = "02",
+
+ AnnotationType = _revitRepository.GenericAnnotationTypes.FirstOrDefault(t => $"{t.FamilyName}: {t.Name}".Equals(data.AnnotationTypeName)),
+ AnnotationTypeFilter = GetFilterList(data.AnnotationTypeFilterValues, sheetSetVM.SheetSetParams.Params),
+ };
+
+ // Добавляем список дополнительных параметров
+ SetCustomParametersList(sheetComponentVM, data, sheetSetVM.SheetSetParams.Params);
+ return sheetComponentVM;
+ }
+
+ private LegendViewVM CreateLegendViewVM(SheetSetVM sheetSetVM, SheetVM sheetVM, LegendViewData data) {
+ var sheetComponentVM = new LegendViewVM(
+ _revitRepository, _stringParamSetService, sheetSetVM.SheetSetParams.Params, sheetVM, _localizationService) {
+ IsModuleCheck = data.IsModuleCheck ?? false,
+ ModuleName = data.ModuleName ?? string.Empty,
+ ModuleComment = data.ModuleComment ?? string.Empty,
+ ModuleCode = "03",
+
+ LegendView = _revitRepository.LegendsInProject.FirstOrDefault(v => v.Name.Equals(data.ViewName)),
+ LegendViewFilter = GetFilterList(data.ViewFilterValues, sheetSetVM.SheetSetParams.Params),
+ ViewportType = _revitRepository.ViewportTypes.FirstOrDefault(v => v.Name.Equals(data.ViewportTypeName)),
+ ViewportTypeFilter = GetFilterList(data.ViewportTypeFilterValues, sheetSetVM.SheetSetParams.Params),
+ };
+
+ // Добавляем список дополнительных параметров
+ SetCustomParametersList(sheetComponentVM, data, sheetSetVM.SheetSetParams.Params);
+ return sheetComponentVM;
+ }
+
+ ///
+ /// Добавляет список дополнительных параметров
+ ///
+ private void SetCustomParametersList(
+ BaseParamContainerVM baseParamContainer,
+ ParamContainerModuleData data,
+ ObservableCollection sheetSetParams) {
+ // Добавляем список дополнительных параметров
+ var customParamsList = new CustomParametersListVM(sheetSetParams, _stringParamSetService, _localizationService);
+ foreach(var paramData in data.CustomParamsList?.Params ?? []) {
+ var paramVM = new CustomParameterVM(customParamsList, _stringParamSetService, _localizationService) {
+ ParamName = paramData.ParamName ?? string.Empty,
+ ParamValueFormula = paramData.ParamValueFormula ?? string.Empty,
+ ParamValue = paramData.ParamValueFormula ?? string.Empty,
+ };
+ customParamsList.Params.Add(paramVM);
+ }
+ baseParamContainer.CustomParamsList = customParamsList;
+ }
+
+ ///
+ /// Возвращает список фильтров для значения
+ ///
+ private FiltrationComboBoxFilterListVM GetFilterList(
+ FiltrationComboBoxFilterListData data,
+ ObservableCollection sheetSetParams) {
+
+ var filterList = new FiltrationComboBoxFilterListVM(sheetSetParams, _stringParamSetService);
+ foreach(var valueData in data?.ValueList ?? []) {
+ var valueVM = new FiltrationComboBoxFilterVM(filterList, _stringParamSetService) {
+ ValueFormula = valueData.ValueFormula ?? string.Empty,
+ Value = valueData.ValueFormula ?? string.Empty,
+ };
+ filterList.ValueList.Add(valueVM);
+ }
+ return filterList;
+ }
+
+ ///
+ /// Добавляем список фильтров
+ ///
+ private void SetScheduleFilterList(SheetComponentVM sheetComponentVM, SheetComponentData data, ViewSchedule referenceSpec) {
+ // Метод предназначен только для модуля спецификаций
+ if(sheetComponentVM is not ScheduleViewVM scheduleViewVM || data is not ScheduleViewData scheduleViewData) { return; }
+
+ var scheduleFilterList = new ScheduleFilterListVM(scheduleViewVM, _stringParamSetService, _localizationService);
+ foreach(var ruleData in scheduleViewData.ScheduleFilterList?.ScheduleFilterRules ?? []) {
+ var ruleVM = new ScheduleFilterRuleVM(scheduleFilterList, _stringParamSetService, _localizationService) {
+ SelectedSpecFieldName = ruleData.FieldName,
+ SelectedFilterType = _revitRepository.FilterTypes.FirstOrDefault(t => t.FilterType == ruleData.FilterType),
+ FilterValueFormula = ruleData.FilterValueFormula ?? string.Empty,
+ FilterValue = ruleData.FilterValueFormula ?? string.Empty,
+ };
+ scheduleFilterList.ScheduleFilterRules.Add(ruleVM);
+ }
+ scheduleFilterList.SetScheduleToRemember(referenceSpec);
+ scheduleViewVM.ScheduleFilterList = scheduleFilterList;
+ }
+
+ public PluginParamVM CreateParamVM(SheetSetParametersListVM sheetSetParamsList, PluginParamData paramData) {
+ return paramData switch {
+ StringParamData data => new StringParamVM(
+ sheetSetParamsList, data.ParamName, data.ParamComment, data.StringValue, _localizationService),
+ SelectElemParamData data => new SelectElemParamVM(
+ sheetSetParamsList, data.ParamName, data.ParamComment, _localizationService),
+ _ => throw new NotSupportedException($"Тип '{paramData?.GetType().Name}' не поддерживается")
+ };
+ }
+}
diff --git a/src/RevitPackageDocumentation/ViewModels/FiltrationComboBoxVMs/FiltrationComboBoxFilterListVM.cs b/src/RevitPackageDocumentation/ViewModels/FiltrationComboBoxVMs/FiltrationComboBoxFilterListVM.cs
new file mode 100644
index 000000000..dc662d2c4
--- /dev/null
+++ b/src/RevitPackageDocumentation/ViewModels/FiltrationComboBoxVMs/FiltrationComboBoxFilterListVM.cs
@@ -0,0 +1,42 @@
+using System.Collections.ObjectModel;
+using System.Windows.Input;
+
+using dosymep.WPF.Commands;
+using dosymep.WPF.ViewModels;
+
+using RevitPackageDocumentation.Models;
+using RevitPackageDocumentation.ViewModels.Configuration.SheetSetParameters.Parameters;
+
+namespace RevitPackageDocumentation.ViewModels.FiltrationComboBoxVMs;
+internal class FiltrationComboBoxFilterListVM : BaseViewModel {
+ private ObservableCollection _valueList = [];
+
+ public FiltrationComboBoxFilterListVM(
+ ObservableCollection sheetSetParams,
+ StringParamSetService stringParamSetService) {
+ SheetSetParams = sheetSetParams;
+ StrParamSetService = stringParamSetService;
+ AddFilterCommand = RelayCommand.Create(AddValue);
+ RemoveFilterCommand = RelayCommand.Create(RemoveValue);
+ }
+
+ public ICommand AddFilterCommand { get; }
+ public ICommand RemoveFilterCommand { get; }
+
+ public ObservableCollection SheetSetParams { get; }
+ public StringParamSetService StrParamSetService { get; }
+
+ public ObservableCollection ValueList {
+ get => _valueList;
+ set => RaiseAndSetIfChanged(ref _valueList, value);
+ }
+
+ private void AddValue() {
+ var value = new FiltrationComboBoxFilterVM(this, StrParamSetService);
+ ValueList.Add(value);
+ }
+
+ private void RemoveValue(FiltrationComboBoxFilterVM value) {
+ ValueList.Remove(value);
+ }
+}
diff --git a/src/RevitPackageDocumentation/ViewModels/FiltrationComboBoxVMs/FiltrationComboBoxFilterVM.cs b/src/RevitPackageDocumentation/ViewModels/FiltrationComboBoxVMs/FiltrationComboBoxFilterVM.cs
new file mode 100644
index 000000000..867749e62
--- /dev/null
+++ b/src/RevitPackageDocumentation/ViewModels/FiltrationComboBoxVMs/FiltrationComboBoxFilterVM.cs
@@ -0,0 +1,49 @@
+using System.Windows.Input;
+
+using dosymep.WPF.Commands;
+using dosymep.WPF.ViewModels;
+
+using RevitPackageDocumentation.Models;
+using RevitPackageDocumentation.ViewModels.Configuration.SheetSetParameters.Parameters;
+
+namespace RevitPackageDocumentation.ViewModels.FiltrationComboBoxVMs;
+internal class FiltrationComboBoxFilterVM : BaseViewModel {
+ private string _valueFormula = string.Empty;
+ private string _value = string.Empty;
+
+ public FiltrationComboBoxFilterVM(FiltrationComboBoxFilterListVM filterList, StringParamSetService stringParamSetService) {
+ FilterList = filterList;
+ StrParamSetService = stringParamSetService;
+ PropUpdateByFormulaCommand = RelayCommand.Create(PropUpdateByFormula);
+ }
+
+ public ICommand PropUpdateByFormulaCommand { get; }
+
+ public FiltrationComboBoxFilterListVM FilterList { get; }
+ public StringParamSetService StrParamSetService { get; }
+
+ public string ValueFormula {
+ get => _valueFormula;
+ set => RaiseAndSetIfChanged(ref _valueFormula, value);
+ }
+
+ public string Value {
+ get => _value;
+ set => RaiseAndSetIfChanged(ref _value, value);
+ }
+
+ private void PropUpdateByFormula(string formulaPropertyName) {
+ StrParamSetService.Set(this, formulaPropertyName, FilterList.SheetSetParams);
+ }
+
+ ///
+ /// В случае изменения имени параметра конфигурации нужно обновить свойства дополнительного параметра
+ ///
+ public void UpdateDueParamNameChange() {
+ StrParamSetService.SetAll(this, FilterList.SheetSetParams);
+ }
+
+ public void UpdateDueParamValueChange(StringParamVM stringParam) {
+ StrParamSetService.SetAll(this, FilterList.SheetSetParams, stringParam);
+ }
+}
diff --git a/src/RevitPackageDocumentation/ViewModels/MainViewModel.cs b/src/RevitPackageDocumentation/ViewModels/MainViewModel.cs
new file mode 100644
index 000000000..3edb20e72
--- /dev/null
+++ b/src/RevitPackageDocumentation/ViewModels/MainViewModel.cs
@@ -0,0 +1,423 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Windows.Input;
+
+using Autodesk.Revit.DB;
+
+using dosymep.Revit;
+using dosymep.SimpleServices;
+using dosymep.WPF.Commands;
+using dosymep.WPF.ViewModels;
+
+using RevitPackageDocumentation.Models;
+using RevitPackageDocumentation.Models.ConfigSerializer;
+using RevitPackageDocumentation.Models.ScheduleFilters;
+using RevitPackageDocumentation.ViewModels.Configuration;
+using RevitPackageDocumentation.ViewModels.Configuration.Sheet.SheetComponents;
+using RevitPackageDocumentation.ViewModels.Configuration.SheetSetParameters.Parameters;
+
+namespace RevitPackageDocumentation.ViewModels;
+
+///
+/// Основная ViewModel главного окна плагина.
+///
+internal class MainViewModel : BaseViewModel {
+ private readonly PluginConfig _pluginConfig;
+ private readonly RevitRepository _revitRepository;
+ private readonly ILocalizationService _localizationService;
+ private readonly ISheetSetVMFactory _sheetSetVMFactory;
+ private readonly ISheetSetDataFactory _sheetSetDataFactory;
+ private readonly IRevitElementPickerService _revitElementPickerService;
+ private readonly SheetSetConfig _sheetSetConfig;
+
+ private SheetSetVM _currentSheetSet;
+
+ private string _errorText;
+ private string _sheetSetDataPath;
+
+ private List _sheetSetParamTypes;
+ private ComponentTypeItem _selectedSheetSetParamType;
+ private List _componentTypes;
+ private ComponentTypeItem _selectedComponentType;
+
+ private List _sectionViewFamilyTypes;
+ private List _planViewTemplates;
+ private List _sectionViewTemplates;
+ private List _structuralPlanViewFamilyTypes;
+ private List _viewportTypes;
+ private List _specsInPj;
+ private List _textNoteTypes;
+ private List _genericAnnotationTypes;
+ private List _legendsInProject;
+ private List _titleBlockFamilies;
+ private IList _filterTypes;
+
+ ///
+ /// Создает экземпляр основной ViewModel главного окна.
+ ///
+ /// Настройки плагина.
+ /// Класс доступа к интерфейсу Revit.
+ /// Интерфейс доступа к сервису локализации.
+ public MainViewModel(
+ PluginConfig pluginConfig,
+ RevitRepository revitRepository,
+ ILocalizationService localizationService,
+ IOpenFileDialogService openFileDialogService,
+ ISaveFileDialogService saveFileDialogService,
+ IMessageBoxService messageBoxService,
+ ISheetSetVMFactory sheetSetVMFactory,
+ ISheetSetDataFactory sheetSetDataFactory,
+ IRevitElementPickerService revitElementPickerService,
+ SheetSetConfig sheetSetConfig) {
+
+ _pluginConfig = pluginConfig;
+ _revitRepository = revitRepository;
+ _localizationService = localizationService;
+ _sheetSetVMFactory = sheetSetVMFactory;
+ _sheetSetDataFactory = sheetSetDataFactory;
+ _revitElementPickerService = revitElementPickerService;
+ _sheetSetConfig = sheetSetConfig;
+
+ MessageBoxService = messageBoxService ?? throw new ArgumentNullException(nameof(messageBoxService));
+ OpenFileDialogService = openFileDialogService ?? throw new ArgumentNullException(nameof(openFileDialogService));
+ SaveFileDialogService = saveFileDialogService ?? throw new ArgumentNullException(nameof(saveFileDialogService));
+
+ ImportCommand = RelayCommand.Create(ImportSheetSet);
+ ExportCommand = RelayCommand.Create(ExportSheetSet);
+
+ LoadViewCommand = RelayCommand.Create(LoadView);
+ AcceptViewCommand = RelayCommand.Create(AcceptView, CanAcceptView);
+
+ SelectElemForParamCommand = RelayCommand.Create(SelectElemForParam);
+ }
+
+ public ICommand ImportCommand { get; }
+ public ICommand ExportCommand { get; }
+ public ICommand SelectElemForParamCommand { get; }
+
+ ///
+ /// Команда загрузки главного окна.
+ ///
+ public ICommand LoadViewCommand { get; }
+
+ ///
+ /// Команда применения настроек главного окна. (запуск плагина)
+ ///
+ /// В случаях, когда используется немодальное окно, требуется данную команду удалять.
+ public ICommand AcceptViewCommand { get; }
+
+
+ public IOpenFileDialogService OpenFileDialogService { get; }
+ public ISaveFileDialogService SaveFileDialogService { get; }
+ public IMessageBoxService MessageBoxService { get; }
+
+ ///
+ /// Текст ошибки, который отображается при неверном вводе пользователя.
+ ///
+ public string ErrorText {
+ get => _errorText;
+ set => RaiseAndSetIfChanged(ref _errorText, value);
+ }
+
+ public SheetSetVM CurrentSheetSet {
+ get => _currentSheetSet;
+ set => RaiseAndSetIfChanged(ref _currentSheetSet, value);
+ }
+
+ public List PlanViewFamilyTypes {
+ get => _structuralPlanViewFamilyTypes;
+ set => RaiseAndSetIfChanged(ref _structuralPlanViewFamilyTypes, value);
+ }
+
+ public List SectionViewFamilyTypes {
+ get => _sectionViewFamilyTypes;
+ set => RaiseAndSetIfChanged(ref _sectionViewFamilyTypes, value);
+ }
+
+ public List PlanViewTemplates {
+ get => _planViewTemplates;
+ set => RaiseAndSetIfChanged(ref _planViewTemplates, value);
+ }
+
+ public List SectionViewTemplates {
+ get => _sectionViewTemplates;
+ set => RaiseAndSetIfChanged(ref _sectionViewTemplates, value);
+ }
+
+ public List ViewportTypes {
+ get => _viewportTypes;
+ set => RaiseAndSetIfChanged(ref _viewportTypes, value);
+ }
+
+ public List SpecsInPj {
+ get => _specsInPj;
+ set => RaiseAndSetIfChanged(ref _specsInPj, value);
+ }
+
+ public List TextNoteTypes {
+ get => _textNoteTypes;
+ set => RaiseAndSetIfChanged(ref _textNoteTypes, value);
+ }
+
+ public List GenericAnnotationTypes {
+ get => _genericAnnotationTypes;
+ set => RaiseAndSetIfChanged(ref _genericAnnotationTypes, value);
+ }
+
+ public List LegendsInProject {
+ get => _legendsInProject;
+ set => RaiseAndSetIfChanged(ref _legendsInProject, value);
+ }
+
+ public List TitleBlockFamilies {
+ get => _titleBlockFamilies;
+ set => RaiseAndSetIfChanged(ref _titleBlockFamilies, value);
+ }
+
+ public IList FilterTypes {
+ get => _filterTypes;
+ set => RaiseAndSetIfChanged(ref _filterTypes, value);
+ }
+
+ public List ComponentTypes {
+ get => _componentTypes;
+ set => RaiseAndSetIfChanged(ref _componentTypes, value);
+ }
+
+ public ComponentTypeItem SelectedComponentType {
+ get => _selectedComponentType;
+ set => RaiseAndSetIfChanged(ref _selectedComponentType, value);
+ }
+
+ public List SheetSetParamTypes {
+ get => _sheetSetParamTypes;
+ set => RaiseAndSetIfChanged(ref _sheetSetParamTypes, value);
+ }
+
+ public ComponentTypeItem SelectedSheetSetParamType {
+ get => _selectedSheetSetParamType;
+ set => RaiseAndSetIfChanged(ref _selectedSheetSetParamType, value);
+ }
+
+
+ private void LoadView() {
+ LoadConfig();
+ GetSettingsForUI();
+
+ if(string.IsNullOrEmpty(_sheetSetDataPath) || !File.Exists(_sheetSetDataPath)) {
+ ImportSheetSet();
+ } else {
+ ImportSheetSet(_sheetSetDataPath);
+ }
+ }
+
+ private void GetSettingsForUI() {
+ ComponentTypes = Assembly.GetExecutingAssembly()
+ .GetTypes()
+ .Where(t => t.IsClass && !t.IsAbstract && t.IsSubclassOf(typeof(SheetComponentVM)))
+ .Select(t =>
+ new ComponentTypeItem(t, _localizationService.GetLocalizedString($"Type.{t.Name}") ?? string.Empty))
+ .OrderBy(item => item.ComponentType switch {
+ Type t when t == typeof(PlanViewVM) => 1,
+ Type t when t == typeof(SectionViewVM) => 2,
+ Type t when t == typeof(CalloutViewVM) => 3,
+ _ => 4
+ })
+ .ToList();
+ SelectedComponentType = null;
+
+ SheetSetParamTypes = Assembly.GetExecutingAssembly()
+ .GetTypes()
+ .Where(t => t.IsClass && !t.IsAbstract && t.IsSubclassOf(typeof(PluginParamVM)))
+ .Select(t =>
+ new ComponentTypeItem(t, _localizationService.GetLocalizedString($"Type.{t.Name}") ?? string.Empty))
+ .ToList();
+ SelectedSheetSetParamType = null;
+
+ PlanViewFamilyTypes = _revitRepository.PlanViewTypes;
+ SectionViewFamilyTypes = _revitRepository.SectionViewTypes;
+ PlanViewTemplates = _revitRepository.PlanViewTemplates;
+ SectionViewTemplates = _revitRepository.SectionViewTemplates;
+ ViewportTypes = _revitRepository.ViewportTypes;
+ SpecsInPj = _revitRepository.Specs;
+ TextNoteTypes = _revitRepository.TextNoteTypes;
+ GenericAnnotationTypes = _revitRepository.GenericAnnotationTypes;
+ LegendsInProject = _revitRepository.LegendsInProject;
+ TitleBlockFamilies = _revitRepository.TitleBlockFamilies;
+
+ FilterTypes = _revitRepository.FilterTypes;
+ }
+
+ private void ImportSheetSet() {
+ if(OpenFileDialogService.ShowDialog()) {
+ // Если пользователь выбрал файл при выборе файла
+ _sheetSetDataPath = OpenFileDialogService.File.FullName;
+ ImportSheetSet(_sheetSetDataPath);
+ } else {
+ if(CurrentSheetSet is null) {
+ // Если пользователь нажал Отмена при выборе файла и текущая конфигурация еще не загружена
+ var sheetSetData = _sheetSetDataFactory.CreateSheetSetData();
+ ImportSheetSet(sheetSetData);
+ } else {
+ // Если пользователь нажал Отмена при выборе файла и текущая конфигурация УЖЕ загружена
+ return;
+ }
+ }
+ }
+
+ private void ImportSheetSet(string sheetSetDataPath) {
+ var sheetSetData = _sheetSetConfig.Import(sheetSetDataPath);
+ ImportSheetSet(sheetSetData);
+ }
+
+ private void ImportSheetSet(SheetSetData sheetSetData) {
+ CurrentSheetSet = _sheetSetVMFactory.CreateSheetSetVM(sheetSetData);
+ CurrentSheetSet.SelectedSheet = CurrentSheetSet.SheetList.FirstOrDefault();
+ CurrentSheetSet.ValidateAllSheets();
+ }
+
+ private void ExportSheetSet() {
+ if(SaveFileDialogService.ShowDialog(_sheetSetDataPath, "config.json")) {
+ string temp = SaveFileDialogService.File.FullName;
+
+ // Если путь некорректен
+ if(string.IsNullOrEmpty(temp)) {
+ MessageBoxService.Show(
+ _localizationService.GetLocalizedString("MainViewModel.ExportPathIsNotCorrect"),
+ _localizationService.GetLocalizedString("MainViewModel.Export"));
+ return;
+ }
+
+ var currentSheetSetData = _sheetSetDataFactory.CreateSheetSetData(CurrentSheetSet);
+ _sheetSetConfig.Export(currentSheetSetData, temp);
+ // Сохраняем в плагине только после того, как все успешно экспортировалось
+ _sheetSetDataPath = temp;
+
+ MessageBoxService.Show(
+ _localizationService.GetLocalizedString("MainViewModel.ExportIsSuccessful"),
+ _localizationService.GetLocalizedString("MainViewModel.Export"));
+ } else {
+ // Если пользователь нажал отмена
+ MessageBoxService.Show(
+ _localizationService.GetLocalizedString("MainViewModel.ExportCanceled"),
+ _localizationService.GetLocalizedString("MainViewModel.Export"));
+ return;
+ }
+ }
+
+
+ ///
+ /// Загрузка настроек плагина.
+ ///
+ private void LoadConfig() {
+ RevitSettings setting = _pluginConfig.GetSettings(_revitRepository.Document);
+
+ _sheetSetDataPath = setting?.SheetSetDataPath;
+ }
+
+ ///
+ /// Сохранение настроек плагина.
+ ///
+ private void SaveConfig() {
+ RevitSettings setting = _pluginConfig.GetSettings(_revitRepository.Document)
+ ?? _pluginConfig.AddSettings(_revitRepository.Document);
+
+ setting.SheetSetDataPath = _sheetSetDataPath;
+ _pluginConfig.SaveProjectConfig();
+ }
+
+
+ ///
+ /// Метод применения настроек главного окна. (выполнение плагина)
+ ///
+ ///
+ /// В данном методе должны браться настройки пользователя и сохраняться в конфиг, а так же быть основной код плагина.
+ ///
+ private void AcceptView() {
+ SaveConfig();
+
+ using var transaction = _revitRepository.Document.StartTransaction(
+ _localizationService.GetLocalizedString("MainWindow.Title"));
+
+ foreach(var sheet in CurrentSheetSet.SheetList.Where(s => s.IsModuleCheck).ToList()) {
+ sheet.Process(true);
+ }
+ transaction.Commit();
+ }
+
+ ///
+ /// Метод проверки возможности выполнения команды применения настроек.
+ ///
+ /// В случае когда true - команда может выполниться, в случае false - нет.
+ ///
+ /// В данном методе происходит валидация ввода пользователя и уведомление его о неверных значениях.
+ /// В методе проверяемые свойства окна должны быть отсортированы в таком же порядке как в окне (сверху-вниз)
+ ///
+ private bool CanAcceptView() {
+ if(CurrentSheetSet?.SheetList?
+ .Where(s => s.IsModuleCheck)
+ .Any(c => c.HasErrors) ?? true) {
+ ErrorText = _localizationService.GetLocalizedString("MainWindow.ErrorInSheets");
+ return false;
+ }
+ if(CurrentSheetSet?.SheetList?.Count() == 0) {
+ ErrorText = _localizationService.GetLocalizedString("MainWindow.SheetSetHasNotSheets");
+ return false;
+ }
+ if(CurrentSheetSet?.SheetList?.All(p => !p.IsModuleCheck) == true) {
+ ErrorText = _localizationService.GetLocalizedString("MainWindow.NoSheetsSelected");
+ return false;
+ }
+
+ var viewNames = new HashSet();
+ foreach(var sheetComponent in CurrentSheetSet?.SheetList
+ ?.SelectMany(s => s.SheetComponents)
+ .Where(c => c.IsModuleCheck)
+ .ToList() ?? []) {
+ string name = sheetComponent switch {
+ ScheduleViewVM sv => sv.ViewName,
+ SectionViewVM sv => sv.ViewName,
+ CalloutViewVM sv => sv.ViewName,
+ PlanViewVM sv => sv.ViewName,
+ _ => string.Empty
+ };
+
+ if(string.IsNullOrEmpty(name))
+ continue;
+
+ if(!viewNames.Add(name)) {
+ ErrorText = $"{_localizationService.GetLocalizedString("MainWindow.ViewNameAlreadyHas")} - " +
+ $"{sheetComponent.Sheet.ModuleName} - {name}";
+ return false;
+ }
+ }
+
+ var paramNames = new HashSet();
+ foreach(var parameter in CurrentSheetSet.SheetSetParams.Params) {
+ string name = parameter.ParamName;
+ if(string.IsNullOrEmpty(name))
+ continue;
+
+ if(!paramNames.Add(name)) {
+ ErrorText = $"{_localizationService.GetLocalizedString("MainWindow.ParamNameAlreadyHas")} - {name}";
+ return false;
+ }
+ }
+
+ ErrorText = string.Empty;
+ return true;
+ }
+
+ ///
+ /// Метод команды по выбору элемента для параметра конфигурации
+ ///
+ private void SelectElemForParam(SelectElemParamVM vm) {
+ _revitElementPickerService.PickElement(onSelected: (element) => {
+ vm.SelectedElem = element;
+ vm.ParamValueChangeCommand.Execute(null);
+ });
+ }
+}
diff --git a/src/RevitPackageDocumentation/ViewModels/ScheduleFilters/ScheduleFilterListVM.cs b/src/RevitPackageDocumentation/ViewModels/ScheduleFilters/ScheduleFilterListVM.cs
new file mode 100644
index 000000000..f396b9a35
--- /dev/null
+++ b/src/RevitPackageDocumentation/ViewModels/ScheduleFilters/ScheduleFilterListVM.cs
@@ -0,0 +1,118 @@
+using System;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Windows.Input;
+
+using Autodesk.Revit.DB;
+
+using dosymep.SimpleServices;
+using dosymep.WPF.Commands;
+
+using RevitPackageDocumentation.Models;
+using RevitPackageDocumentation.Models.ScheduleFilters;
+using RevitPackageDocumentation.ViewModels.Configuration.Sheet.SheetComponents;
+using RevitPackageDocumentation.ViewModels.Validation;
+using RevitPackageDocumentation.ViewModels.Validation.Attributes;
+
+namespace RevitPackageDocumentation.ViewModels.ScheduleFilters;
+internal class ScheduleFilterListVM : ValidatableVM {
+ private readonly ILocalizationService _localizationService;
+ private ObservableCollection _scheduleFilterRules = [];
+ private ObservableCollection _selectedScheduleFields = [];
+
+ public ScheduleFilterListVM(
+ ScheduleViewVM scheduleViewVM,
+ StringParamSetService stringParamSetService,
+ ILocalizationService localizationService)
+ : base(localizationService) {
+
+ ScheduleView = scheduleViewVM;
+ StrParamSetService = stringParamSetService;
+ _localizationService = localizationService;
+ ValidateAllProperties();
+
+ AddFilterCommand = RelayCommand.Create(AddFilter);
+ RemoveFilterCommand = RelayCommand.Create(RemoveFilter);
+ }
+
+ public ICommand AddFilterCommand { get; }
+ public ICommand RemoveFilterCommand { get; }
+
+ public ScheduleViewVM ScheduleView { get; }
+ public StringParamSetService StrParamSetService { get; }
+
+ [ChildHasErrors]
+ public ObservableCollection ScheduleFilterRules {
+ get => _scheduleFilterRules;
+ set => RaiseAndSetIfChanged(ref _scheduleFilterRules, value);
+ }
+
+ public ObservableCollection SelectedScheduleFields {
+ get => _selectedScheduleFields;
+ set => RaiseAndSetIfChanged(ref _selectedScheduleFields, value);
+ }
+
+ private void AddFilter() {
+ var rule = new ScheduleFilterRuleVM(this, StrParamSetService, _localizationService);
+ ScheduleFilterRules.Add(rule);
+ }
+
+ private void RemoveFilter(ScheduleFilterRuleVM rule) {
+ ScheduleFilterRules.Remove(rule);
+ }
+
+ internal void SetSchedule(ViewSchedule schedule) {
+ ScheduleFilterRules.Clear();
+ SelectedScheduleFields.Clear();
+
+ if(schedule == null) {
+ return;
+ }
+ var definition = schedule.Definition;
+
+ foreach(var fieldId in definition.GetFieldOrder()) {
+ SelectedScheduleFields.Add(new ScheduleFieldInfo(definition.GetField(fieldId)));
+ }
+
+ foreach(var scheduleFilter in definition.GetFilters()) {
+ var fieldId = scheduleFilter.FieldId;
+ var scheduleFilterField = definition.GetField(fieldId);
+ var selectedSpecField =
+ SelectedScheduleFields.FirstOrDefault(f => f.Field.FieldId == scheduleFilterField.FieldId);
+
+ string filterValue = string.Empty;
+ try {
+ if(definition.CanFilterBySubstring(fieldId)) {
+ filterValue = scheduleFilter.GetStringValue() ?? string.Empty;
+ } else {
+ filterValue = scheduleFilter.GetIntegerValue().ToString() ?? string.Empty;
+ }
+ } catch(Exception) { }
+
+ var ruleVM = new ScheduleFilterRuleVM(this, StrParamSetService, _localizationService) {
+ SelectedSpecField = selectedSpecField,
+ SelectedSpecFieldName = selectedSpecField.FieldName,
+ SelectedFilterType =
+ ScheduleView.Repository.FilterTypes.FirstOrDefault(f => f.FilterType == scheduleFilter.FilterType),
+ FilterValueFormula = filterValue,
+ FilterValue = filterValue,
+ };
+ ScheduleFilterRules.Add(ruleVM);
+ }
+ }
+
+ internal void SetScheduleToRemember(ViewSchedule schedule) {
+ if(schedule == null) {
+ return;
+ }
+ var definition = schedule.Definition;
+
+ foreach(var fieldId in definition.GetFieldOrder()) {
+ SelectedScheduleFields.Add(new ScheduleFieldInfo(definition.GetField(fieldId)));
+ }
+
+ foreach(var rule in ScheduleFilterRules) {
+ rule.SelectedSpecField = SelectedScheduleFields.FirstOrDefault(f => f.FieldName == rule.SelectedSpecFieldName);
+ }
+ }
+}
diff --git a/src/RevitPackageDocumentation/ViewModels/ScheduleFilters/ScheduleFilterRuleVM.cs b/src/RevitPackageDocumentation/ViewModels/ScheduleFilters/ScheduleFilterRuleVM.cs
new file mode 100644
index 000000000..0ea281972
--- /dev/null
+++ b/src/RevitPackageDocumentation/ViewModels/ScheduleFilters/ScheduleFilterRuleVM.cs
@@ -0,0 +1,88 @@
+using System.ComponentModel.DataAnnotations;
+using System.Windows.Input;
+
+using dosymep.SimpleServices;
+using dosymep.WPF.Commands;
+
+using RevitPackageDocumentation.Models;
+using RevitPackageDocumentation.Models.ScheduleFilters;
+using RevitPackageDocumentation.ViewModels.Configuration.SheetSetParameters.Parameters;
+using RevitPackageDocumentation.ViewModels.Validation;
+
+namespace RevitPackageDocumentation.ViewModels.ScheduleFilters;
+internal class ScheduleFilterRuleVM : ValidatableVM {
+ private ScheduleFieldInfo _selectedSpecField;
+ private string _selectedSpecFieldName;
+ private ScheduleTypeInfo _selectedFilterType;
+
+ private string _filterValueFormula = string.Empty;
+ private string _filterValue;
+
+ public ScheduleFilterRuleVM(
+ ScheduleFilterListVM scheduleFilterListVM,
+ StringParamSetService stringParamSetService,
+ ILocalizationService localizationService)
+ : base(localizationService) {
+
+ ScheduleFilterList = scheduleFilterListVM;
+ StrParamSetService = stringParamSetService;
+ ValidateAllProperties();
+
+ SelectSpecFieldCommand = RelayCommand.Create(SelectSpecField);
+ PropUpdateByFormulaCommand = RelayCommand.Create(PropUpdateByFormula);
+ }
+
+ public ICommand SelectSpecFieldCommand { get; }
+ public ICommand PropUpdateByFormulaCommand { get; }
+
+ public ScheduleFilterListVM ScheduleFilterList { get; }
+ public StringParamSetService StrParamSetService { get; }
+
+ [Required]
+ public ScheduleFieldInfo SelectedSpecField {
+ get => _selectedSpecField;
+ set => RaiseAndSetIfChanged(ref _selectedSpecField, value);
+ }
+
+ public string SelectedSpecFieldName {
+ get => _selectedSpecFieldName;
+ set => RaiseAndSetIfChanged(ref _selectedSpecFieldName, value);
+ }
+
+ [Required]
+ public ScheduleTypeInfo SelectedFilterType {
+ get => _selectedFilterType;
+ set => RaiseAndSetIfChanged(ref _selectedFilterType, value);
+ }
+
+ public string FilterValueFormula {
+ get => _filterValueFormula;
+ set => RaiseAndSetIfChanged(ref _filterValueFormula, value);
+ }
+
+ public string FilterValue {
+ get => _filterValue;
+ set => RaiseAndSetIfChanged(ref _filterValue, value);
+ }
+
+ private void SelectSpecField() {
+ if(SelectedSpecField != null) {
+ SelectedSpecFieldName = SelectedSpecField.FieldName ?? string.Empty;
+ }
+ }
+
+ private void PropUpdateByFormula(string formulaPropertyName) {
+ StrParamSetService.Set(this, formulaPropertyName, ScheduleFilterList.ScheduleView.Sheet.SheetSet.SheetSetParams.Params);
+ }
+
+ ///
+ /// В случае изменения имени параметра конфигурации нужно обновить свойства дополнительного параметра
+ ///
+ public void UpdateDueParamNameChange() {
+ StrParamSetService.SetAll(this, ScheduleFilterList.ScheduleView.Sheet.SheetSet.SheetSetParams.Params);
+ }
+
+ public void UpdateDueParamValueChange(StringParamVM stringParam) {
+ StrParamSetService.SetAll(this, ScheduleFilterList.ScheduleView.Sheet.SheetSet.SheetSetParams.Params, stringParam);
+ }
+}
diff --git a/src/RevitPackageDocumentation/ViewModels/Validation/Attributes/ChildHasErrorsAttribute.cs b/src/RevitPackageDocumentation/ViewModels/Validation/Attributes/ChildHasErrorsAttribute.cs
new file mode 100644
index 000000000..9ff21c88b
--- /dev/null
+++ b/src/RevitPackageDocumentation/ViewModels/Validation/Attributes/ChildHasErrorsAttribute.cs
@@ -0,0 +1,27 @@
+using System.Collections;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+
+namespace RevitPackageDocumentation.ViewModels.Validation.Attributes;
+internal class ChildHasErrorsAttribute : ValidationAttribute {
+ protected override ValidationResult IsValid(object value, ValidationContext context) {
+ switch(value) {
+ case null:
+ return ValidationResult.Success;
+
+ case INotifyDataErrorInfo notify:
+ return notify.HasErrors
+ ? new ValidationResult(ErrorMessage)
+ : ValidationResult.Success;
+
+ case IEnumerable enumerable:
+ foreach(var item in enumerable) {
+ if(item is INotifyDataErrorInfo child && child.HasErrors) {
+ return new ValidationResult(ErrorMessage);
+ }
+ }
+ break;
+ }
+ return ValidationResult.Success;
+ }
+}
diff --git a/src/RevitPackageDocumentation/ViewModels/Validation/Attributes/PositiveIntegerAttribute.cs b/src/RevitPackageDocumentation/ViewModels/Validation/Attributes/PositiveIntegerAttribute.cs
new file mode 100644
index 000000000..799f17f86
--- /dev/null
+++ b/src/RevitPackageDocumentation/ViewModels/Validation/Attributes/PositiveIntegerAttribute.cs
@@ -0,0 +1,14 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace RevitPackageDocumentation.ViewModels.Validation.Attributes;
+internal class PositiveIntegerAttribute : ValidationAttribute {
+ protected override ValidationResult IsValid(object value, ValidationContext context) {
+ if(value is null
+ || value is not string stringValue
+ || !int.TryParse(stringValue, out int valueAsInt)
+ || valueAsInt < 1) {
+ return new ValidationResult(ErrorMessage);
+ }
+ return ValidationResult.Success;
+ }
+}
diff --git a/src/RevitPackageDocumentation/ViewModels/Validation/Attributes/SelectedElemIsNullAttribute.cs b/src/RevitPackageDocumentation/ViewModels/Validation/Attributes/SelectedElemIsNullAttribute.cs
new file mode 100644
index 000000000..7ffe3ee4b
--- /dev/null
+++ b/src/RevitPackageDocumentation/ViewModels/Validation/Attributes/SelectedElemIsNullAttribute.cs
@@ -0,0 +1,13 @@
+using System.ComponentModel.DataAnnotations;
+
+using RevitPackageDocumentation.ViewModels.Configuration.SheetSetParameters.Parameters;
+
+namespace RevitPackageDocumentation.ViewModels.Validation.Attributes;
+internal class SelectedElemIsNullAttribute : ValidationAttribute {
+ protected override ValidationResult IsValid(object value, ValidationContext context) {
+ if(value is null || value is not SelectElemParamVM selectElemParam || selectElemParam.SelectedElem is null) {
+ return new ValidationResult(ErrorMessage);
+ }
+ return ValidationResult.Success;
+ }
+}
diff --git a/src/RevitPackageDocumentation/ViewModels/Validation/ValidatableVM.cs b/src/RevitPackageDocumentation/ViewModels/Validation/ValidatableVM.cs
new file mode 100644
index 000000000..068da147c
--- /dev/null
+++ b/src/RevitPackageDocumentation/ViewModels/Validation/ValidatableVM.cs
@@ -0,0 +1,243 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Reflection;
+
+using dosymep.SimpleServices;
+using dosymep.WPF.ViewModels;
+
+using RevitPackageDocumentation.ViewModels.Validation.Attributes;
+
+namespace RevitPackageDocumentation.ViewModels.Validation;
+
+internal abstract class ValidatableVM : BaseViewModel, INotifyDataErrorInfo {
+ private readonly ILocalizationService _localizationService;
+
+ protected readonly Dictionary> _errors = [];
+
+ private readonly IReadOnlyDictionary _propertyCache;
+ private readonly IReadOnlyDictionary _trackedChildProperties;
+
+ ///
+ /// Подписки на PropertyChanged дочерних объектов
+ ///
+ private readonly Dictionary