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 _childSubscriptions = []; + + /// + /// Подписки на CollectionChanged коллекций + /// + private readonly Dictionary _collectionSubscriptions = []; + + /// + /// Текущее значение отслеживаемого свойства, необходимо для корректной переподписки + /// + private readonly Dictionary _trackedValues = []; + + private bool _hasErrors; + private string _firstError; + + public event EventHandler ErrorsChanged; + + public bool HasErrors { + get => _hasErrors; + set => RaiseAndSetIfChanged(ref _hasErrors, value); + } + + public string FirstError { + get => _firstError; + set => RaiseAndSetIfChanged(ref _firstError, value); + } + + protected ValidatableVM(ILocalizationService localizationService) { + _localizationService = localizationService; + + var props = GetType() + .GetProperties(BindingFlags.Public | BindingFlags.Instance); + + _propertyCache = props + .Where(x => x.CanRead && x.GetCustomAttributes().Any()) + .ToDictionary(x => x.Name); + + _trackedChildProperties = props + .Where(x => x.GetCustomAttribute() != null) + .ToDictionary(x => x.Name); + + foreach(var property in _trackedChildProperties.Values) { + RegisterTrackedProperty(property); + } + } + + public void ValidateAllProperties() { + foreach(var propertyName in _propertyCache.Keys) { + ValidateProperty(propertyName); + } + } + + private void ValidateProperty(string propertyName) { + if(!_propertyCache.TryGetValue(propertyName, out var property)) + return; + + var value = property.GetValue(this); + var validationResults = new List(); + var validationContext = new ValidationContext(this) { + MemberName = propertyName + }; + Validator.TryValidateProperty(value, validationContext, validationResults); + + var localizedErrors = validationResults.Select(x => Localize(x.ErrorMessage)).ToList(); + UpdateErrors(propertyName, localizedErrors); + } + + private void UpdateErrors(string propertyName, IEnumerable errors) { + var list = errors.ToList(); + + if(list.Any()) { + _errors[propertyName] = list; + } else { + _errors.Remove(propertyName); + } + + ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName)); + HasErrors = _errors.Any(); + FirstError = _errors.Values + .FirstOrDefault(x => x.Any()) + ?.FirstOrDefault() ?? string.Empty; + } + + private string Localize(string key) { + if(string.IsNullOrWhiteSpace(key)) + return string.Empty; + + string text = _localizationService.GetLocalizedString(key); + return string.IsNullOrWhiteSpace(text) ? key : text; + } + + + private void RegisterTrackedProperty(PropertyInfo property) { + var value = property.GetValue(this); + _trackedValues[property.Name] = value; + + switch(value) { + case INotifyCollectionChanged collection: + SubscribeCollection(property.Name, collection); + break; + + case INotifyPropertyChanged notify: + SubscribeChild(property.Name, notify); + break; + } + } + + private void ReRegisterTrackedProperty(PropertyInfo property) { + if(_trackedValues.TryGetValue(property.Name, out var oldValue)) { + + switch(oldValue) { + case INotifyCollectionChanged collection: + UnsubscribeCollection(collection); + break; + + case INotifyPropertyChanged notify: + UnsubscribeChild(notify); + break; + } + } + RegisterTrackedProperty(property); + ValidateProperty(property.Name); + } + + private void SubscribeCollection( + string propertyName, + INotifyCollectionChanged collection) { + + foreach(var item in (IEnumerable) collection) { + SubscribeItem(propertyName, item); + } + + NotifyCollectionChangedEventHandler handler = (_, e) => { + if(e.NewItems != null) { + foreach(var item in e.NewItems) { + SubscribeItem(propertyName, item); + } + } + if(e.OldItems != null) { + foreach(var item in e.OldItems) { + UnsubscribeChild(item); + } + } + ValidateProperty(propertyName); + }; + + collection.CollectionChanged += handler; + _collectionSubscriptions[collection] = handler; + } + + private void SubscribeItem(string propertyName, object item) { + if(item is INotifyPropertyChanged notify) { + SubscribeChild(propertyName, notify); + } + } + + private void SubscribeChild( + string propertyName, + INotifyPropertyChanged notify) { + + PropertyChangedEventHandler handler = (_, e) => { + if(e.PropertyName == nameof(HasErrors)) { + ValidateProperty(propertyName); + } + }; + notify.PropertyChanged += handler; + _childSubscriptions[notify] = handler; + } + + private void UnsubscribeCollection(INotifyCollectionChanged collection) { + if(_collectionSubscriptions.TryGetValue(collection, out var handler)) { + collection.CollectionChanged -= handler; + _collectionSubscriptions.Remove(collection); + } + + foreach(var item in (IEnumerable) collection) { + UnsubscribeChild(item); + } + } + + private void UnsubscribeChild(object item) { + if(item is not INotifyPropertyChanged notify) + return; + + if(_childSubscriptions.TryGetValue(notify, out var handler)) { + notify.PropertyChanged -= handler; + _childSubscriptions.Remove(notify); + } + } + + + protected override void RaisePropertyChanged(string propertyName) { + base.RaisePropertyChanged(propertyName); + + if(string.IsNullOrEmpty(propertyName)) + return; + + if(propertyName != nameof(HasErrors) && propertyName != nameof(FirstError)) { + ValidateProperty(propertyName); + } + + if(_trackedChildProperties.TryGetValue(propertyName, out var property)) { + ReRegisterTrackedProperty(property); + } + } + + public IEnumerable GetErrors(string propertyName) { + try { + return _errors.TryGetValue(propertyName, out var errors) + ? errors + : Enumerable.Empty(); + } catch { + return Enumerable.Empty(); + } + } +} diff --git a/src/RevitPackageDocumentation/Views/Controls/CustomParamItemControl.xaml b/src/RevitPackageDocumentation/Views/Controls/CustomParamItemControl.xaml new file mode 100644 index 000000000..fba317c7d --- /dev/null +++ b/src/RevitPackageDocumentation/Views/Controls/CustomParamItemControl.xaml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/RevitPackageDocumentation/Views/Controls/CustomParamItemControl.xaml.cs b/src/RevitPackageDocumentation/Views/Controls/CustomParamItemControl.xaml.cs new file mode 100644 index 000000000..b8843eeda --- /dev/null +++ b/src/RevitPackageDocumentation/Views/Controls/CustomParamItemControl.xaml.cs @@ -0,0 +1,20 @@ +using System.Windows; +using System.Windows.Controls; + +namespace RevitPackageDocumentation.Views.Controls; +/// +/// Логика взаимодействия для CustomParamItemControl.xaml +/// +public partial class CustomParamItemControl : UserControl { + public static readonly DependencyProperty TextBoxStyleProperty = + DependencyProperty.Register(nameof(TextBoxStyle), typeof(Style), typeof(CustomParamItemControl)); + + public CustomParamItemControl() { + InitializeComponent(); + } + + public Style TextBoxStyle { + get => (Style) GetValue(TextBoxStyleProperty); + set => SetValue(TextBoxStyleProperty, value); + } +} diff --git a/src/RevitPackageDocumentation/Views/Controls/FiltrationComboBoxControl.xaml b/src/RevitPackageDocumentation/Views/Controls/FiltrationComboBoxControl.xaml new file mode 100644 index 000000000..517c4afa2 --- /dev/null +++ b/src/RevitPackageDocumentation/Views/Controls/FiltrationComboBoxControl.xaml @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/RevitPackageDocumentation/Views/Controls/FiltrationComboBoxControl.xaml.cs b/src/RevitPackageDocumentation/Views/Controls/FiltrationComboBoxControl.xaml.cs new file mode 100644 index 000000000..6138b66ad --- /dev/null +++ b/src/RevitPackageDocumentation/Views/Controls/FiltrationComboBoxControl.xaml.cs @@ -0,0 +1,199 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Threading; + +using Autodesk.Revit.DB; + +using RevitPackageDocumentation.ViewModels.FiltrationComboBoxVMs; + +namespace RevitPackageDocumentation.Views.Controls; +public partial class FiltrationComboBoxControl : UserControl { + public static readonly DependencyProperty ComboBoxSourceProperty = + DependencyProperty.Register(nameof(ComboBoxSource), typeof(IEnumerable), typeof(FiltrationComboBoxControl), + new PropertyMetadata(null, OnComboBoxSourceChanged)); + + public static readonly DependencyProperty ComboBoxSelectedProperty = + DependencyProperty.Register(nameof(ComboBoxSelected), typeof(Element), typeof(FiltrationComboBoxControl)); + + public static readonly DependencyProperty FilterListProperty = + DependencyProperty.Register(nameof(FilterList), typeof(FiltrationComboBoxFilterListVM), typeof(FiltrationComboBoxControl), + new PropertyMetadata(null, OnFilterListSourceChanged)); + + public static readonly DependencyProperty ComboBoxSelectionChangedCommandProperty = + DependencyProperty.Register(nameof(ComboBoxSelectionChangedCommand), typeof(ICommand), typeof(FiltrationComboBoxControl)); + + public static readonly DependencyProperty ItemTemplateProperty = DependencyProperty.Register( + nameof(ItemTemplate), typeof(DataTemplate), typeof(FiltrationComboBoxControl), new PropertyMetadata(default(DataTemplate))); + + public static readonly DependencyProperty ComboBoxStyleProperty = + DependencyProperty.Register(nameof(ComboBoxStyle), typeof(Style), typeof(FiltrationComboBoxControl)); + + public FiltrationComboBoxControl() { + InitializeComponent(); + } + + public IEnumerable ComboBoxSource { + get => (IEnumerable) GetValue(ComboBoxSourceProperty); + set => SetValue(ComboBoxSourceProperty, value); + } + + public Element ComboBoxSelected { + get => (Element) GetValue(ComboBoxSelectedProperty); + set => SetValue(ComboBoxSelectedProperty, value); + } + + internal FiltrationComboBoxFilterListVM FilterList { + get => (FiltrationComboBoxFilterListVM) GetValue(FilterListProperty); + set => SetValue(FilterListProperty, value); + } + + public ICommand ComboBoxSelectionChangedCommand { + get => (ICommand) GetValue(ComboBoxSelectionChangedCommandProperty); + set => SetValue(ComboBoxSelectionChangedCommandProperty, value); + } + + public DataTemplate ItemTemplate { + get => (DataTemplate) GetValue(ItemTemplateProperty); + set => SetValue(ItemTemplateProperty, value); + } + + public Style ComboBoxStyle { + get => (Style) GetValue(ComboBoxStyleProperty); + set => SetValue(ComboBoxStyleProperty, value); + } + + /// + /// Коллекция элементов для ComboBox, после того как проведена фильтрация по фильтрам + /// + public ObservableCollection FilteredItemsSource { get; } = []; + + + /// + /// Выполняем обновление значений ComboBox по фильтрам после привязки источника + /// + private static void OnComboBoxSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { + var control = (FiltrationComboBoxControl) d; + control.UpdateFilteredItems(); + } + + /// + /// Выполняем обновление значений FilterList после привязки источника + /// Срабатывает, когда меняется (назначается впервые) ссылка на коллекцию + /// + private static void OnFilterListSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { + var control = (FiltrationComboBoxControl) d; + control.UpdateFilteredItems(); + control.SubscribeToFilterList(); + } + + /// + /// Подписываемся на изменения списка фильтров - добавление фильтра, удаление фильтра, изменения значения фильтра + /// + private void SubscribeToFilterList() { + if(FilterList?.ValueList == null) { return; } + + // Подписываемся на изменения коллекции - добавления и удаления + FilterList.ValueList.CollectionChanged += OnValueListCollectionChanged; + + // Подписываемся на изменения значения каждого фильтра + foreach(var item in FilterList.ValueList) { + SubscribeToFilterItem(item); + } + } + + /// + /// Получаем фильтры и перезапускаем фильтрацию при добавлении и удалении фильтров + /// + private void OnValueListCollectionChanged( + object sender, + System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { + + // Обработка добавленных элементов + if(e.NewItems != null) { + foreach(FiltrationComboBoxFilterVM newItem in e.NewItems) { + SubscribeToFilterItem(newItem); + } + } + + // Обработка удаленных элементов + if(e.OldItems != null) { + foreach(FiltrationComboBoxFilterVM oldItem in e.OldItems) { + UnsubscribeFromFilterItem(oldItem); + } + } + + // Обновляем фильтры при изменении коллекции + UpdateFilteredItems(); + } + + private void SubscribeToFilterItem(FiltrationComboBoxFilterVM item) { + if(item is INotifyPropertyChanged notifyItem) { + notifyItem.PropertyChanged += OnFilterItemPropertyChanged; + } + } + + private void UnsubscribeFromFilterItem(FiltrationComboBoxFilterVM item) { + if(item is INotifyPropertyChanged notifyItem) { + notifyItem.PropertyChanged -= OnFilterItemPropertyChanged; + } + } + + private void OnFilterItemPropertyChanged(object sender, PropertyChangedEventArgs e) { + if(e.PropertyName == nameof(FiltrationComboBoxFilterVM.Value)) { + Dispatcher.BeginInvoke(new Action(UpdateFilteredItems), DispatcherPriority.Background); + } + } + + + /// + /// Обновляет значения ComboBox по фильтрам + /// + private void UpdateFilteredItems() { + FilteredItemsSource.Clear(); + + if(ComboBoxSource is null || FilterList is null) { + return; + } + var filters = FilterList.ValueList + .Select(x => x?.Value) + .Where(x => !string.IsNullOrWhiteSpace(x)) + .ToList(); + + foreach(var element in ComboBoxSource.OfType()) { + if(ItemMatchesAllFilters(element, filters)) { + FilteredItemsSource.Add(element); + } + } + + if(FilteredItemsSource.Count == 1) { + ComboBoxSelected = FilteredItemsSource.First(); + } + } + + /// + /// Проверяет, что имя элемента содержит все строки фильтра + /// + private bool ItemMatchesAllFilters(Element element, IReadOnlyCollection filters) { + if(filters.Count == 0) { + return true; + } + string name = element.Name ?? string.Empty; + return filters.All(name.Contains); + } + + /// + /// Показывает/скрывает видимость фильтров + /// + private void Button_Click(object sender, RoutedEventArgs e) { + flyout.IsOpen = flyout.IsOpen == true + ? false + : true; + } +} diff --git a/src/RevitPackageDocumentation/Views/Controls/FormulaTextBox.cs b/src/RevitPackageDocumentation/Views/Controls/FormulaTextBox.cs new file mode 100644 index 000000000..f84e2340b --- /dev/null +++ b/src/RevitPackageDocumentation/Views/Controls/FormulaTextBox.cs @@ -0,0 +1,40 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Threading; + +namespace RevitPackageDocumentation.Views.Controls; + +public class FormulaTextBox : TextBox { + private readonly DispatcherTimer _timer; + + public static readonly DependencyProperty UpdateFormulaCommandProperty = + DependencyProperty.Register( + nameof(UpdateFormulaCommand), + typeof(ICommand), + typeof(FormulaTextBox), + new PropertyMetadata(null)); + + public ICommand UpdateFormulaCommand { + get => (ICommand) GetValue(UpdateFormulaCommandProperty); + set => SetValue(UpdateFormulaCommandProperty, value); + } + + public FormulaTextBox() { + _timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(250) }; + _timer.Tick += (s, e) => { _timer.Stop(); UpdateFormula(); }; + } + + protected override void OnTextChanged(TextChangedEventArgs e) { + _timer.Stop(); + _timer.Start(); + } + + private void UpdateFormula() { + var propertyName = GetBindingExpression(TextProperty)?.ResolvedSourcePropertyName; + if(propertyName != null) { + UpdateFormulaCommand?.Execute(propertyName); + } + } +} diff --git a/src/RevitPackageDocumentation/Views/Controls/FormulaTextBoxControl.xaml b/src/RevitPackageDocumentation/Views/Controls/FormulaTextBoxControl.xaml new file mode 100644 index 000000000..237722ba1 --- /dev/null +++ b/src/RevitPackageDocumentation/Views/Controls/FormulaTextBoxControl.xaml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/RevitPackageDocumentation/Views/Controls/FormulaTextBoxControl.xaml.cs b/src/RevitPackageDocumentation/Views/Controls/FormulaTextBoxControl.xaml.cs new file mode 100644 index 000000000..930e69cd9 --- /dev/null +++ b/src/RevitPackageDocumentation/Views/Controls/FormulaTextBoxControl.xaml.cs @@ -0,0 +1,95 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Threading; + +namespace RevitPackageDocumentation.Views.Controls; + +public partial class FormulaTextBoxControl : UserControl { + private readonly DispatcherTimer _timer; + + public static readonly DependencyProperty PropFormulaProperty = + DependencyProperty.Register(nameof(PropFormula), typeof(string), typeof(FormulaTextBoxControl)); + + public static readonly DependencyProperty PropResultProperty = + DependencyProperty.Register(nameof(PropResult), typeof(string), typeof(FormulaTextBoxControl), + new FrameworkPropertyMetadata(OnPropResultChanged)); + + public static readonly DependencyProperty UpdateCommandProperty = + DependencyProperty.Register(nameof(UpdateCommand), typeof(ICommand), typeof(FormulaTextBoxControl)); + + public static readonly DependencyProperty TextBoxStyleProperty = + DependencyProperty.Register(nameof(TextBoxStyle), typeof(Style), typeof(FormulaTextBoxControl)); + + public static readonly DependencyProperty AcceptsReturnProperty = + DependencyProperty.Register(nameof(AcceptsReturn), typeof(bool), typeof(FormulaTextBoxControl), + new FrameworkPropertyMetadata(false)); + + + public string PropFormula { + get => (string) GetValue(PropFormulaProperty); + set => SetValue(PropFormulaProperty, value); + } + + public string PropResult { + get => (string) GetValue(PropResultProperty); + set => SetValue(PropResultProperty, value); + } + + public ICommand UpdateCommand { + get => (ICommand) GetValue(UpdateCommandProperty); + set => SetValue(UpdateCommandProperty, value); + } + + public Style TextBoxStyle { + get => (Style) GetValue(TextBoxStyleProperty); + set => SetValue(TextBoxStyleProperty, value); + } + + public bool AcceptsReturn { + get => (bool) GetValue(AcceptsReturnProperty); + set => SetValue(AcceptsReturnProperty, value); + } + + public FormulaTextBoxControl() { + InitializeComponent(); + + // Когда меняется формула + _timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(250) }; + _timer.Tick += (s, e) => { + _timer.Stop(); + UpdateFormula(); + UpdateResultVisibility(); + }; + FormulaInputTextBox.TextChanged += OnTextChanged; + } + + /// + /// При инициализации + /// + private static void OnPropResultChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { + var control = d as FormulaTextBoxControl; + control?.UpdateResultVisibility(); + } + + private void OnTextChanged(object sender, TextChangedEventArgs e) { + _timer.Stop(); + _timer.Start(); + } + + /// + /// Запускает команду, что текст формулы изменился + /// + private void UpdateFormula() { + string propertyName = GetBindingExpression(PropFormulaProperty)?.ResolvedSourcePropertyName; + if(propertyName != null) { + UpdateCommand?.Execute(propertyName); + } + } + + private void UpdateResultVisibility() { + bool areEqual = string.Equals(PropFormula ?? string.Empty, PropResult ?? string.Empty); + ResultTextBlock.Visibility = areEqual ? Visibility.Collapsed : Visibility.Visible; + } +} diff --git a/src/RevitPackageDocumentation/Views/Controls/ParamItemControl.xaml b/src/RevitPackageDocumentation/Views/Controls/ParamItemControl.xaml new file mode 100644 index 000000000..ed627252b --- /dev/null +++ b/src/RevitPackageDocumentation/Views/Controls/ParamItemControl.xaml @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/RevitPackageDocumentation/Views/Controls/ParamItemControl.xaml.cs b/src/RevitPackageDocumentation/Views/Controls/ParamItemControl.xaml.cs new file mode 100644 index 000000000..6c15df704 --- /dev/null +++ b/src/RevitPackageDocumentation/Views/Controls/ParamItemControl.xaml.cs @@ -0,0 +1,41 @@ +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; + +namespace RevitPackageDocumentation.Views.Controls; + +public partial class ParamItemControl : UserControl { + public static readonly DependencyProperty DerivedTemplateProperty = + DependencyProperty.Register(nameof(DerivedTemplate), typeof(DataTemplate), typeof(ParamItemControl)); + + public ParamItemControl() { + InitializeComponent(); + } + + public DataTemplate DerivedTemplate { + get => (DataTemplate) GetValue(DerivedTemplateProperty); + set => SetValue(DerivedTemplateProperty, value); + } + + private void EditMenuItem_Click(object sender, RoutedEventArgs e) { + // Переключаемся в режим редактирования + DisplayTextBlock.Visibility = Visibility.Collapsed; + EditTextBox.Visibility = Visibility.Visible; + + EditTextBox.Focus(); + EditTextBox.SelectAll(); + } + + private void EditTextBox_LostFocus(object sender, RoutedEventArgs e) { + DisplayTextBlock.Visibility = Visibility.Visible; + EditTextBox.Visibility = Visibility.Collapsed; + } + + private void EditTextBox_KeyDown(object sender, KeyEventArgs e) { + if(e.Key == Key.Enter || e.Key == Key.Escape) { + DisplayTextBlock.Visibility = Visibility.Visible; + EditTextBox.Visibility = Visibility.Collapsed; + e.Handled = true; + } + } +} diff --git a/src/RevitPackageDocumentation/Views/Controls/SheetComponentItemControl.xaml b/src/RevitPackageDocumentation/Views/Controls/SheetComponentItemControl.xaml new file mode 100644 index 000000000..142488dc2 --- /dev/null +++ b/src/RevitPackageDocumentation/Views/Controls/SheetComponentItemControl.xaml @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/RevitPackageDocumentation/Views/Controls/SheetComponentItemControl.xaml.cs b/src/RevitPackageDocumentation/Views/Controls/SheetComponentItemControl.xaml.cs new file mode 100644 index 000000000..02df20f42 --- /dev/null +++ b/src/RevitPackageDocumentation/Views/Controls/SheetComponentItemControl.xaml.cs @@ -0,0 +1,21 @@ +using System.Windows; +using System.Windows.Controls; + +namespace RevitPackageDocumentation.Views.Controls; +/// +/// Логика взаимодействия для SheetComponentItemControl.xaml +/// +public partial class SheetComponentItemControl : UserControl { + public static readonly DependencyProperty DerivedTemplateProperty = + DependencyProperty.Register(nameof(DerivedTemplate), typeof(DataTemplate), + typeof(SheetComponentItemControl)); + + public DataTemplate DerivedTemplate { + get => (DataTemplate) GetValue(DerivedTemplateProperty); + set => SetValue(DerivedTemplateProperty, value); + } + + public SheetComponentItemControl() { + InitializeComponent(); + } +} diff --git a/src/RevitPackageDocumentation/Views/Controls/SheetItemControl.xaml b/src/RevitPackageDocumentation/Views/Controls/SheetItemControl.xaml new file mode 100644 index 000000000..f71039425 --- /dev/null +++ b/src/RevitPackageDocumentation/Views/Controls/SheetItemControl.xaml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/RevitPackageDocumentation/Views/Controls/SheetItemControl.xaml.cs b/src/RevitPackageDocumentation/Views/Controls/SheetItemControl.xaml.cs new file mode 100644 index 000000000..a9ed11006 --- /dev/null +++ b/src/RevitPackageDocumentation/Views/Controls/SheetItemControl.xaml.cs @@ -0,0 +1,11 @@ +using System.Windows.Controls; + +namespace RevitPackageDocumentation.Views.Controls; +/// +/// Логика взаимодействия для SheetItemControl.xaml +/// +public partial class SheetItemControl : UserControl { + public SheetItemControl() { + InitializeComponent(); + } +} diff --git a/src/RevitPackageDocumentation/Views/Controls/SpecFilterItemControl.xaml b/src/RevitPackageDocumentation/Views/Controls/SpecFilterItemControl.xaml new file mode 100644 index 000000000..b74d74b30 --- /dev/null +++ b/src/RevitPackageDocumentation/Views/Controls/SpecFilterItemControl.xaml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/RevitPackageDocumentation/Views/Controls/SpecFilterItemControl.xaml.cs b/src/RevitPackageDocumentation/Views/Controls/SpecFilterItemControl.xaml.cs new file mode 100644 index 000000000..9bfdd0d84 --- /dev/null +++ b/src/RevitPackageDocumentation/Views/Controls/SpecFilterItemControl.xaml.cs @@ -0,0 +1,20 @@ +using System.Windows; +using System.Windows.Controls; + +namespace RevitPackageDocumentation.Views.Controls; +/// +/// Логика взаимодействия для SpecFilterItemControl.xaml +/// +public partial class SpecFilterItemControl : UserControl { + public static readonly DependencyProperty ComboBoxStyleProperty = + DependencyProperty.Register(nameof(ComboBoxStyle), typeof(Style), typeof(SpecFilterItemControl)); + + public SpecFilterItemControl() { + InitializeComponent(); + } + + public Style ComboBoxStyle { + get => (Style) GetValue(ComboBoxStyleProperty); + set => SetValue(ComboBoxStyleProperty, value); + } +} diff --git a/src/RevitPackageDocumentation/Views/CustomDataTemplates.xaml b/src/RevitPackageDocumentation/Views/CustomDataTemplates.xaml new file mode 100644 index 000000000..3a18ca537 --- /dev/null +++ b/src/RevitPackageDocumentation/Views/CustomDataTemplates.xaml @@ -0,0 +1,1299 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +