Загружать: ВСЕГДА. Нарушение любого инварианта = баг или Fatal Error в Revit.
Revit API однопоточен. Любой вызов API из WPF-потока — только через IExternalEventHandler.Execute().
Запрещено:
- Вызов Revit API из
async/await - Вызов Revit API из
Task.Run() - Вызов Revit API из обработчиков WPF-событий напрямую
Правильно:
// WPF UI thread:
_externalEvent.Raise();
// Revit main thread (Execute):
_transactionService.RunInTransaction("name", doc => { /* Revit API здесь */ });Весь Core работает в decimal feet (Internal Units Revit).
Конвертация в мм/м — только в точках входа (UI-слой):
UnitUtils.ConvertToInternalUnits(value, UnitTypeId.Millimeters)
UnitUtils.ConvertFromInternalUnits(value, UnitTypeId.Millimeters)Создавать new Transaction(doc) напрямую запрещено вне RevitTransactionService.
Весь код пишет только:
_transactionService.RunInTransaction("Name", doc => { ... });Каждый Transaction / TransactionGroup оборачивается в using-блок внутри сервиса.
new Transaction(familyDoc, ...) разрешён при работе с family document,
полученным через doc.EditFamily(family). Family document — это отдельный
Document, не управляемый ITransactionService (у него свой стек транзакций).
Где применяется:
FittingCtcManager.ApplyFittingCtcToFamily— запись CTC описаний коннекторов в family.RevitFamilyConnectorService.SetConnectorTypeCode— запись описания коннектора в family.
Правило: new Transaction используется только для family doc. Проектный
Document всегда через ITransactionService. После commit/load family doc
закрывается (familyDoc.Close(false)).
Вся операция соединения труб оборачивается в TransactionGroup:
- Соединить →
TransactionGroup.Assimilate()— одна запись Undo (Ctrl+Z) - Отмена →
TransactionGroup.RollBack()— полный откат
Объекты Element, Connector, FamilySymbol — не хранить между транзакциями.
Хранить: только ElementId (или UniqueId для workshared-проектов).
Запрашивать: актуальный объект через doc.GetElement(id) в начале каждой операции.
DisplayUnitType удалён начиная с Revit 2022. Использовать только:
UnitTypeId(напримерUnitTypeId.Millimeters)ForgeTypeId
Все транзакции в RevitTransactionService подключают SmartConFailurePreprocessor.
Он тихо подавляет ожидаемые предупреждения:
- "Element is slightly off axis"
- Другие предупреждения при перемещении/повороте
var options = transaction.GetFailureHandlingOptions();
options.SetFailuresPreprocessor(new SmartConFailurePreprocessor());
transaction.SetFailureHandlingOptions(options);Коннекторы с ConnectorType == ConnectorType.Curve (врезки) исключаются из фильтра выбора FreeConnectorFilter.
CoordinateSystem.BasisZ у них может давать неверное направление.
SmartCon.Core ссылается на RevitAPI.dll только как compile-time reference (CopyLocal = false).
Разрешено в Core:
- Использовать Revit value-типы как data carriers в моделях и сигнатурах интерфейсов:
ElementId,XYZ,Domain,BuiltInParameter,ForgeTypeId - Использовать
Documentкак opaque parameter в интерфейсах (передаётся, но не вызываются его методы). Примеры:IShareProjectSettingsRepository.Load(Document doc) using Autodesk.Revit.DB;— только в файлах моделей и интерфейсов для объявления типов- Для чистой математики (VectorUtils, ConnectorAligner) использовать
Vec3вместоXYZ(ADR-009). Конвертация на границе Revit-слоя.
Запрещено в Core:
- Вызывать методы Revit API (
doc.GetElement(),Element.get_Parameter(),Transaction, и т.д.) using Autodesk.Revit.UI;using System.Windows;- Создавать экземпляры Revit-классов (кроме
new ElementId(long))
Принцип: Core описывает контракты через Revit-типы, но никогда не вызывает Revit API. Вся логика вызовов — в SmartCon.Revit.
Проверять при каждом коммите.
Файлы *.xaml.cs содержат только:
public partial class SomeView : Window
{
public SomeView(SomeViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
}
}- Вся логика — во ViewModel (наследуют
ObservableObjectиз CommunityToolkit.Mvvm) - Свойства — через
[ObservableProperty]source generator - Команды — через
[RelayCommand]source generator илиRelayCommandиз CommunityToolkit - Все биндинги — через
{Binding}в XAML - Открытие окон — через
IDialogService, неnew Window().ShowDialog()
Исключение: BindCloseRequest(viewModel) в конструкторах диалоговых окон (DialogWindowBase) — допустимый паттерн для поддержки закрытия окна из ViewModel через IObservableRequestClose.
Исключение: Программная установка заголовков DataGridColumn.Header через x:Name в code-behind (см. I-12).
ElementIdCompat в SmartCon.Core/Compatibility/ — единственный допустимый класс в Core, зависящий от RevitAPI (carrier-тип ElementId). Новые классы с RevitAPI-зависимостью в Core — запрещены без явного ревью архитектора.
Мотивация: Multi-version support (Revit 2021-2025) требует абстракции над различиями 32/64-bit ElementId. ElementIdCompat решает это через #if REVIT2024_OR_GREATER.
DataGridColumn не наследует от FrameworkElement, поэтому {DynamicResource} в Header не резолвится когда Application.Current == null (Revit не создаёт WPF Application).
Правильно: задавать заголовки программно через x:Name в code-behind:
<!-- XAML -->
<DataGridTextColumn x:Name="ColCode" Binding="{Binding Code}" Width="80">// Code-behind
ColCode.Header = LanguageManager.GetString(StringLocalization.Keys.Col_Code);Запрещено: Header="{DynamicResource Col_Code}" — работает только на net8.0 (Revit 2025), молча пусто на net48 (Revit 2021-2024).
Подробнее: multi-version-guide.md, Правило 4.
Настройки ConnectorTypes и FittingMappingRules хранятся исключительно в
DataStorage (ExtensibleStorage.Schema = SmartConFittingMappingSchema)
текущего Revit Document — ADR-012.
Запрещено:
- Автоматически читать/мигрировать данные из
%APPDATA%\AGK\SmartCon\connector-mapping.json. - Открывать второй файловый кэш параллельно с DataStorage (единый источник правды).
- Обращаться к ExtensibleStorage напрямую из Core — только через
IFittingMappingRepository(реализацияRevitFittingMappingRepositoryвSmartCon.Revit/Storage/).
Разрешено:
- Ручной Import/Export JSON через окно Settings (
ShowOpenJsonDialog/ShowSaveJsonDialog). Диалог Импорта может по умолчанию открываться в AppData-папке для удобства миграции. - Читать DataStorage напрямую для диагностики (AccessLevel =
Public), но не писать (WriteAccess =Vendor).
Мотивация: Разные .rvt содержат разные семейства фитингов → правила
должны быть привязаны к проекту и путешествовать вместе с моделью.
Авто-миграция из AppData создаёт «невидимые» импорты, которые
дезориентируют пользователя и могут загрузить устаревшие правила в новый
проект.
Подробнее: adr/012-per-project-extensible-storage.md.
Все SQLite-операции идут через LocalCatalogDatabase:
- DELETE journal mode для универсальной совместимости (локальные диски и сетевые SMB)
- DELETE mode используется universally для любых путей (локальные D:/C: и сетевые UNC)
- Только один writer одновременно (ограничение SQLite)
LocalCatalogDatabase.SwitchToPathзащищён lock-омnew SqliteConnection()внеLocalCatalogDatabaseзапрещён
FamilyManagerPaneProvider— singleton, регистрируется один раз вOnStartup- Панель не пересоздаётся при клике на кнопку — только
Show()/Hide()черезDockablePane - ViewModel — singleton на экземпляр панели
- Состояние панели сохраняется между show/hide
.rfa файлы в managed storage (%APPDATA%\SmartCon\FamilyManager\databases\{id}\storage\) — read-only после импорта:
- Прямое изменение файлов запрещено — изменения = новая версия (ADR-016)
Sha256FileHasherверифицирует целостность файла при чтенииIFamilyFileResolver— единственная точка входа для доступа к файлам- Исключение:
OverwriteCurrent— явное действие пользователя через batch dialog (комбо-бокс "Перезаписать текущую версию"). При OverwriteCurrent managed-файл текущей версии перезаписывается по тому же пути, а запись вcatalog_versionsUPDATE (а не INSERT новой строки). См. ADR-040 и ADR-016 §"Exception: OverwriteCurrent"
Exa — единственный инструмент для веб-поиска. Context7 — только для официальной документации библиотек и фреймворков с примерами кода.
Exa используй для:
- Примеров кода с Revit API, форумов Autodesk, Jeremy Tammik, StackOverflow, GitHub
- Поиска best practices, известных проблем, крашей, edge cases
- Любых веб-источников (блоги, open source плагины, форумы, Autodesk Community)
- Общих алгоритмов и паттернов программирования
Context7 используй ТОЛЬКО для:
- Официальной документации библиотек и фреймворков (.NET, CommunityToolkit.Mvvm, Moq, Microsoft.Data.Sqlite, и т.д.)
- Получения актуальных сигнатур, API reference, примеров кода из официальных источников
- Проверки version-specific поведения библиотек
- Чтения документации конкретной библиотеки, когда известно её имя
ЗАПРЕЩЕНО:
- Использовать Context7 как универсальный поисковик для всего подряд
- Использовать Context7 для Revit API — для Revit API используй Exa и MCP
revit-api-docs - Использовать Context7 для форумов, блогов, StackOverflow, GitHub (кроме официальных документов библиотек)
- Использовать Exa для чтения официальной документации по библиотеке, если тот же ответ можно получить из Context7 (Context7 точнее и короче)
Pipeline:
- Нужен пример кода / best practice / форум / edge case →
exa_web_search_exa - Нужна официальная документация библиотеки с примерами →
context7_resolve-library-id→context7_query-docs - Нужна сигнатура Revit API → MCP
revit-api-docs - Нужна версия NuGet-пакета → Exa (
exa_web_search_exa)