diff --git a/src/RevitParamsChecker/Models/Filtration/Filter.cs b/src/RevitParamsChecker/Models/Filtration/Filter.cs
index 8e76659f8..2563a418a 100644
--- a/src/RevitParamsChecker/Models/Filtration/Filter.cs
+++ b/src/RevitParamsChecker/Models/Filtration/Filter.cs
@@ -6,5 +6,15 @@ public Filter() {
public string Name { get; set; }
+ ///
+ /// Сериализованный контекст фильтра по параметрам элементов.
+ /// Пустое значение означает, что фильтрация не задана.
+ ///
public string FilterContext { get; set; }
+
+ ///
+ /// Сериализованный контекст фильтра по параметрам материалов элементов.
+ /// Пустое значение означает, что фильтрация по материалам не задана.
+ ///
+ public string MaterialsFilterContext { get; set; }
}
diff --git a/src/RevitParamsChecker/Models/Filtration/FilterDataProvider.cs b/src/RevitParamsChecker/Models/Filtration/FilterDataProvider.cs
index 0f0ff7824..73062798c 100644
--- a/src/RevitParamsChecker/Models/Filtration/FilterDataProvider.cs
+++ b/src/RevitParamsChecker/Models/Filtration/FilterDataProvider.cs
@@ -27,6 +27,14 @@ public DataProvider CreateDataProvider() {
return new DataProvider(GetCategories(), GetParams, GetParamValues);
}
+ ///
+ /// Создает провайдер данных для фильтра по параметрам материалов элементов.
+ ///
+ /// Провайдер данных с единственной категорией - "Материалы".
+ public DataProvider CreateMaterialsDataProvider() {
+ return new DataProvider(GetMaterialsCategories(), GetMaterialParams, GetMaterialParamValues);
+ }
+
private ICollection GetParams(ICollection categories) {
return ParameterFilterUtilities
.GetFilterableParametersInCommon(_revitRepository.Document, [..categories.Select(c => c.Id)])
@@ -43,6 +51,52 @@ private ICollection GetCategories() {
.ToArray();
}
+ private ICollection GetMaterialsCategories() {
+ var category = Category.GetCategory(_revitRepository.Document, BuiltInCategory.OST_Materials);
+ return category is null ? [] : [category];
+ }
+
+ ///
+ /// Возвращает параметры, доступные для фильтрации материалов.
+ ///
+ ///
+ /// Материалы не входят в фильтруемые категории видов Revit, поэтому,
+ /// если получить параметры штатным способом не удалось,
+ /// они берутся из первого попавшегося материала документа.
+ ///
+ private ICollection GetMaterialParams(ICollection categories) {
+ ICollection paramIds;
+ try {
+ paramIds = ParameterFilterUtilities.GetFilterableParametersInCommon(
+ _revitRepository.Document,
+ [..categories.Select(c => c.Id)]);
+ } catch(Autodesk.Revit.Exceptions.ApplicationException) {
+ paramIds = [];
+ }
+
+ var revitParams = paramIds
+ .Select(GetFilterableParam)
+ .Where(p => p != null)
+ .ToArray();
+ return revitParams.Length > 0 ? revitParams : GetFirstMaterialParams();
+ }
+
+ private ICollection GetFirstMaterialParams() {
+ var material = new FilteredElementCollector(_revitRepository.Document)
+ .OfClass(typeof(Material))
+ .FirstElement();
+ if(material is null) {
+ return [];
+ }
+
+ return material.Parameters
+ .OfType()
+ .Select(p => p.Id)
+ .Select(GetFilterableParam)
+ .Where(p => p != null)
+ .ToArray();
+ }
+
private RevitParam GetFilterableParam(ElementId paramId) {
try {
if(paramId.IsSystemId()) {
@@ -99,4 +153,23 @@ private ICollection GetParamValues(
return [];
}
}
+
+ private ICollection GetMaterialParamValues(ICollection categories, RevitParam param) {
+ return _revitRepository.GetDocuments()
+ .SelectMany(d => GetMaterialParamValues(d.Document, param))
+ .Distinct()
+ .ToArray();
+ }
+
+ private ICollection GetMaterialParamValues(Document doc, RevitParam param) {
+ try {
+ return new FilteredElementCollector(doc)
+ .OfClass(typeof(Material))
+ .Where(e => e.IsExistsParamValue(param.Name))
+ .Select(e => e.GetParamValueString(param))
+ .ToArray();
+ } catch(Autodesk.Revit.Exceptions.ApplicationException) {
+ return [];
+ }
+ }
}
diff --git a/src/RevitParamsChecker/Models/Revit/RevitRepository.cs b/src/RevitParamsChecker/Models/Revit/RevitRepository.cs
index 55bd2cbfa..17b8833ed 100644
--- a/src/RevitParamsChecker/Models/Revit/RevitRepository.cs
+++ b/src/RevitParamsChecker/Models/Revit/RevitRepository.cs
@@ -6,7 +6,9 @@
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
+using Bim4Everyone.RevitFiltration;
using Bim4Everyone.RevitFiltration.Controls;
+using Bim4Everyone.RevitFiltration.Extensions.Materials;
using RevitParamsChecker.Models.Filtration;
@@ -14,11 +16,17 @@ namespace RevitParamsChecker.Models.Revit;
internal class RevitRepository {
private readonly IFilterContextParser _filterParser;
+ private readonly ILogicalFilterFactory _filterFactory;
private DocumentModel[] _documentsCache;
private readonly Bim4Everyone.RevitFiltration.Options _filterBuildOptions = new() { Tolerance = 0.001 };
+ private readonly MaterialOptions _materialsBuildOptions = new() { Tolerance = 0.001 };
- public RevitRepository(UIApplication uiApplication, IFilterContextParser filterParser) {
+ public RevitRepository(
+ UIApplication uiApplication,
+ IFilterContextParser filterParser,
+ ILogicalFilterFactory filterFactory) {
_filterParser = filterParser ?? throw new ArgumentNullException(nameof(filterParser));
+ _filterFactory = filterFactory ?? throw new ArgumentNullException(nameof(filterFactory));
UIApplication = uiApplication ?? throw new ArgumentNullException(nameof(uiApplication));
}
@@ -57,10 +65,12 @@ public ICollection GetElements(DocumentModel doc, Filter filter) {
var paramsFilter = context!.GetFilter().Build(doc.Document, _filterBuildOptions);
var categoriesFilter = new ElementMulticategoryFilter(context.SelectedCategories);
+ var materialsFilter = GetMaterialsFilter(doc.Document, filter.MaterialsFilterContext);
return new FilteredElementCollector(doc.Document)
.WhereElementIsNotElementType()
.WherePasses(categoriesFilter)
.WherePasses(paramsFilter)
+ .PassesFilter(materialsFilter)
.Select(e => new ElementModel(
e,
doc.IsLink ? new Reference(e).CreateLinkReference(doc.Link) : new Reference(e)))
@@ -104,6 +114,24 @@ public void SelectElements(ICollection elements) {
#endif
}
+ ///
+ /// Создает фильтр элементов по параметрам их материалов.
+ ///
+ /// Документ, в котором отбираются материалы.
+ /// Сериализованный контекст фильтра по материалам.
+ ///
+ /// Фильтр по материалам. Если контекст не задан либо его не удалось прочитать,
+ /// возвращается фильтр без правил, пропускающий все элементы.
+ ///
+ private MaterialsFilter GetMaterialsFilter(Document doc, string serializedContext) {
+ if(!string.IsNullOrWhiteSpace(serializedContext)
+ && _filterParser.TryParse(serializedContext, out var context)) {
+ return context!.GetFilter().BuildMaterialsFilter(doc, _materialsBuildOptions);
+ }
+
+ return _filterFactory.CreateAndFilter().BuildMaterialsFilter(doc, _materialsBuildOptions);
+ }
+
private ICollection GetRevitLinkInstances() {
return new FilteredElementCollector(Document)
.OfClass(typeof(RevitLinkInstance))
diff --git a/src/RevitParamsChecker/RevitParamsCheckerCommand.cs b/src/RevitParamsChecker/RevitParamsCheckerCommand.cs
index 9609a978a..43a9ffcfe 100644
--- a/src/RevitParamsChecker/RevitParamsCheckerCommand.cs
+++ b/src/RevitParamsChecker/RevitParamsCheckerCommand.cs
@@ -6,7 +6,6 @@
using Autodesk.Revit.Attributes;
using Autodesk.Revit.UI;
-using Bim4Everyone.RevitFiltration.Controls;
using Bim4Everyone.RevitFiltration.Ninject;
using dosymep.Bim4Everyone;
@@ -62,8 +61,8 @@ protected override void Execute(UIApplication uiApplication) {
kernel.UseLogicalFilterFactory();
kernel.UseLogicalFilterProviderFactory();
kernel.UseFilterContextParser();
- kernel.Bind()
- .ToMethod(c => new FilterDataProvider(c.Kernel.Get()).CreateDataProvider())
+ kernel.Bind()
+ .ToSelf()
.InSingletonScope();
kernel.Bind()
.To()
diff --git a/src/RevitParamsChecker/ViewModels/Filtration/FilterViewModel.cs b/src/RevitParamsChecker/ViewModels/Filtration/FilterViewModel.cs
index dcde69121..63d973d86 100644
--- a/src/RevitParamsChecker/ViewModels/Filtration/FilterViewModel.cs
+++ b/src/RevitParamsChecker/ViewModels/Filtration/FilterViewModel.cs
@@ -11,15 +11,29 @@ namespace RevitParamsChecker.ViewModels.Filtration;
internal class FilterViewModel : BaseViewModel, IEquatable, IName {
private readonly Guid _guid;
+ private readonly IFilterContextParser _filterContextParser;
+ private string _savedFilterContext;
+ private string _savedMaterialsFilterContext;
private string _name;
private bool _modified;
- public FilterViewModel(string name, ILogicalFilterProvider filterProvider) {
+ public FilterViewModel(
+ string name,
+ ILogicalFilterProvider filterProvider,
+ ILogicalFilterProvider materialsFilterProvider,
+ IFilterContextParser filterContextParser) {
if(string.IsNullOrWhiteSpace(name)) {
throw new ArgumentException(nameof(name));
}
FilterProvider = filterProvider ?? throw new ArgumentNullException(nameof(filterProvider));
+ MaterialsFilterProvider = materialsFilterProvider
+ ?? throw new ArgumentNullException(nameof(materialsFilterProvider));
+ _filterContextParser = filterContextParser ?? throw new ArgumentNullException(nameof(filterContextParser));
+ _savedFilterContext = GetContext(FilterProvider);
+ _savedMaterialsFilterContext = GetContext(MaterialsFilterProvider);
+ FilterProvider.FilterContextChanged += OnFilterContextChanged;
+ MaterialsFilterProvider.FilterContextChanged += OnFilterContextChanged;
Name = name;
Modified = true;
_guid = Guid.NewGuid();
@@ -36,8 +50,25 @@ public bool Modified {
set => RaiseAndSetIfChanged(ref _modified, value);
}
+ ///
+ /// Провайдер фильтра по параметрам элементов.
+ ///
public ILogicalFilterProvider FilterProvider { get; }
+ ///
+ /// Провайдер фильтра по параметрам материалов элементов.
+ ///
+ public ILogicalFilterProvider MaterialsFilterProvider { get; }
+
+ ///
+ /// Запоминает текущее состояние фильтров как сохраненное и снимает пометку об изменениях.
+ ///
+ public void AcceptChanges() {
+ _savedFilterContext = GetContext(FilterProvider);
+ _savedMaterialsFilterContext = GetContext(MaterialsFilterProvider);
+ Modified = false;
+ }
+
public override bool Equals(object obj) {
return Equals(obj as FilterViewModel);
}
@@ -58,6 +89,39 @@ public bool Equals(FilterViewModel other) {
return _guid == other._guid;
}
+ ///
+ /// Помечает набор измененным, если фильтр действительно отличается от сохраненного.
+ ///
+ ///
+ /// Контрол пересобирает контекст фильтра при загрузке провайдера, то есть при каждом выборе набора в списке,
+ /// поэтому одного факта события недостаточно: состояния сравниваются в том же виде, в котором сохраняются.
+ ///
+ private void OnFilterContextChanged(object sender, FilterContextChangedEventArgs e) {
+ if(IsFilterChanged()) {
+ Modified = true;
+ }
+ }
+
+ private bool IsFilterChanged() {
+ return !string.Equals(
+ _savedFilterContext,
+ GetContext(FilterProvider),
+ StringComparison.CurrentCultureIgnoreCase)
+ || !string.Equals(
+ _savedMaterialsFilterContext,
+ GetContext(MaterialsFilterProvider),
+ StringComparison.CurrentCultureIgnoreCase);
+ }
+
+ ///
+ /// Возвращает сериализованный контекст фильтра либо null, если фильтр задан некорректно.
+ ///
+ private string GetContext(ILogicalFilterProvider provider) {
+ return provider.CanGetFilter(out _)
+ ? _filterContextParser.Serialize(provider.GetFilter())
+ : null;
+ }
+
private void OnModelPropertyChanged(object sender, PropertyChangedEventArgs e) {
if(e.PropertyName == nameof(Name)) {
Modified = true;
diff --git a/src/RevitParamsChecker/ViewModels/Filtration/FiltrationPageViewModel.cs b/src/RevitParamsChecker/ViewModels/Filtration/FiltrationPageViewModel.cs
index 45e460b06..1f896cc41 100644
--- a/src/RevitParamsChecker/ViewModels/Filtration/FiltrationPageViewModel.cs
+++ b/src/RevitParamsChecker/ViewModels/Filtration/FiltrationPageViewModel.cs
@@ -7,6 +7,9 @@
using System.Linq;
using System.Windows.Input;
+using Autodesk.Revit.DB;
+
+using Bim4Everyone.RevitFiltration;
using Bim4Everyone.RevitFiltration.Controls;
using dosymep.SimpleServices;
@@ -21,8 +24,10 @@ namespace RevitParamsChecker.ViewModels.Filtration;
internal class FiltrationPageViewModel : BaseViewModel {
private readonly ILocalizationService _localization;
private readonly ILogicalFilterProviderFactory _filterProviderFactory;
+ private readonly ILogicalFilterFactory _filterFactory;
private readonly IFilterContextParser _filterContextParser;
private readonly DataProvider _dataProvider;
+ private readonly DataProvider _materialsDataProvider;
private readonly FiltersRepository _filtersRepo;
private readonly FiltersConverter _filtersConverter;
private readonly NamesService _namesService;
@@ -38,8 +43,9 @@ public FiltrationPageViewModel(
ISaveFileDialogService saveFileDialogService,
IMessageBoxService messageBoxService,
ILogicalFilterProviderFactory filterProviderFactory,
+ ILogicalFilterFactory filterFactory,
IFilterContextParser filterContextParser,
- DataProvider dataProvider,
+ FilterDataProvider dataProviderFactory,
FiltersRepository filtersRepo,
FiltersConverter filtersConverter,
NamesService namesService) {
@@ -50,8 +56,14 @@ public FiltrationPageViewModel(
_localization = localization ?? throw new ArgumentNullException(nameof(localization));
_filterProviderFactory =
filterProviderFactory ?? throw new ArgumentNullException(nameof(filterProviderFactory));
+ _filterFactory = filterFactory ?? throw new ArgumentNullException(nameof(filterFactory));
_filterContextParser = filterContextParser ?? throw new ArgumentNullException(nameof(filterContextParser));
- _dataProvider = dataProvider ?? throw new ArgumentNullException(nameof(dataProvider));
+ if(dataProviderFactory is null) {
+ throw new ArgumentNullException(nameof(dataProviderFactory));
+ }
+
+ _dataProvider = dataProviderFactory.CreateDataProvider();
+ _materialsDataProvider = dataProviderFactory.CreateMaterialsDataProvider();
_filtersRepo = filtersRepo ?? throw new ArgumentNullException(nameof(filtersRepo));
_filtersConverter = filtersConverter ?? throw new ArgumentNullException(nameof(filtersConverter));
_namesService = namesService ?? throw new ArgumentNullException(nameof(namesService));
@@ -104,7 +116,11 @@ private void AddFilter() {
string newName = _namesService.CreateNewName(
_localization.GetLocalizedString("FiltersPage.NewFilterPrompt"),
Filters.Select(f => f.Name).ToArray());
- var vm = new FilterViewModel(newName, GetFilterProvider());
+ var vm = new FilterViewModel(
+ newName,
+ GetFilterProvider(),
+ GetMaterialsFilterProvider(),
+ _filterContextParser);
vm.PropertyChanged += OnFilterChanged;
Filters.Add(vm);
SelectedFilter = vm;
@@ -134,7 +150,9 @@ private void CopyFilter(FilterViewModel filter) {
Filters.Select(f => f.Name).ToArray(),
filter.Name);
var copyContext = GetFilterProvider(_filterContextParser.Serialize(filter.FilterProvider.GetFilter()));
- var vm = new FilterViewModel(copyName, copyContext);
+ var copyMaterialsContext = GetMaterialsFilterProvider(
+ _filterContextParser.Serialize(filter.MaterialsFilterProvider.GetFilter()));
+ var vm = new FilterViewModel(copyName, copyContext, copyMaterialsContext, _filterContextParser);
vm.PropertyChanged += OnFilterChanged;
Filters.Add(vm);
SelectedFilter = vm;
@@ -144,7 +162,9 @@ private void CopyFilter(FilterViewModel filter) {
}
private bool CanCopyFilter(FilterViewModel filter) {
- return filter is not null && filter.FilterProvider.CanGetFilter(out _);
+ return filter is not null
+ && filter.FilterProvider.CanGetFilter(out _)
+ && filter.MaterialsFilterProvider.CanGetFilter(out _);
}
private void RemoveFilters(IList items) {
@@ -166,7 +186,7 @@ private bool CanRemoveFilters(IList items) {
private void Save() {
_filtersRepo.SetFilters(GetFilterModels(Filters));
foreach(var vm in Filters) {
- vm.Modified = false;
+ vm.AcceptChanges();
}
FiltersModified = false;
@@ -186,7 +206,18 @@ private void Export() {
private bool CanSave() {
foreach(var vm in Filters) {
if(!vm.FilterProvider.CanGetFilter(out var errors)) {
- ErrorText = $"{vm.Name}: {errors.FirstOrDefault()?.Message}";
+ ErrorText = $"{vm.Name}: "
+ + _localization.GetLocalizedString(
+ "FiltrationPage.Error.ElementsFilter",
+ errors.FirstOrDefault()?.Message ?? string.Empty);
+ return false;
+ }
+
+ if(!vm.MaterialsFilterProvider.CanGetFilter(out var materialsErrors)) {
+ ErrorText = $"{vm.Name}: "
+ + _localization.GetLocalizedString(
+ "FiltrationPage.Error.MaterialsFilter",
+ materialsErrors.FirstOrDefault()?.Message ?? string.Empty);
return false;
}
}
@@ -198,7 +229,8 @@ private bool CanSave() {
private Filter[] GetFilterModels(IEnumerable filterViewModels) {
return filterViewModels.Select(f => new Filter() {
Name = f.Name,
- FilterContext = _filterContextParser.Serialize(f.FilterProvider.GetFilter())
+ FilterContext = _filterContextParser.Serialize(f.FilterProvider.GetFilter()),
+ MaterialsFilterContext = _filterContextParser.Serialize(f.MaterialsFilterProvider.GetFilter())
})
.ToArray();
}
@@ -249,7 +281,11 @@ private void OnFilterChanged(object sender, PropertyChangedEventArgs e) {
}
private FilterViewModel GetFilterViewModel(Filter filter) {
- return new FilterViewModel(filter.Name, GetFilterProvider(filter.FilterContext)) { Modified = false };
+ return new FilterViewModel(
+ filter.Name,
+ GetFilterProvider(filter.FilterContext),
+ GetMaterialsFilterProvider(filter.MaterialsFilterContext),
+ _filterContextParser) { Modified = false };
}
private ILogicalFilterProvider GetFilterProvider(string serializedContext = "") {
@@ -264,4 +300,23 @@ private ILogicalFilterProvider GetFilterProvider(string serializedContext = "")
return _filterProviderFactory.Create(_dataProvider);
}
}
+
+ ///
+ /// Создает провайдер фильтра по параметрам материалов элементов.
+ ///
+ /// Сериализованный контекст фильтра.
+ ///
+ /// Если контекст не задан, либо его не удалось прочитать, создается провайдер с пустым набором правил.
+ ///
+ private ILogicalFilterProvider GetMaterialsFilterProvider(string serializedContext = "") {
+ if(!string.IsNullOrWhiteSpace(serializedContext)
+ && _filterContextParser.TryParse(serializedContext, out var context)) {
+ return _filterProviderFactory.Create(_materialsDataProvider, context!);
+ }
+
+ return _filterProviderFactory.Create(
+ _materialsDataProvider,
+ _filterFactory.CreateAndFilter(),
+ [BuiltInCategory.OST_Materials]);
+ }
}
diff --git a/src/RevitParamsChecker/Views/Filtration/FiltrationPage.xaml b/src/RevitParamsChecker/Views/Filtration/FiltrationPage.xaml
index ddc3c9587..4042dfdf3 100644
--- a/src/RevitParamsChecker/Views/Filtration/FiltrationPage.xaml
+++ b/src/RevitParamsChecker/Views/Filtration/FiltrationPage.xaml
@@ -79,6 +79,21 @@
Grid.Row="1"
Grid.Column="2"
LanguageService="{Binding LanguageService}"
- LogicalFilterProvider="{Binding SelectedFilter.FilterProvider}" />
+ LocalizationService="{Binding LocalizationService}"
+ LogicalFilterProvider="{Binding SelectedFilter.FilterProvider}">
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/RevitParamsChecker/assets/Localization/Language.en-US.xaml b/src/RevitParamsChecker/assets/Localization/Language.en-US.xaml
index e4905b430..397b58004 100644
--- a/src/RevitParamsChecker/assets/Localization/Language.en-US.xaml
+++ b/src/RevitParamsChecker/assets/Localization/Language.en-US.xaml
@@ -149,6 +149,14 @@ Previously selected checks are missing:
x:Key="FiltrationPage.SaveFileDefaultName">Search Sets Configuration
Failed to load search sets
+ Element materials parameters filter criteria
+ Element parameters filter: {0}
+ Element materials parameters filter: {0}
+ Element parameters filter criteria
Load
diff --git a/src/RevitParamsChecker/assets/Localization/Language.ru-RU.xaml b/src/RevitParamsChecker/assets/Localization/Language.ru-RU.xaml
index 257b5d3b9..21ade2d2f 100644
--- a/src/RevitParamsChecker/assets/Localization/Language.ru-RU.xaml
+++ b/src/RevitParamsChecker/assets/Localization/Language.ru-RU.xaml
@@ -149,6 +149,14 @@
x:Key="FiltrationPage.SaveFileDefaultName">Настройка поисковых наборов
Не удалось загрузить поисковые наборы
+ Критерии фильтрации по параметрам материалов элементов
+ Фильтр по параметрам элементов: {0}
+ Фильтр по параметрам материалов элементов: {0}
+ Критерии фильтрации по параметрам элементов
Загрузить