diff --git a/src/cpp/vim/object-model.h b/src/cpp/vim/object-model.h index 7a37add..6322522 100644 --- a/src/cpp/vim/object-model.h +++ b/src/cpp/vim/object-model.h @@ -126,6 +126,18 @@ namespace Vim class LightSourceTable; class SunAndShadowSettings; class SunAndShadowSettingsTable; + class ExportRevision; + class ExportRevisionTable; + class ElementInExportRevision; + class ElementInExportRevisionTable; + class Contributor; + class ContributorTable; + class SyncSession; + class SyncSessionTable; + class ElementChange; + class ElementChangeTable; + class ActivityMarker; + class ActivityMarkerTable; class DocumentModel { @@ -187,6 +199,12 @@ namespace Vim LightTypeTable* mLightType; LightSourceTable* mLightSource; SunAndShadowSettingsTable* mSunAndShadowSettings; + ExportRevisionTable* mExportRevision; + ElementInExportRevisionTable* mElementInExportRevision; + ContributorTable* mContributor; + SyncSessionTable* mSyncSession; + ElementChangeTable* mElementChange; + ActivityMarkerTable* mActivityMarker; DocumentModel(Scene& scene); ~DocumentModel(); @@ -16362,6 +16380,1508 @@ namespace Vim return new SunAndShadowSettingsTable(scene.mEntityTables["Vim.SunAndShadowSettings"], scene.mStrings); } + class ExportRevision + { + public: + int mIndex; + std::string mVimRevision; + int mKind; + std::string mGenerator; + std::string mCreatedUtc; + int mDocumentNumSaves; + std::string mDocumentVersionGuid; + + int mBimDocumentIndex; + BimDocument* mBimDocument; + + ExportRevision() {} + }; + + class ExportRevisionTable + { + EntityTable& mEntityTable; + std::vector& mStrings; + public: + ExportRevisionTable(EntityTable& entityTable, std::vector& strings): + mEntityTable(entityTable), mStrings(strings) {} + + size_t GetCount() + { + return mEntityTable.get_count(); + } + + ExportRevision* Get(int exportRevisionIndex) + { + ExportRevision* exportRevision = new ExportRevision(); + exportRevision->mIndex = exportRevisionIndex; + exportRevision->mVimRevision = GetVimRevision(exportRevisionIndex); + exportRevision->mKind = GetKind(exportRevisionIndex); + exportRevision->mGenerator = GetGenerator(exportRevisionIndex); + exportRevision->mCreatedUtc = GetCreatedUtc(exportRevisionIndex); + exportRevision->mDocumentNumSaves = GetDocumentNumSaves(exportRevisionIndex); + exportRevision->mDocumentVersionGuid = GetDocumentVersionGuid(exportRevisionIndex); + exportRevision->mBimDocumentIndex = GetBimDocumentIndex(exportRevisionIndex); + return exportRevision; + } + + std::vector* GetAll() + { + bool existsVimRevision = mEntityTable.column_exists("string:VimRevision"); + bool existsKind = mEntityTable.column_exists("int:Kind"); + bool existsGenerator = mEntityTable.column_exists("string:Generator"); + bool existsCreatedUtc = mEntityTable.column_exists("string:CreatedUtc"); + bool existsDocumentNumSaves = mEntityTable.column_exists("int:DocumentNumSaves"); + bool existsDocumentVersionGuid = mEntityTable.column_exists("string:DocumentVersionGuid"); + bool existsBimDocument = mEntityTable.column_exists("index:Vim.BimDocument:BimDocument"); + + const auto count = GetCount(); + + std::vector* exportRevision = new std::vector(); + exportRevision->reserve(count); + + const std::vector& vimRevisionData = mEntityTable.column_exists("string:VimRevision") ? mEntityTable.mStringColumns["string:VimRevision"] : std::vector(); + + int* kindData = new int[count]; + if (mEntityTable.column_exists("int:Kind")) { + memcpy(kindData, mEntityTable.mDataColumns["int:Kind"].begin(), count * sizeof(int)); + } + + const std::vector& generatorData = mEntityTable.column_exists("string:Generator") ? mEntityTable.mStringColumns["string:Generator"] : std::vector(); + + const std::vector& createdUtcData = mEntityTable.column_exists("string:CreatedUtc") ? mEntityTable.mStringColumns["string:CreatedUtc"] : std::vector(); + + int* documentNumSavesData = new int[count]; + if (mEntityTable.column_exists("int:DocumentNumSaves")) { + memcpy(documentNumSavesData, mEntityTable.mDataColumns["int:DocumentNumSaves"].begin(), count * sizeof(int)); + } + + const std::vector& documentVersionGuidData = mEntityTable.column_exists("string:DocumentVersionGuid") ? mEntityTable.mStringColumns["string:DocumentVersionGuid"] : std::vector(); + + const std::vector& bimDocumentData = mEntityTable.column_exists("index:Vim.BimDocument:BimDocument") ? mEntityTable.mIndexColumns["index:Vim.BimDocument:BimDocument"] : std::vector(); + + for (int i = 0; i < count; ++i) + { + ExportRevision entity; + entity.mIndex = i; + if (existsVimRevision) + entity.mVimRevision = std::string(reinterpret_cast(mStrings[vimRevisionData[i]])); + if (existsKind) + entity.mKind = kindData[i]; + if (existsGenerator) + entity.mGenerator = std::string(reinterpret_cast(mStrings[generatorData[i]])); + if (existsCreatedUtc) + entity.mCreatedUtc = std::string(reinterpret_cast(mStrings[createdUtcData[i]])); + if (existsDocumentNumSaves) + entity.mDocumentNumSaves = documentNumSavesData[i]; + if (existsDocumentVersionGuid) + entity.mDocumentVersionGuid = std::string(reinterpret_cast(mStrings[documentVersionGuidData[i]])); + entity.mBimDocumentIndex = existsBimDocument ? bimDocumentData[i] : -1; + exportRevision->push_back(entity); + } + + delete[] kindData; + delete[] documentNumSavesData; + + return exportRevision; + } + + std::string GetVimRevision(int exportRevisionIndex) + { + if (exportRevisionIndex < 0 || exportRevisionIndex >= GetCount()) + return {}; + + if (mEntityTable.column_exists("string:VimRevision")) { + return std::string(reinterpret_cast(mStrings[mEntityTable.mStringColumns["string:VimRevision"][exportRevisionIndex]])); + } + + return {}; + } + + std::vector* GetAllVimRevision() + { + const auto count = GetCount(); + + const std::vector& vimRevisionData = mEntityTable.column_exists("string:VimRevision") ? mEntityTable.mStringColumns["string:VimRevision"] : std::vector(); + + std::vector* result = new std::vector(); + result->reserve(count); + + for (int i = 0; i < count; ++i) + { + result->push_back(std::string(reinterpret_cast(mStrings[vimRevisionData[i]]))); + } + + return result; + } + + int GetKind(int exportRevisionIndex) + { + if (exportRevisionIndex < 0 || exportRevisionIndex >= GetCount()) + return {}; + + if (mEntityTable.column_exists("int:Kind")) { + return *reinterpret_cast(const_cast(mEntityTable.mDataColumns["int:Kind"].begin() + exportRevisionIndex * sizeof(int))); + } + + return {}; + } + + std::vector* GetAllKind() + { + const auto count = GetCount(); + + int* kindData = new int[count]; + if (mEntityTable.column_exists("int:Kind")) { + memcpy(kindData, mEntityTable.mDataColumns["int:Kind"].begin(), count * sizeof(int)); + } + + std::vector* result = new std::vector(kindData, kindData + count); + + delete[] kindData; + + return result; + } + + std::string GetGenerator(int exportRevisionIndex) + { + if (exportRevisionIndex < 0 || exportRevisionIndex >= GetCount()) + return {}; + + if (mEntityTable.column_exists("string:Generator")) { + return std::string(reinterpret_cast(mStrings[mEntityTable.mStringColumns["string:Generator"][exportRevisionIndex]])); + } + + return {}; + } + + std::vector* GetAllGenerator() + { + const auto count = GetCount(); + + const std::vector& generatorData = mEntityTable.column_exists("string:Generator") ? mEntityTable.mStringColumns["string:Generator"] : std::vector(); + + std::vector* result = new std::vector(); + result->reserve(count); + + for (int i = 0; i < count; ++i) + { + result->push_back(std::string(reinterpret_cast(mStrings[generatorData[i]]))); + } + + return result; + } + + std::string GetCreatedUtc(int exportRevisionIndex) + { + if (exportRevisionIndex < 0 || exportRevisionIndex >= GetCount()) + return {}; + + if (mEntityTable.column_exists("string:CreatedUtc")) { + return std::string(reinterpret_cast(mStrings[mEntityTable.mStringColumns["string:CreatedUtc"][exportRevisionIndex]])); + } + + return {}; + } + + std::vector* GetAllCreatedUtc() + { + const auto count = GetCount(); + + const std::vector& createdUtcData = mEntityTable.column_exists("string:CreatedUtc") ? mEntityTable.mStringColumns["string:CreatedUtc"] : std::vector(); + + std::vector* result = new std::vector(); + result->reserve(count); + + for (int i = 0; i < count; ++i) + { + result->push_back(std::string(reinterpret_cast(mStrings[createdUtcData[i]]))); + } + + return result; + } + + int GetDocumentNumSaves(int exportRevisionIndex) + { + if (exportRevisionIndex < 0 || exportRevisionIndex >= GetCount()) + return {}; + + if (mEntityTable.column_exists("int:DocumentNumSaves")) { + return *reinterpret_cast(const_cast(mEntityTable.mDataColumns["int:DocumentNumSaves"].begin() + exportRevisionIndex * sizeof(int))); + } + + return {}; + } + + std::vector* GetAllDocumentNumSaves() + { + const auto count = GetCount(); + + int* documentNumSavesData = new int[count]; + if (mEntityTable.column_exists("int:DocumentNumSaves")) { + memcpy(documentNumSavesData, mEntityTable.mDataColumns["int:DocumentNumSaves"].begin(), count * sizeof(int)); + } + + std::vector* result = new std::vector(documentNumSavesData, documentNumSavesData + count); + + delete[] documentNumSavesData; + + return result; + } + + std::string GetDocumentVersionGuid(int exportRevisionIndex) + { + if (exportRevisionIndex < 0 || exportRevisionIndex >= GetCount()) + return {}; + + if (mEntityTable.column_exists("string:DocumentVersionGuid")) { + return std::string(reinterpret_cast(mStrings[mEntityTable.mStringColumns["string:DocumentVersionGuid"][exportRevisionIndex]])); + } + + return {}; + } + + std::vector* GetAllDocumentVersionGuid() + { + const auto count = GetCount(); + + const std::vector& documentVersionGuidData = mEntityTable.column_exists("string:DocumentVersionGuid") ? mEntityTable.mStringColumns["string:DocumentVersionGuid"] : std::vector(); + + std::vector* result = new std::vector(); + result->reserve(count); + + for (int i = 0; i < count; ++i) + { + result->push_back(std::string(reinterpret_cast(mStrings[documentVersionGuidData[i]]))); + } + + return result; + } + + int GetBimDocumentIndex(int exportRevisionIndex) + { + if (!mEntityTable.column_exists("index:Vim.BimDocument:BimDocument")) { + return -1; + } + + if (exportRevisionIndex < 0 || exportRevisionIndex >= GetCount()) + return -1; + + return mEntityTable.mIndexColumns["index:Vim.BimDocument:BimDocument"][exportRevisionIndex]; + } + + }; + + static ExportRevisionTable* GetExportRevisionTable(Scene& scene) + { + if (scene.mEntityTables.find("Vim.ExportRevision") == scene.mEntityTables.end()) + return {}; + + return new ExportRevisionTable(scene.mEntityTables["Vim.ExportRevision"], scene.mStrings); + } + + class ElementInExportRevision + { + public: + int mIndex; + + int mExportRevisionIndex; + ExportRevision* mExportRevision; + int mElementIndex; + Element* mElement; + + ElementInExportRevision() {} + }; + + class ElementInExportRevisionTable + { + EntityTable& mEntityTable; + std::vector& mStrings; + public: + ElementInExportRevisionTable(EntityTable& entityTable, std::vector& strings): + mEntityTable(entityTable), mStrings(strings) {} + + size_t GetCount() + { + return mEntityTable.get_count(); + } + + ElementInExportRevision* Get(int elementInExportRevisionIndex) + { + ElementInExportRevision* elementInExportRevision = new ElementInExportRevision(); + elementInExportRevision->mIndex = elementInExportRevisionIndex; + elementInExportRevision->mExportRevisionIndex = GetExportRevisionIndex(elementInExportRevisionIndex); + elementInExportRevision->mElementIndex = GetElementIndex(elementInExportRevisionIndex); + return elementInExportRevision; + } + + std::vector* GetAll() + { + bool existsExportRevision = mEntityTable.column_exists("index:Vim.ExportRevision:ExportRevision"); + bool existsElement = mEntityTable.column_exists("index:Vim.Element:Element"); + + const auto count = GetCount(); + + std::vector* elementInExportRevision = new std::vector(); + elementInExportRevision->reserve(count); + + const std::vector& exportRevisionData = mEntityTable.column_exists("index:Vim.ExportRevision:ExportRevision") ? mEntityTable.mIndexColumns["index:Vim.ExportRevision:ExportRevision"] : std::vector(); + const std::vector& elementData = mEntityTable.column_exists("index:Vim.Element:Element") ? mEntityTable.mIndexColumns["index:Vim.Element:Element"] : std::vector(); + + for (int i = 0; i < count; ++i) + { + ElementInExportRevision entity; + entity.mIndex = i; + entity.mExportRevisionIndex = existsExportRevision ? exportRevisionData[i] : -1; + entity.mElementIndex = existsElement ? elementData[i] : -1; + elementInExportRevision->push_back(entity); + } + + return elementInExportRevision; + } + + int GetExportRevisionIndex(int elementInExportRevisionIndex) + { + if (!mEntityTable.column_exists("index:Vim.ExportRevision:ExportRevision")) { + return -1; + } + + if (elementInExportRevisionIndex < 0 || elementInExportRevisionIndex >= GetCount()) + return -1; + + return mEntityTable.mIndexColumns["index:Vim.ExportRevision:ExportRevision"][elementInExportRevisionIndex]; + } + + int GetElementIndex(int elementInExportRevisionIndex) + { + if (!mEntityTable.column_exists("index:Vim.Element:Element")) { + return -1; + } + + if (elementInExportRevisionIndex < 0 || elementInExportRevisionIndex >= GetCount()) + return -1; + + return mEntityTable.mIndexColumns["index:Vim.Element:Element"][elementInExportRevisionIndex]; + } + + }; + + static ElementInExportRevisionTable* GetElementInExportRevisionTable(Scene& scene) + { + if (scene.mEntityTables.find("Vim.ElementInExportRevision") == scene.mEntityTables.end()) + return {}; + + return new ElementInExportRevisionTable(scene.mEntityTables["Vim.ElementInExportRevision"], scene.mStrings); + } + + class Contributor + { + public: + int mIndex; + std::string mName; + int mSource; + + Contributor() {} + }; + + class ContributorTable + { + EntityTable& mEntityTable; + std::vector& mStrings; + public: + ContributorTable(EntityTable& entityTable, std::vector& strings): + mEntityTable(entityTable), mStrings(strings) {} + + size_t GetCount() + { + return mEntityTable.get_count(); + } + + Contributor* Get(int contributorIndex) + { + Contributor* contributor = new Contributor(); + contributor->mIndex = contributorIndex; + contributor->mName = GetName(contributorIndex); + contributor->mSource = GetSource(contributorIndex); + return contributor; + } + + std::vector* GetAll() + { + bool existsName = mEntityTable.column_exists("string:Name"); + bool existsSource = mEntityTable.column_exists("int:Source"); + + const auto count = GetCount(); + + std::vector* contributor = new std::vector(); + contributor->reserve(count); + + const std::vector& nameData = mEntityTable.column_exists("string:Name") ? mEntityTable.mStringColumns["string:Name"] : std::vector(); + + int* sourceData = new int[count]; + if (mEntityTable.column_exists("int:Source")) { + memcpy(sourceData, mEntityTable.mDataColumns["int:Source"].begin(), count * sizeof(int)); + } + + for (int i = 0; i < count; ++i) + { + Contributor entity; + entity.mIndex = i; + if (existsName) + entity.mName = std::string(reinterpret_cast(mStrings[nameData[i]])); + if (existsSource) + entity.mSource = sourceData[i]; + contributor->push_back(entity); + } + + delete[] sourceData; + + return contributor; + } + + std::string GetName(int contributorIndex) + { + if (contributorIndex < 0 || contributorIndex >= GetCount()) + return {}; + + if (mEntityTable.column_exists("string:Name")) { + return std::string(reinterpret_cast(mStrings[mEntityTable.mStringColumns["string:Name"][contributorIndex]])); + } + + return {}; + } + + std::vector* GetAllName() + { + const auto count = GetCount(); + + const std::vector& nameData = mEntityTable.column_exists("string:Name") ? mEntityTable.mStringColumns["string:Name"] : std::vector(); + + std::vector* result = new std::vector(); + result->reserve(count); + + for (int i = 0; i < count; ++i) + { + result->push_back(std::string(reinterpret_cast(mStrings[nameData[i]]))); + } + + return result; + } + + int GetSource(int contributorIndex) + { + if (contributorIndex < 0 || contributorIndex >= GetCount()) + return {}; + + if (mEntityTable.column_exists("int:Source")) { + return *reinterpret_cast(const_cast(mEntityTable.mDataColumns["int:Source"].begin() + contributorIndex * sizeof(int))); + } + + return {}; + } + + std::vector* GetAllSource() + { + const auto count = GetCount(); + + int* sourceData = new int[count]; + if (mEntityTable.column_exists("int:Source")) { + memcpy(sourceData, mEntityTable.mDataColumns["int:Source"].begin(), count * sizeof(int)); + } + + std::vector* result = new std::vector(sourceData, sourceData + count); + + delete[] sourceData; + + return result; + } + + }; + + static ContributorTable* GetContributorTable(Scene& scene) + { + if (scene.mEntityTables.find("Vim.Contributor") == scene.mEntityTables.end()) + return {}; + + return new ContributorTable(scene.mEntityTables["Vim.Contributor"], scene.mStrings); + } + + class SyncSession + { + public: + int mIndex; + std::string mSessionGuid; + std::string mMachineName; + std::string mRevitVersion; + std::string mStartedUtc; + std::string mEndedUtc; + + int mContributorIndex; + Contributor* mContributor; + int mBimDocumentIndex; + BimDocument* mBimDocument; + + SyncSession() {} + }; + + class SyncSessionTable + { + EntityTable& mEntityTable; + std::vector& mStrings; + public: + SyncSessionTable(EntityTable& entityTable, std::vector& strings): + mEntityTable(entityTable), mStrings(strings) {} + + size_t GetCount() + { + return mEntityTable.get_count(); + } + + SyncSession* Get(int syncSessionIndex) + { + SyncSession* syncSession = new SyncSession(); + syncSession->mIndex = syncSessionIndex; + syncSession->mSessionGuid = GetSessionGuid(syncSessionIndex); + syncSession->mMachineName = GetMachineName(syncSessionIndex); + syncSession->mRevitVersion = GetRevitVersion(syncSessionIndex); + syncSession->mStartedUtc = GetStartedUtc(syncSessionIndex); + syncSession->mEndedUtc = GetEndedUtc(syncSessionIndex); + syncSession->mContributorIndex = GetContributorIndex(syncSessionIndex); + syncSession->mBimDocumentIndex = GetBimDocumentIndex(syncSessionIndex); + return syncSession; + } + + std::vector* GetAll() + { + bool existsSessionGuid = mEntityTable.column_exists("string:SessionGuid"); + bool existsMachineName = mEntityTable.column_exists("string:MachineName"); + bool existsRevitVersion = mEntityTable.column_exists("string:RevitVersion"); + bool existsStartedUtc = mEntityTable.column_exists("string:StartedUtc"); + bool existsEndedUtc = mEntityTable.column_exists("string:EndedUtc"); + bool existsContributor = mEntityTable.column_exists("index:Vim.Contributor:Contributor"); + bool existsBimDocument = mEntityTable.column_exists("index:Vim.BimDocument:BimDocument"); + + const auto count = GetCount(); + + std::vector* syncSession = new std::vector(); + syncSession->reserve(count); + + const std::vector& sessionGuidData = mEntityTable.column_exists("string:SessionGuid") ? mEntityTable.mStringColumns["string:SessionGuid"] : std::vector(); + + const std::vector& machineNameData = mEntityTable.column_exists("string:MachineName") ? mEntityTable.mStringColumns["string:MachineName"] : std::vector(); + + const std::vector& revitVersionData = mEntityTable.column_exists("string:RevitVersion") ? mEntityTable.mStringColumns["string:RevitVersion"] : std::vector(); + + const std::vector& startedUtcData = mEntityTable.column_exists("string:StartedUtc") ? mEntityTable.mStringColumns["string:StartedUtc"] : std::vector(); + + const std::vector& endedUtcData = mEntityTable.column_exists("string:EndedUtc") ? mEntityTable.mStringColumns["string:EndedUtc"] : std::vector(); + + const std::vector& contributorData = mEntityTable.column_exists("index:Vim.Contributor:Contributor") ? mEntityTable.mIndexColumns["index:Vim.Contributor:Contributor"] : std::vector(); + const std::vector& bimDocumentData = mEntityTable.column_exists("index:Vim.BimDocument:BimDocument") ? mEntityTable.mIndexColumns["index:Vim.BimDocument:BimDocument"] : std::vector(); + + for (int i = 0; i < count; ++i) + { + SyncSession entity; + entity.mIndex = i; + if (existsSessionGuid) + entity.mSessionGuid = std::string(reinterpret_cast(mStrings[sessionGuidData[i]])); + if (existsMachineName) + entity.mMachineName = std::string(reinterpret_cast(mStrings[machineNameData[i]])); + if (existsRevitVersion) + entity.mRevitVersion = std::string(reinterpret_cast(mStrings[revitVersionData[i]])); + if (existsStartedUtc) + entity.mStartedUtc = std::string(reinterpret_cast(mStrings[startedUtcData[i]])); + if (existsEndedUtc) + entity.mEndedUtc = std::string(reinterpret_cast(mStrings[endedUtcData[i]])); + entity.mContributorIndex = existsContributor ? contributorData[i] : -1; + entity.mBimDocumentIndex = existsBimDocument ? bimDocumentData[i] : -1; + syncSession->push_back(entity); + } + + return syncSession; + } + + std::string GetSessionGuid(int syncSessionIndex) + { + if (syncSessionIndex < 0 || syncSessionIndex >= GetCount()) + return {}; + + if (mEntityTable.column_exists("string:SessionGuid")) { + return std::string(reinterpret_cast(mStrings[mEntityTable.mStringColumns["string:SessionGuid"][syncSessionIndex]])); + } + + return {}; + } + + std::vector* GetAllSessionGuid() + { + const auto count = GetCount(); + + const std::vector& sessionGuidData = mEntityTable.column_exists("string:SessionGuid") ? mEntityTable.mStringColumns["string:SessionGuid"] : std::vector(); + + std::vector* result = new std::vector(); + result->reserve(count); + + for (int i = 0; i < count; ++i) + { + result->push_back(std::string(reinterpret_cast(mStrings[sessionGuidData[i]]))); + } + + return result; + } + + std::string GetMachineName(int syncSessionIndex) + { + if (syncSessionIndex < 0 || syncSessionIndex >= GetCount()) + return {}; + + if (mEntityTable.column_exists("string:MachineName")) { + return std::string(reinterpret_cast(mStrings[mEntityTable.mStringColumns["string:MachineName"][syncSessionIndex]])); + } + + return {}; + } + + std::vector* GetAllMachineName() + { + const auto count = GetCount(); + + const std::vector& machineNameData = mEntityTable.column_exists("string:MachineName") ? mEntityTable.mStringColumns["string:MachineName"] : std::vector(); + + std::vector* result = new std::vector(); + result->reserve(count); + + for (int i = 0; i < count; ++i) + { + result->push_back(std::string(reinterpret_cast(mStrings[machineNameData[i]]))); + } + + return result; + } + + std::string GetRevitVersion(int syncSessionIndex) + { + if (syncSessionIndex < 0 || syncSessionIndex >= GetCount()) + return {}; + + if (mEntityTable.column_exists("string:RevitVersion")) { + return std::string(reinterpret_cast(mStrings[mEntityTable.mStringColumns["string:RevitVersion"][syncSessionIndex]])); + } + + return {}; + } + + std::vector* GetAllRevitVersion() + { + const auto count = GetCount(); + + const std::vector& revitVersionData = mEntityTable.column_exists("string:RevitVersion") ? mEntityTable.mStringColumns["string:RevitVersion"] : std::vector(); + + std::vector* result = new std::vector(); + result->reserve(count); + + for (int i = 0; i < count; ++i) + { + result->push_back(std::string(reinterpret_cast(mStrings[revitVersionData[i]]))); + } + + return result; + } + + std::string GetStartedUtc(int syncSessionIndex) + { + if (syncSessionIndex < 0 || syncSessionIndex >= GetCount()) + return {}; + + if (mEntityTable.column_exists("string:StartedUtc")) { + return std::string(reinterpret_cast(mStrings[mEntityTable.mStringColumns["string:StartedUtc"][syncSessionIndex]])); + } + + return {}; + } + + std::vector* GetAllStartedUtc() + { + const auto count = GetCount(); + + const std::vector& startedUtcData = mEntityTable.column_exists("string:StartedUtc") ? mEntityTable.mStringColumns["string:StartedUtc"] : std::vector(); + + std::vector* result = new std::vector(); + result->reserve(count); + + for (int i = 0; i < count; ++i) + { + result->push_back(std::string(reinterpret_cast(mStrings[startedUtcData[i]]))); + } + + return result; + } + + std::string GetEndedUtc(int syncSessionIndex) + { + if (syncSessionIndex < 0 || syncSessionIndex >= GetCount()) + return {}; + + if (mEntityTable.column_exists("string:EndedUtc")) { + return std::string(reinterpret_cast(mStrings[mEntityTable.mStringColumns["string:EndedUtc"][syncSessionIndex]])); + } + + return {}; + } + + std::vector* GetAllEndedUtc() + { + const auto count = GetCount(); + + const std::vector& endedUtcData = mEntityTable.column_exists("string:EndedUtc") ? mEntityTable.mStringColumns["string:EndedUtc"] : std::vector(); + + std::vector* result = new std::vector(); + result->reserve(count); + + for (int i = 0; i < count; ++i) + { + result->push_back(std::string(reinterpret_cast(mStrings[endedUtcData[i]]))); + } + + return result; + } + + int GetContributorIndex(int syncSessionIndex) + { + if (!mEntityTable.column_exists("index:Vim.Contributor:Contributor")) { + return -1; + } + + if (syncSessionIndex < 0 || syncSessionIndex >= GetCount()) + return -1; + + return mEntityTable.mIndexColumns["index:Vim.Contributor:Contributor"][syncSessionIndex]; + } + + int GetBimDocumentIndex(int syncSessionIndex) + { + if (!mEntityTable.column_exists("index:Vim.BimDocument:BimDocument")) { + return -1; + } + + if (syncSessionIndex < 0 || syncSessionIndex >= GetCount()) + return -1; + + return mEntityTable.mIndexColumns["index:Vim.BimDocument:BimDocument"][syncSessionIndex]; + } + + }; + + static SyncSessionTable* GetSyncSessionTable(Scene& scene) + { + if (scene.mEntityTables.find("Vim.SyncSession") == scene.mEntityTables.end()) + return {}; + + return new SyncSessionTable(scene.mEntityTables["Vim.SyncSession"], scene.mStrings); + } + + class ElementChange + { + public: + int mIndex; + int mChangeKind; + int mConfidence; + std::string mChangedUtc; + std::string mUniqueId; + std::string mCategoryName; + std::string mFamilyName; + std::string mTypeName; + std::string mElementName; + std::string mLevelName; + std::string mWorksetName; + + int mExportRevisionIndex; + ExportRevision* mExportRevision; + int mSyncSessionIndex; + SyncSession* mSyncSession; + int mContributorIndex; + Contributor* mContributor; + + ElementChange() {} + }; + + class ElementChangeTable + { + EntityTable& mEntityTable; + std::vector& mStrings; + public: + ElementChangeTable(EntityTable& entityTable, std::vector& strings): + mEntityTable(entityTable), mStrings(strings) {} + + size_t GetCount() + { + return mEntityTable.get_count(); + } + + ElementChange* Get(int elementChangeIndex) + { + ElementChange* elementChange = new ElementChange(); + elementChange->mIndex = elementChangeIndex; + elementChange->mChangeKind = GetChangeKind(elementChangeIndex); + elementChange->mConfidence = GetConfidence(elementChangeIndex); + elementChange->mChangedUtc = GetChangedUtc(elementChangeIndex); + elementChange->mUniqueId = GetUniqueId(elementChangeIndex); + elementChange->mCategoryName = GetCategoryName(elementChangeIndex); + elementChange->mFamilyName = GetFamilyName(elementChangeIndex); + elementChange->mTypeName = GetTypeName(elementChangeIndex); + elementChange->mElementName = GetElementName(elementChangeIndex); + elementChange->mLevelName = GetLevelName(elementChangeIndex); + elementChange->mWorksetName = GetWorksetName(elementChangeIndex); + elementChange->mExportRevisionIndex = GetExportRevisionIndex(elementChangeIndex); + elementChange->mSyncSessionIndex = GetSyncSessionIndex(elementChangeIndex); + elementChange->mContributorIndex = GetContributorIndex(elementChangeIndex); + return elementChange; + } + + std::vector* GetAll() + { + bool existsChangeKind = mEntityTable.column_exists("int:ChangeKind"); + bool existsConfidence = mEntityTable.column_exists("int:Confidence"); + bool existsChangedUtc = mEntityTable.column_exists("string:ChangedUtc"); + bool existsUniqueId = mEntityTable.column_exists("string:UniqueId"); + bool existsCategoryName = mEntityTable.column_exists("string:CategoryName"); + bool existsFamilyName = mEntityTable.column_exists("string:FamilyName"); + bool existsTypeName = mEntityTable.column_exists("string:TypeName"); + bool existsElementName = mEntityTable.column_exists("string:ElementName"); + bool existsLevelName = mEntityTable.column_exists("string:LevelName"); + bool existsWorksetName = mEntityTable.column_exists("string:WorksetName"); + bool existsExportRevision = mEntityTable.column_exists("index:Vim.ExportRevision:ExportRevision"); + bool existsSyncSession = mEntityTable.column_exists("index:Vim.SyncSession:SyncSession"); + bool existsContributor = mEntityTable.column_exists("index:Vim.Contributor:Contributor"); + + const auto count = GetCount(); + + std::vector* elementChange = new std::vector(); + elementChange->reserve(count); + + int* changeKindData = new int[count]; + if (mEntityTable.column_exists("int:ChangeKind")) { + memcpy(changeKindData, mEntityTable.mDataColumns["int:ChangeKind"].begin(), count * sizeof(int)); + } + + int* confidenceData = new int[count]; + if (mEntityTable.column_exists("int:Confidence")) { + memcpy(confidenceData, mEntityTable.mDataColumns["int:Confidence"].begin(), count * sizeof(int)); + } + + const std::vector& changedUtcData = mEntityTable.column_exists("string:ChangedUtc") ? mEntityTable.mStringColumns["string:ChangedUtc"] : std::vector(); + + const std::vector& uniqueIdData = mEntityTable.column_exists("string:UniqueId") ? mEntityTable.mStringColumns["string:UniqueId"] : std::vector(); + + const std::vector& categoryNameData = mEntityTable.column_exists("string:CategoryName") ? mEntityTable.mStringColumns["string:CategoryName"] : std::vector(); + + const std::vector& familyNameData = mEntityTable.column_exists("string:FamilyName") ? mEntityTable.mStringColumns["string:FamilyName"] : std::vector(); + + const std::vector& typeNameData = mEntityTable.column_exists("string:TypeName") ? mEntityTable.mStringColumns["string:TypeName"] : std::vector(); + + const std::vector& elementNameData = mEntityTable.column_exists("string:ElementName") ? mEntityTable.mStringColumns["string:ElementName"] : std::vector(); + + const std::vector& levelNameData = mEntityTable.column_exists("string:LevelName") ? mEntityTable.mStringColumns["string:LevelName"] : std::vector(); + + const std::vector& worksetNameData = mEntityTable.column_exists("string:WorksetName") ? mEntityTable.mStringColumns["string:WorksetName"] : std::vector(); + + const std::vector& exportRevisionData = mEntityTable.column_exists("index:Vim.ExportRevision:ExportRevision") ? mEntityTable.mIndexColumns["index:Vim.ExportRevision:ExportRevision"] : std::vector(); + const std::vector& syncSessionData = mEntityTable.column_exists("index:Vim.SyncSession:SyncSession") ? mEntityTable.mIndexColumns["index:Vim.SyncSession:SyncSession"] : std::vector(); + const std::vector& contributorData = mEntityTable.column_exists("index:Vim.Contributor:Contributor") ? mEntityTable.mIndexColumns["index:Vim.Contributor:Contributor"] : std::vector(); + + for (int i = 0; i < count; ++i) + { + ElementChange entity; + entity.mIndex = i; + if (existsChangeKind) + entity.mChangeKind = changeKindData[i]; + if (existsConfidence) + entity.mConfidence = confidenceData[i]; + if (existsChangedUtc) + entity.mChangedUtc = std::string(reinterpret_cast(mStrings[changedUtcData[i]])); + if (existsUniqueId) + entity.mUniqueId = std::string(reinterpret_cast(mStrings[uniqueIdData[i]])); + if (existsCategoryName) + entity.mCategoryName = std::string(reinterpret_cast(mStrings[categoryNameData[i]])); + if (existsFamilyName) + entity.mFamilyName = std::string(reinterpret_cast(mStrings[familyNameData[i]])); + if (existsTypeName) + entity.mTypeName = std::string(reinterpret_cast(mStrings[typeNameData[i]])); + if (existsElementName) + entity.mElementName = std::string(reinterpret_cast(mStrings[elementNameData[i]])); + if (existsLevelName) + entity.mLevelName = std::string(reinterpret_cast(mStrings[levelNameData[i]])); + if (existsWorksetName) + entity.mWorksetName = std::string(reinterpret_cast(mStrings[worksetNameData[i]])); + entity.mExportRevisionIndex = existsExportRevision ? exportRevisionData[i] : -1; + entity.mSyncSessionIndex = existsSyncSession ? syncSessionData[i] : -1; + entity.mContributorIndex = existsContributor ? contributorData[i] : -1; + elementChange->push_back(entity); + } + + delete[] changeKindData; + delete[] confidenceData; + + return elementChange; + } + + int GetChangeKind(int elementChangeIndex) + { + if (elementChangeIndex < 0 || elementChangeIndex >= GetCount()) + return {}; + + if (mEntityTable.column_exists("int:ChangeKind")) { + return *reinterpret_cast(const_cast(mEntityTable.mDataColumns["int:ChangeKind"].begin() + elementChangeIndex * sizeof(int))); + } + + return {}; + } + + std::vector* GetAllChangeKind() + { + const auto count = GetCount(); + + int* changeKindData = new int[count]; + if (mEntityTable.column_exists("int:ChangeKind")) { + memcpy(changeKindData, mEntityTable.mDataColumns["int:ChangeKind"].begin(), count * sizeof(int)); + } + + std::vector* result = new std::vector(changeKindData, changeKindData + count); + + delete[] changeKindData; + + return result; + } + + int GetConfidence(int elementChangeIndex) + { + if (elementChangeIndex < 0 || elementChangeIndex >= GetCount()) + return {}; + + if (mEntityTable.column_exists("int:Confidence")) { + return *reinterpret_cast(const_cast(mEntityTable.mDataColumns["int:Confidence"].begin() + elementChangeIndex * sizeof(int))); + } + + return {}; + } + + std::vector* GetAllConfidence() + { + const auto count = GetCount(); + + int* confidenceData = new int[count]; + if (mEntityTable.column_exists("int:Confidence")) { + memcpy(confidenceData, mEntityTable.mDataColumns["int:Confidence"].begin(), count * sizeof(int)); + } + + std::vector* result = new std::vector(confidenceData, confidenceData + count); + + delete[] confidenceData; + + return result; + } + + std::string GetChangedUtc(int elementChangeIndex) + { + if (elementChangeIndex < 0 || elementChangeIndex >= GetCount()) + return {}; + + if (mEntityTable.column_exists("string:ChangedUtc")) { + return std::string(reinterpret_cast(mStrings[mEntityTable.mStringColumns["string:ChangedUtc"][elementChangeIndex]])); + } + + return {}; + } + + std::vector* GetAllChangedUtc() + { + const auto count = GetCount(); + + const std::vector& changedUtcData = mEntityTable.column_exists("string:ChangedUtc") ? mEntityTable.mStringColumns["string:ChangedUtc"] : std::vector(); + + std::vector* result = new std::vector(); + result->reserve(count); + + for (int i = 0; i < count; ++i) + { + result->push_back(std::string(reinterpret_cast(mStrings[changedUtcData[i]]))); + } + + return result; + } + + std::string GetUniqueId(int elementChangeIndex) + { + if (elementChangeIndex < 0 || elementChangeIndex >= GetCount()) + return {}; + + if (mEntityTable.column_exists("string:UniqueId")) { + return std::string(reinterpret_cast(mStrings[mEntityTable.mStringColumns["string:UniqueId"][elementChangeIndex]])); + } + + return {}; + } + + std::vector* GetAllUniqueId() + { + const auto count = GetCount(); + + const std::vector& uniqueIdData = mEntityTable.column_exists("string:UniqueId") ? mEntityTable.mStringColumns["string:UniqueId"] : std::vector(); + + std::vector* result = new std::vector(); + result->reserve(count); + + for (int i = 0; i < count; ++i) + { + result->push_back(std::string(reinterpret_cast(mStrings[uniqueIdData[i]]))); + } + + return result; + } + + std::string GetCategoryName(int elementChangeIndex) + { + if (elementChangeIndex < 0 || elementChangeIndex >= GetCount()) + return {}; + + if (mEntityTable.column_exists("string:CategoryName")) { + return std::string(reinterpret_cast(mStrings[mEntityTable.mStringColumns["string:CategoryName"][elementChangeIndex]])); + } + + return {}; + } + + std::vector* GetAllCategoryName() + { + const auto count = GetCount(); + + const std::vector& categoryNameData = mEntityTable.column_exists("string:CategoryName") ? mEntityTable.mStringColumns["string:CategoryName"] : std::vector(); + + std::vector* result = new std::vector(); + result->reserve(count); + + for (int i = 0; i < count; ++i) + { + result->push_back(std::string(reinterpret_cast(mStrings[categoryNameData[i]]))); + } + + return result; + } + + std::string GetFamilyName(int elementChangeIndex) + { + if (elementChangeIndex < 0 || elementChangeIndex >= GetCount()) + return {}; + + if (mEntityTable.column_exists("string:FamilyName")) { + return std::string(reinterpret_cast(mStrings[mEntityTable.mStringColumns["string:FamilyName"][elementChangeIndex]])); + } + + return {}; + } + + std::vector* GetAllFamilyName() + { + const auto count = GetCount(); + + const std::vector& familyNameData = mEntityTable.column_exists("string:FamilyName") ? mEntityTable.mStringColumns["string:FamilyName"] : std::vector(); + + std::vector* result = new std::vector(); + result->reserve(count); + + for (int i = 0; i < count; ++i) + { + result->push_back(std::string(reinterpret_cast(mStrings[familyNameData[i]]))); + } + + return result; + } + + std::string GetTypeName(int elementChangeIndex) + { + if (elementChangeIndex < 0 || elementChangeIndex >= GetCount()) + return {}; + + if (mEntityTable.column_exists("string:TypeName")) { + return std::string(reinterpret_cast(mStrings[mEntityTable.mStringColumns["string:TypeName"][elementChangeIndex]])); + } + + return {}; + } + + std::vector* GetAllTypeName() + { + const auto count = GetCount(); + + const std::vector& typeNameData = mEntityTable.column_exists("string:TypeName") ? mEntityTable.mStringColumns["string:TypeName"] : std::vector(); + + std::vector* result = new std::vector(); + result->reserve(count); + + for (int i = 0; i < count; ++i) + { + result->push_back(std::string(reinterpret_cast(mStrings[typeNameData[i]]))); + } + + return result; + } + + std::string GetElementName(int elementChangeIndex) + { + if (elementChangeIndex < 0 || elementChangeIndex >= GetCount()) + return {}; + + if (mEntityTable.column_exists("string:ElementName")) { + return std::string(reinterpret_cast(mStrings[mEntityTable.mStringColumns["string:ElementName"][elementChangeIndex]])); + } + + return {}; + } + + std::vector* GetAllElementName() + { + const auto count = GetCount(); + + const std::vector& elementNameData = mEntityTable.column_exists("string:ElementName") ? mEntityTable.mStringColumns["string:ElementName"] : std::vector(); + + std::vector* result = new std::vector(); + result->reserve(count); + + for (int i = 0; i < count; ++i) + { + result->push_back(std::string(reinterpret_cast(mStrings[elementNameData[i]]))); + } + + return result; + } + + std::string GetLevelName(int elementChangeIndex) + { + if (elementChangeIndex < 0 || elementChangeIndex >= GetCount()) + return {}; + + if (mEntityTable.column_exists("string:LevelName")) { + return std::string(reinterpret_cast(mStrings[mEntityTable.mStringColumns["string:LevelName"][elementChangeIndex]])); + } + + return {}; + } + + std::vector* GetAllLevelName() + { + const auto count = GetCount(); + + const std::vector& levelNameData = mEntityTable.column_exists("string:LevelName") ? mEntityTable.mStringColumns["string:LevelName"] : std::vector(); + + std::vector* result = new std::vector(); + result->reserve(count); + + for (int i = 0; i < count; ++i) + { + result->push_back(std::string(reinterpret_cast(mStrings[levelNameData[i]]))); + } + + return result; + } + + std::string GetWorksetName(int elementChangeIndex) + { + if (elementChangeIndex < 0 || elementChangeIndex >= GetCount()) + return {}; + + if (mEntityTable.column_exists("string:WorksetName")) { + return std::string(reinterpret_cast(mStrings[mEntityTable.mStringColumns["string:WorksetName"][elementChangeIndex]])); + } + + return {}; + } + + std::vector* GetAllWorksetName() + { + const auto count = GetCount(); + + const std::vector& worksetNameData = mEntityTable.column_exists("string:WorksetName") ? mEntityTable.mStringColumns["string:WorksetName"] : std::vector(); + + std::vector* result = new std::vector(); + result->reserve(count); + + for (int i = 0; i < count; ++i) + { + result->push_back(std::string(reinterpret_cast(mStrings[worksetNameData[i]]))); + } + + return result; + } + + int GetExportRevisionIndex(int elementChangeIndex) + { + if (!mEntityTable.column_exists("index:Vim.ExportRevision:ExportRevision")) { + return -1; + } + + if (elementChangeIndex < 0 || elementChangeIndex >= GetCount()) + return -1; + + return mEntityTable.mIndexColumns["index:Vim.ExportRevision:ExportRevision"][elementChangeIndex]; + } + + int GetSyncSessionIndex(int elementChangeIndex) + { + if (!mEntityTable.column_exists("index:Vim.SyncSession:SyncSession")) { + return -1; + } + + if (elementChangeIndex < 0 || elementChangeIndex >= GetCount()) + return -1; + + return mEntityTable.mIndexColumns["index:Vim.SyncSession:SyncSession"][elementChangeIndex]; + } + + int GetContributorIndex(int elementChangeIndex) + { + if (!mEntityTable.column_exists("index:Vim.Contributor:Contributor")) { + return -1; + } + + if (elementChangeIndex < 0 || elementChangeIndex >= GetCount()) + return -1; + + return mEntityTable.mIndexColumns["index:Vim.Contributor:Contributor"][elementChangeIndex]; + } + + }; + + static ElementChangeTable* GetElementChangeTable(Scene& scene) + { + if (scene.mEntityTables.find("Vim.ElementChange") == scene.mEntityTables.end()) + return {}; + + return new ElementChangeTable(scene.mEntityTables["Vim.ElementChange"], scene.mStrings); + } + + class ActivityMarker + { + public: + int mIndex; + int mKind; + std::string mOccurredUtc; + std::string mDescription; + + int mExportRevisionIndex; + ExportRevision* mExportRevision; + int mSyncSessionIndex; + SyncSession* mSyncSession; + int mContributorIndex; + Contributor* mContributor; + + ActivityMarker() {} + }; + + class ActivityMarkerTable + { + EntityTable& mEntityTable; + std::vector& mStrings; + public: + ActivityMarkerTable(EntityTable& entityTable, std::vector& strings): + mEntityTable(entityTable), mStrings(strings) {} + + size_t GetCount() + { + return mEntityTable.get_count(); + } + + ActivityMarker* Get(int activityMarkerIndex) + { + ActivityMarker* activityMarker = new ActivityMarker(); + activityMarker->mIndex = activityMarkerIndex; + activityMarker->mKind = GetKind(activityMarkerIndex); + activityMarker->mOccurredUtc = GetOccurredUtc(activityMarkerIndex); + activityMarker->mDescription = GetDescription(activityMarkerIndex); + activityMarker->mExportRevisionIndex = GetExportRevisionIndex(activityMarkerIndex); + activityMarker->mSyncSessionIndex = GetSyncSessionIndex(activityMarkerIndex); + activityMarker->mContributorIndex = GetContributorIndex(activityMarkerIndex); + return activityMarker; + } + + std::vector* GetAll() + { + bool existsKind = mEntityTable.column_exists("int:Kind"); + bool existsOccurredUtc = mEntityTable.column_exists("string:OccurredUtc"); + bool existsDescription = mEntityTable.column_exists("string:Description"); + bool existsExportRevision = mEntityTable.column_exists("index:Vim.ExportRevision:ExportRevision"); + bool existsSyncSession = mEntityTable.column_exists("index:Vim.SyncSession:SyncSession"); + bool existsContributor = mEntityTable.column_exists("index:Vim.Contributor:Contributor"); + + const auto count = GetCount(); + + std::vector* activityMarker = new std::vector(); + activityMarker->reserve(count); + + int* kindData = new int[count]; + if (mEntityTable.column_exists("int:Kind")) { + memcpy(kindData, mEntityTable.mDataColumns["int:Kind"].begin(), count * sizeof(int)); + } + + const std::vector& occurredUtcData = mEntityTable.column_exists("string:OccurredUtc") ? mEntityTable.mStringColumns["string:OccurredUtc"] : std::vector(); + + const std::vector& descriptionData = mEntityTable.column_exists("string:Description") ? mEntityTable.mStringColumns["string:Description"] : std::vector(); + + const std::vector& exportRevisionData = mEntityTable.column_exists("index:Vim.ExportRevision:ExportRevision") ? mEntityTable.mIndexColumns["index:Vim.ExportRevision:ExportRevision"] : std::vector(); + const std::vector& syncSessionData = mEntityTable.column_exists("index:Vim.SyncSession:SyncSession") ? mEntityTable.mIndexColumns["index:Vim.SyncSession:SyncSession"] : std::vector(); + const std::vector& contributorData = mEntityTable.column_exists("index:Vim.Contributor:Contributor") ? mEntityTable.mIndexColumns["index:Vim.Contributor:Contributor"] : std::vector(); + + for (int i = 0; i < count; ++i) + { + ActivityMarker entity; + entity.mIndex = i; + if (existsKind) + entity.mKind = kindData[i]; + if (existsOccurredUtc) + entity.mOccurredUtc = std::string(reinterpret_cast(mStrings[occurredUtcData[i]])); + if (existsDescription) + entity.mDescription = std::string(reinterpret_cast(mStrings[descriptionData[i]])); + entity.mExportRevisionIndex = existsExportRevision ? exportRevisionData[i] : -1; + entity.mSyncSessionIndex = existsSyncSession ? syncSessionData[i] : -1; + entity.mContributorIndex = existsContributor ? contributorData[i] : -1; + activityMarker->push_back(entity); + } + + delete[] kindData; + + return activityMarker; + } + + int GetKind(int activityMarkerIndex) + { + if (activityMarkerIndex < 0 || activityMarkerIndex >= GetCount()) + return {}; + + if (mEntityTable.column_exists("int:Kind")) { + return *reinterpret_cast(const_cast(mEntityTable.mDataColumns["int:Kind"].begin() + activityMarkerIndex * sizeof(int))); + } + + return {}; + } + + std::vector* GetAllKind() + { + const auto count = GetCount(); + + int* kindData = new int[count]; + if (mEntityTable.column_exists("int:Kind")) { + memcpy(kindData, mEntityTable.mDataColumns["int:Kind"].begin(), count * sizeof(int)); + } + + std::vector* result = new std::vector(kindData, kindData + count); + + delete[] kindData; + + return result; + } + + std::string GetOccurredUtc(int activityMarkerIndex) + { + if (activityMarkerIndex < 0 || activityMarkerIndex >= GetCount()) + return {}; + + if (mEntityTable.column_exists("string:OccurredUtc")) { + return std::string(reinterpret_cast(mStrings[mEntityTable.mStringColumns["string:OccurredUtc"][activityMarkerIndex]])); + } + + return {}; + } + + std::vector* GetAllOccurredUtc() + { + const auto count = GetCount(); + + const std::vector& occurredUtcData = mEntityTable.column_exists("string:OccurredUtc") ? mEntityTable.mStringColumns["string:OccurredUtc"] : std::vector(); + + std::vector* result = new std::vector(); + result->reserve(count); + + for (int i = 0; i < count; ++i) + { + result->push_back(std::string(reinterpret_cast(mStrings[occurredUtcData[i]]))); + } + + return result; + } + + std::string GetDescription(int activityMarkerIndex) + { + if (activityMarkerIndex < 0 || activityMarkerIndex >= GetCount()) + return {}; + + if (mEntityTable.column_exists("string:Description")) { + return std::string(reinterpret_cast(mStrings[mEntityTable.mStringColumns["string:Description"][activityMarkerIndex]])); + } + + return {}; + } + + std::vector* GetAllDescription() + { + const auto count = GetCount(); + + const std::vector& descriptionData = mEntityTable.column_exists("string:Description") ? mEntityTable.mStringColumns["string:Description"] : std::vector(); + + std::vector* result = new std::vector(); + result->reserve(count); + + for (int i = 0; i < count; ++i) + { + result->push_back(std::string(reinterpret_cast(mStrings[descriptionData[i]]))); + } + + return result; + } + + int GetExportRevisionIndex(int activityMarkerIndex) + { + if (!mEntityTable.column_exists("index:Vim.ExportRevision:ExportRevision")) { + return -1; + } + + if (activityMarkerIndex < 0 || activityMarkerIndex >= GetCount()) + return -1; + + return mEntityTable.mIndexColumns["index:Vim.ExportRevision:ExportRevision"][activityMarkerIndex]; + } + + int GetSyncSessionIndex(int activityMarkerIndex) + { + if (!mEntityTable.column_exists("index:Vim.SyncSession:SyncSession")) { + return -1; + } + + if (activityMarkerIndex < 0 || activityMarkerIndex >= GetCount()) + return -1; + + return mEntityTable.mIndexColumns["index:Vim.SyncSession:SyncSession"][activityMarkerIndex]; + } + + int GetContributorIndex(int activityMarkerIndex) + { + if (!mEntityTable.column_exists("index:Vim.Contributor:Contributor")) { + return -1; + } + + if (activityMarkerIndex < 0 || activityMarkerIndex >= GetCount()) + return -1; + + return mEntityTable.mIndexColumns["index:Vim.Contributor:Contributor"][activityMarkerIndex]; + } + + }; + + static ActivityMarkerTable* GetActivityMarkerTable(Scene& scene) + { + if (scene.mEntityTables.find("Vim.ActivityMarker") == scene.mEntityTables.end()) + return {}; + + return new ActivityMarkerTable(scene.mEntityTables["Vim.ActivityMarker"], scene.mStrings); + } + DocumentModel::DocumentModel(Scene& scene) { mAsset = GetAssetTable(scene); @@ -16421,6 +17941,12 @@ namespace Vim mLightType = GetLightTypeTable(scene); mLightSource = GetLightSourceTable(scene); mSunAndShadowSettings = GetSunAndShadowSettingsTable(scene); + mExportRevision = GetExportRevisionTable(scene); + mElementInExportRevision = GetElementInExportRevisionTable(scene); + mContributor = GetContributorTable(scene); + mSyncSession = GetSyncSessionTable(scene); + mElementChange = GetElementChangeTable(scene); + mActivityMarker = GetActivityMarkerTable(scene); } DocumentModel::~DocumentModel() @@ -16482,6 +18008,12 @@ namespace Vim delete mLightType; delete mLightSource; delete mSunAndShadowSettings; + delete mExportRevision; + delete mElementInExportRevision; + delete mContributor; + delete mSyncSession; + delete mElementChange; + delete mActivityMarker; } } diff --git a/src/cs/vim/Vim.Format.Core/DocumentBuilder.cs b/src/cs/vim/Vim.Format.Core/DocumentBuilder.cs index a906e95..5dc31ef 100644 --- a/src/cs/vim/Vim.Format.Core/DocumentBuilder.cs +++ b/src/cs/vim/Vim.Format.Core/DocumentBuilder.cs @@ -31,6 +31,14 @@ public DocumentBuilder( Header = new SerializableHeader(generator, schema, versionString, optionalHeaderValues); } + /// + /// A builder which writes the given header (for example ). + /// + public DocumentBuilder(SerializableHeader header) + { + Header = header ?? throw new ArgumentNullException(nameof(header)); + } + public EntityTableBuilder GetTableBuilderOrCreate(string name) { if (!Tables.ContainsKey(name)) diff --git a/src/cs/vim/Vim.Format.Core/SerializableHeader.cs b/src/cs/vim/Vim.Format.Core/SerializableHeader.cs index c1c3a97..29f0589 100644 --- a/src/cs/vim/Vim.Format.Core/SerializableHeader.cs +++ b/src/cs/vim/Vim.Format.Core/SerializableHeader.cs @@ -93,6 +93,32 @@ public SerializableHeader(string generator, SerializableVersion schema, string v AddOptionalValues(values ?? new Dictionary(), versionString)) { } + /// + /// A header for a new revision of an existing VIM file (for example a patched file): the Id is kept for + /// continuity, the Revision and the creation date are new (see ). The optional values + /// of the prior header are kept; are added or replace them. + /// + public static SerializableHeader CreateRevisionOf( + SerializableHeader prior, + string generator, + SerializableVersion schema, + IReadOnlyDictionary values = null) + { + var d = prior.Values?.ToDictionary(kv => kv.Key, kv => kv.Value) ?? new Dictionary(); + if (values != null) + foreach (var kv in values) + d[kv.Key] = kv.Value; + + return new SerializableHeader( + CurrentVimFormatVersion, + prior.Id == Guid.Empty ? Guid.NewGuid() : prior.Id, + Guid.NewGuid(), + generator, + DateTime.UtcNow, + schema ?? prior.Schema, + d); + } + /// /// Parses the input. Throws exceptions if the input does not define a correctly formatted header. /// diff --git a/src/cs/vim/Vim.Format.Core/VimConstants.cs b/src/cs/vim/Vim.Format.Core/VimConstants.cs index 4b738a2..e78aede 100644 --- a/src/cs/vim/Vim.Format.Core/VimConstants.cs +++ b/src/cs/vim/Vim.Format.Core/VimConstants.cs @@ -71,6 +71,12 @@ public static class TableNames public const string LightType = "Vim.LightType"; public const string LightSource = "Vim.LightSource"; public const string SunAndShadowSettings = "Vim.SunAndShadowSettings"; + public const string ExportRevision = "Vim.ExportRevision"; + public const string ElementInExportRevision = "Vim.ElementInExportRevision"; + public const string Contributor = "Vim.Contributor"; + public const string SyncSession = "Vim.SyncSession"; + public const string ElementChange = "Vim.ElementChange"; + public const string ActivityMarker = "Vim.ActivityMarker"; } public static class VimConstants diff --git a/src/cs/vim/Vim.Format.Tests/ActivityLogTests.cs b/src/cs/vim/Vim.Format.Tests/ActivityLogTests.cs new file mode 100644 index 0000000..48cb244 --- /dev/null +++ b/src/cs/vim/Vim.Format.Tests/ActivityLogTests.cs @@ -0,0 +1,239 @@ +using System; +using System.IO; +using System.Linq; +using NUnit.Framework; +using Vim.Format.Activity; +using Vim.Format.ObjectModel; +using Vim.Format.SceneBuilder; +using Vim.LinqArray; +using Vim.Util.Tests; + +namespace Vim.Format.Tests +{ + /// + /// The activity log (object model 5.11.0): the four entities survive a write and a read, the sidecar text says + /// the same thing as the file, the retention caps drop the oldest and the CSV reads as a person would expect. + /// + [TestFixture] + public static class ActivityLogTests + { + private const string Session = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"; + + private static ActivityLog Sample() + => new ActivityLog + { + Sessions = + { + new ActivitySession + { + SessionGuid = Session, + MachineName = "DESK-7", + RevitVersion = "2027", + StartedUtc = "2026-10-12T08:00:00.0000000Z", + EndedUtc = "2026-10-12T14:22:00.0000000Z", + Contributor = new ActivityContributor("s.arsenault", ContributorSource.Session), + }, + }, + Changes = + { + new ActivityChange + { + ChangedUtc = "2026-10-12T14:22:00.0000000Z", + SessionGuid = Session, + Contributor = new ActivityContributor("s.arsenault", ContributorSource.Session), + Kind = ElementChangeKind.Deleted, + Confidence = ElementChangeConfidence.Observed, + UniqueId = "uid-door", + CategoryName = "Doors", + FamilyName = "M_Single-Flush", + TypeName = "0915x2134", + ElementName = "0915x2134", + LevelName = "Level 4", + WorksetName = "Shell", + }, + new ActivityChange + { + ChangedUtc = "2026-10-12T11:40:00.0000000Z", + Contributor = new ActivityContributor("j.tremblay", ContributorSource.Central), + Kind = ElementChangeKind.Modified, + Confidence = ElementChangeConfidence.FromCentral, + UniqueId = "uid-wall", + CategoryName = "Walls", + }, + new ActivityChange + { + ChangedUtc = "2026-10-12T11:40:00.0000000Z", + Contributor = new ActivityContributor("", ContributorSource.Unknown), + Kind = ElementChangeKind.Deleted, + Confidence = ElementChangeConfidence.Unknown, + UniqueId = "uid-gone", + CategoryName = "Pipe Fittings", + }, + }, + Notes = + { + new ActivityNote + { + OccurredUtc = "2026-10-12T11:40:00.0000000Z", + Kind = ActivityMarkerKind.HistoryReset, + Contributor = new ActivityContributor("", ContributorSource.Unknown), + Description = "This model looks like it was re-saved as a new central since the last export.", + }, + }, + }; + + /// A document with one BimDocument, so the sessions have something to belong to. + private static string WriteVim(string dir, string name, ActivityLog log) + { + var store = new ObjectModelStore(); + var omb = store.ObjectModelBuilder; + var bimDocument = new BimDocument { Name = "Activity", Title = "Activity", PathName = "C:\\activity.rvt" }; + var holder = omb.ElementBuilder.Add(bimDocument.CreateParameterHolderElement()).Entity; + bimDocument._Element.Index = holder.Index; + omb.BimDocumentBuilder.Add(bimDocument); + + var db = store.ToDocumentBuilder(nameof(ActivityLogTests), "0.0.0"); + log.WriteInto(db, 0); + var path = Path.Combine(dir, name); + db.Write(path); + return path; + } + + [Test] + public static void TestTheFourEntitiesSurviveAWriteAndARead() + { + var dir = new CallerTestContext().PrepareDirectory(); + var path = WriteVim(dir, "activity.vim", Sample()); + + var vim = VimScene.LoadVim(path); + vim.Validate(); + var dm = vim.DocumentModel; + + Assert.AreEqual(SchemaVersion.Current.ToString(), vim.Document.Header.Schema.ToString()); + + var session = Enumerable.Range(0, dm.NumSyncSession).Select(dm.GetSyncSession).Single(); + Assert.AreEqual(Session, session.SessionGuid); + Assert.AreEqual("DESK-7", session.MachineName); + Assert.AreEqual("2027", session.RevitVersion); + Assert.AreEqual("s.arsenault", session.Contributor.Name); + Assert.AreEqual(ContributorSource.Session, session.Contributor.GetSource()); + Assert.AreEqual(0, session._BimDocument.Index); + + var changes = Enumerable.Range(0, dm.NumElementChange).Select(dm.GetElementChange) + .ToDictionary(c => c.UniqueId, c => c); + Assert.AreEqual(3, changes.Count); + + var door = changes["uid-door"]; + Assert.AreEqual(ElementChangeKind.Deleted, door.GetChangeKind()); + Assert.AreEqual(ElementChangeConfidence.Observed, door.GetConfidence()); + Assert.AreEqual("Doors", door.CategoryName); + Assert.AreEqual("M_Single-Flush", door.FamilyName); + Assert.AreEqual("0915x2134", door.TypeName); + Assert.AreEqual("Level 4", door.LevelName); + Assert.AreEqual("Shell", door.WorksetName); + Assert.AreEqual(session.Index, door._SyncSession.Index, "a watched change belongs to its session"); + Assert.AreEqual("s.arsenault", door.Contributor.Name); + + var wall = changes["uid-wall"]; + Assert.AreEqual(ElementChangeConfidence.FromCentral, wall.GetConfidence()); + Assert.AreEqual(ContributorSource.Central, wall.Contributor.GetSource()); + Assert.AreEqual(-1, wall._SyncSession.Index, "a change that arrived from central belongs to no session of ours"); + + var gone = changes["uid-gone"]; + Assert.AreEqual(ElementChangeConfidence.Unknown, gone.GetConfidence()); + Assert.AreEqual("", gone.Contributor.Name, "Revit records nobody for a deletion in somebody else's local"); + + var marker = Enumerable.Range(0, dm.NumActivityMarker).Select(dm.GetActivityMarker).Single(); + Assert.AreEqual(ActivityMarkerKind.HistoryReset, marker.GetKind()); + StringAssert.Contains("new central", marker.Description); + + // Three names, three sources: the same person seen twice would be two rows on purpose. + Assert.AreEqual(3, dm.NumContributor); + } + + [Test] + public static void TestTheSidecarTextSaysTheSameThing() + { + var text = Sample().ToText(); + var back = ActivityLog.FromText(text); + + Assert.IsNotNull(back); + Assert.AreEqual(1, back.Sessions.Count); + Assert.AreEqual(3, back.Changes.Count); + Assert.AreEqual(1, back.Notes.Count); + Assert.AreEqual(text, back.ToText(), "the text is its own fixed point"); + + var door = back.Changes.Single(c => c.UniqueId == "uid-door"); + Assert.AreEqual(ElementChangeKind.Deleted, door.Kind); + Assert.AreEqual(ElementChangeConfidence.Observed, door.Confidence); + Assert.AreEqual("Level 4", door.LevelName); + Assert.AreEqual(ContributorSource.Session, door.Contributor.Source); + + // A sidecar written by a later VIM is not read at all, rather than read wrongly. + Assert.IsNull(ActivityLog.FromText("vim-activity=99\n")); + } + + [Test] + public static void TestMergeAndTrim() + { + var log = Sample(); + log.Merge(Sample()); + Assert.AreEqual(3, log.Changes.Count, "merging the same log twice adds nothing"); + Assert.AreEqual(1, log.Sessions.Count); + + var later = new ActivityLog + { + Sessions = + { + new ActivitySession + { + SessionGuid = Session, StartedUtc = "2026-10-12T08:00:00.0000000Z", + EndedUtc = "2026-10-12T17:00:00.0000000Z", + Contributor = new ActivityContributor("s.arsenault", ContributorSource.Session), + }, + }, + }; + log.Merge(later); + Assert.AreEqual(1, log.Sessions.Count); + Assert.AreEqual("2026-10-12T17:00:00.0000000Z", log.Sessions[0].EndedUtc, "the session's end moves, it does not fork"); + + // 90 days of retention, counted from a day well after these rows. + var dropped = log.Trim(new DateTime(2027, 10, 12, 0, 0, 0, DateTimeKind.Utc), 90, 0); + Assert.AreEqual(3, dropped); + Assert.AreEqual(0, log.Changes.Count); + Assert.AreEqual(0, log.Sessions.Count, "a session nothing points at goes with its changes"); + + // The row cap keeps the newest. + var capped = Sample(); + Assert.AreEqual(2, capped.Trim(new DateTime(2026, 10, 12, 18, 0, 0, DateTimeKind.Utc), 0, 1)); + Assert.AreEqual("uid-door", capped.Changes.Single().UniqueId); + } + + [Test] + public static void TestTheCsvReadsAsAPersonWouldExpect() + { + var dir = new CallerTestContext().PrepareDirectory(); + var path = WriteVim(dir, "csv.vim", Sample()); + var files = ActivityReport.WriteCsv(path); + + Assert.AreEqual(3, files.Length); + foreach (var f in files) + Assert.IsTrue(File.Exists(f), f); + + var summary = File.ReadAllLines(files[0]); + StringAssert.StartsWith("Day,Person,NameFrom,Category,Added,Changed,Deleted,SessionsOnThatDay,HoursInModelOnThatDay", summary[0]); + Assert.AreEqual(4, summary.Length, "a header and one row per person per day per category"); + Assert.IsTrue(summary.Any(l => l.StartsWith("2026-10-12,s.arsenault,VIM watched it,Doors,0,0,1,1,6.37")), string.Join("\n", summary)); + Assert.IsTrue(summary.Any(l => l.Contains("j.tremblay,Revit (last changed by),Walls,0,1,0,0,0")), string.Join("\n", summary)); + Assert.IsTrue(summary.Any(l => l.Contains(",author unknown,Pipe Fittings,0,0,1,0,0")), string.Join("\n", summary)); + + var changes = File.ReadAllLines(files[1]); + Assert.AreEqual(4, changes.Length); + StringAssert.StartsWith("ChangedUtc,Person,NameFrom,Confidence,Change,Category", changes[0]); + + var notes = File.ReadAllLines(files[2]); + Assert.AreEqual(2, notes.Length); + StringAssert.Contains("HistoryReset", notes[1]); + } + } +} diff --git a/src/cs/vim/Vim.Format.Tests/VimPatchServiceTests.cs b/src/cs/vim/Vim.Format.Tests/VimPatchServiceTests.cs new file mode 100644 index 0000000..b926ffe --- /dev/null +++ b/src/cs/vim/Vim.Format.Tests/VimPatchServiceTests.cs @@ -0,0 +1,361 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using NUnit.Framework; +using Vim.Format.Activity; +using Vim.Format.Merge; +using Vim.Format.ObjectModel; +using Vim.Format.SceneBuilder; +using Vim.LinqArray; +using Vim.Math3d; +using Vim.Util.Tests; + +namespace Vim.Format.Tests +{ + /// + /// VimPatchService: a base file patched with a partial file of the same document. The partial holds a changed wall, + /// a new element, its own temporary 3D view (aliased onto the base's export view) and the door as context; the base + /// loses one deleted element. End to end on TowerS: C:\VimFlexLocal\work\incremental\check (PatchCheck run). + /// + [TestFixture] + public static class VimPatchServiceTests + { + private sealed class Doc + { + public ObjectModelStore Store = new ObjectModelStore(); + public ObjectModelBuilder Omb => Store.ObjectModelBuilder; + public int View; + public ParameterDescriptor Mark; + + public Doc(string viewUid, string viewName, string title = "Patch", string pathName = "C:\\patch.rvt") + { + // Name is the project name (the same in every local file of a model); Title and PathName are the file's. + var bimDocument = new BimDocument { Name = "Patch", Title = title, PathName = pathName }; + var holder = Omb.ElementBuilder.Add(bimDocument.CreateParameterHolderElement()).Entity; + bimDocument._Element.Index = holder.Index; + Omb.BimDocumentBuilder.Add(bimDocument); + var viewElement = AddElement(1000, viewName, viewUid); + View = Omb.ViewBuilder.Add(new View { Title = viewName, _Element = { Index = viewElement } }).Entity.Index; + var unit = Omb.DisplayUnitBuilder.Add(new DisplayUnit { Spec = "text", Type = "text", Label = "text" }).Entity; + Mark = Omb.ParameterDescriptorBuilder.Add(new ParameterDescriptor { Name = "Mark", Group = "Identity Data", _DisplayUnit = { Index = unit.Index } }).Entity; + } + + public int AddElement(long id, string name, string uid) + => Omb.ElementBuilder.Add(new Element { Id = id, Name = name, UniqueId = uid, _BimDocument = { Index = 0 } }).Entity.Index; + + /// An element with a 1 ft box at x, a Mark parameter and an ElementInView row. + public int AddBox(long id, string uid, float x, string mark) + { + var e = AddElement(id, uid, uid); + var mesh = Store.Meshes.Count; + Store.Meshes.Add(new DocumentBuilder.Mesh( + new List { new Vector3(0, 0, 0), new Vector3(1, 0, 0), new Vector3(0, 1, 0) }, + new List { 0, 1, 2 })); + Store.Instances.Add(new DocumentBuilder.Instance { Transform = Matrix4x4.CreateTranslation(x, 0, 0), MeshIndex = mesh, ParentIndex = -1 }); + Omb.NodeBuilder.AddUntracked(new Node { _Element = { Index = e } }); + Omb.ParameterBuilder.AddUntracked(new Parameter { Value = mark, _ParameterDescriptor = { Index = Mark.Index }, _Element = { Index = e } }); + Omb.ElementInViewBuilder.AddUntracked(new ElementInView { _Element = { Index = e }, _View = { Index = View } }); + return e; + } + + public void AddShape(int element, ShapeKind kind, float x) + { + Store.Shapes.Add(new DocumentBuilder.Shape { Vertices = new List { new Vector3(x, 0, 0), new Vector3(x, 1, 0) }, Color = new Vector4(0, 0, 0, 1), Width = 1 }); + Omb.ShapeBuilder.AddUntracked(new Shape { _Element = { Index = element }, Kind = (int)kind }); + } + + public string Write(string dir, string name, ActivityLog activity = null) + { + var path = Path.Combine(dir, name); + var db = Store.ToDocumentBuilder(nameof(VimPatchServiceTests), "0.0.0"); + activity?.WriteInto(db, 0); + db.Write(path); + return path; + } + } + + /// One watched change, as the Revit exporter records it. + private static ActivityChange Watched(string person, string session, string utc, ElementChangeKind kind, string uid, string category) + => new ActivityChange + { + ChangedUtc = utc, + SessionGuid = session, + Contributor = new ActivityContributor(person, ContributorSource.Session), + Kind = kind, + Confidence = ElementChangeConfidence.Observed, + UniqueId = uid, + CategoryName = category, + }; + + private static ActivitySession SessionOf(string person, string guid, string started, string ended) + => new ActivitySession + { + SessionGuid = guid, + MachineName = "DESK-1", + RevitVersion = "2027", + StartedUtc = started, + EndedUtc = ended, + Contributor = new ActivityContributor(person, ContributorSource.Session), + }; + + [Test] + public static void TestPatchReplacesAddsDeletesAndKeepsTheRest() + { + var ctx = new CallerTestContext(); + var dir = ctx.PrepareDirectory(); + + // Base: wall (with 2D linework), door hosted by the wall (with a door swing), an element deleted later. + var b = new Doc("view-all", "VIM-ALL"); + var wall = b.AddBox(1, "wall", 0, "A"); + b.AddShape(wall, ShapeKind.ViewLinework, 0); + var door = b.AddBox(2, "door", 5, "D"); + b.AddShape(door, ShapeKind.SymbolicCurve, 5); + b.Omb.FamilyInstanceBuilder.AddUntracked(new FamilyInstance { _Element = { Index = door }, _Host = { Index = wall } }); + b.AddBox(3, "old", 9, "O"); + var basePath = b.Write(dir, "base.vim"); + + // Partial (exported from a temporary 3D view): the wall moved to x = 2 with Mark B, a new element, the door as context. + var p = new Doc("temp-view", "_VIM_EXPORT_1234"); + var pWall = p.AddBox(1, "wall", 2, "B"); + p.AddShape(pWall, ShapeKind.ViewLinework, 99); // 2D linework of a 3D-only partial is not trusted + var pDoor = p.AddBox(2, "door", 5, "D"); + p.Omb.FamilyInstanceBuilder.AddUntracked(new FamilyInstance { _Element = { Index = pDoor }, _Host = { Index = pWall } }); + p.AddBox(4, "new", 7, "N"); + var partialPath = p.Write(dir, "partial.vim"); + + var outPath = Path.Combine(dir, "patched.vim"); + var result = VimPatchService.Patch(basePath, partialPath, outPath, new VimPatchOptions + { + ChangedUniqueIds = new HashSet { "wall", "new" }, + DeletedUniqueIds = new HashSet { "old" }, + UniqueIdAliases = new Dictionary { { "temp-view", "view-all" } }, + }); + + Assert.AreEqual(1, result.Changed); + Assert.AreEqual(1, result.Added); + Assert.AreEqual(1, result.Deleted); + + var vim = VimScene.LoadVim(outPath); + vim.Validate(); + var dm = vim.DocumentModel; + var uids = Enumerable.Range(0, dm.NumElement).Select(i => dm.GetElement(i).UniqueId).ToList(); + CollectionAssert.DoesNotContain(uids, "old"); + CollectionAssert.DoesNotContain(uids, "temp-view"); + Assert.AreEqual(1, uids.Count(u => u == "view-all")); + var wallIndex = uids.IndexOf("wall"); + var doorIndex = uids.IndexOf("door"); + var newIndex = uids.IndexOf("new"); + Assert.AreEqual(wall, wallIndex, "a changed element keeps its index"); + + // Parameters: the wall's replaced, the door's kept. + var marks = Enumerable.Range(0, dm.NumParameter).Select(i => dm.GetParameter(i)).GroupBy(x => x.Element.UniqueId, x => x.Value).ToDictionary(x => x.Key, x => x.ToArray()); + CollectionAssert.AreEqual(new[] { "B" }, marks["wall"]); + CollectionAssert.AreEqual(new[] { "D" }, marks["door"]); + CollectionAssert.AreEqual(new[] { "N" }, marks["new"]); + + // Geometry: one node per element, the wall at its new place, aligned with the instances. + var g = vim.Document.Geometry; + Assert.AreEqual(dm.NumNode, g.InstanceTransforms.Count); + var nodesOf = Enumerable.Range(0, dm.NumNode).GroupBy(i => dm.GetNode(i).Element.UniqueId).ToDictionary(x => x.Key, x => x.ToArray()); + Assert.AreEqual(1, nodesOf["wall"].Count()); + Assert.AreEqual(2f, g.InstanceTransforms[nodesOf["wall"].Single()].Translation.X); + Assert.AreEqual(5f, g.InstanceTransforms[nodesOf["door"].Single()].Translation.X); + + // Shapes: the wall keeps its base 2D linework (x = 0), the partial's is not written; the door keeps its swing. + var shapes = Enumerable.Range(0, dm.NumShape).Select(i => (Uid: dm.GetShape(i).Element.UniqueId, Kind: dm.GetShape(i).GetKind(), X: g.ShapeVertices[g.ShapeVertexOffsets[i]].X)).ToList(); + CollectionAssert.AreEquivalent(new[] { ("wall", ShapeKind.ViewLinework, 0f), ("door", ShapeKind.SymbolicCurve, 5f) }, shapes); + + // The door still hosted by the wall; the new element visible in the base's export view. + var fi = Enumerable.Range(0, dm.NumFamilyInstance).Select(dm.GetFamilyInstance).Single(); + Assert.AreEqual(doorIndex, fi._Element.Index); + Assert.AreEqual(wallIndex, fi._Host.Index); + var eiv = Enumerable.Range(0, dm.NumElementInView).Select(dm.GetElementInView).ToList(); + Assert.AreEqual(1, dm.NumView); + Assert.IsTrue(eiv.Any(x => x._Element.Index == newIndex && x.View.Title == "VIM-ALL")); + Assert.AreEqual(1, eiv.Count(x => x._Element.Index == wallIndex)); + + // Provenance: the wall and the new element come from the partial export, the door from the base export. + var revisionOf = Enumerable.Range(0, dm.NumElementInExportRevision).Select(dm.GetElementInExportRevision) + .ToDictionary(x => x.Element.UniqueId, x => x.ExportRevision.GetKind()); + Assert.AreEqual(ExportRevisionKind.PartialExport, revisionOf["wall"]); + Assert.AreEqual(ExportRevisionKind.PartialExport, revisionOf["new"]); + Assert.AreEqual(ExportRevisionKind.FullExport, revisionOf["door"]); + } + + /// + /// A sync from another local file of the same central: its host document has another Title and PathName. + /// Matched by title and path, every element of the partial was a new element next to the old one, which stayed + /// stale (Sync to VIM, 2026-10-12); with the host is the host. + /// + [Test] + public static void TestPatchFromAnotherLocalReplacesTheHostRows() + { + var ctx = new CallerTestContext(); + var dir = ctx.PrepareDirectory(); + + var b = new Doc("view-all", "VIM-ALL", "Model", "C:\\locals\\Model_sam.rvt"); + b.AddBox(1, "wall", 0, "A"); + b.AddBox(2, "door", 5, "D"); + var basePath = b.Write(dir, "base.vim"); + + var p = new Doc("temp-view", "_VIM_EXPORT_1234", "Model_jo", "C:\\locals\\Model_jo.rvt"); + p.AddBox(1, "wall", 2, "B"); + var partialPath = p.Write(dir, "partial.vim"); + + VimPatchOptions Options(PatchDocumentMatch match) => new VimPatchOptions + { + ChangedUniqueIds = new HashSet { "wall" }, + UniqueIdAliases = new Dictionary { { "temp-view", "view-all" } }, + DocumentMatch = match, + }; + + // The old matching: the wall is "added" and the base's wall reported as not re-exported. + var old = VimPatchService.Patch(basePath, partialPath, Path.Combine(dir, "patched-old.vim"), Options(PatchDocumentMatch.TitleAndPath)); + Assert.AreEqual(1, old.ChangedButNotInPartial.Count); + + var outPath = Path.Combine(dir, "patched.vim"); + var result = VimPatchService.Patch(basePath, partialPath, outPath, Options(PatchDocumentMatch.HostAndLinkTitles)); + Assert.AreEqual(1, result.Changed); + Assert.AreEqual(0, result.Added); + Assert.AreEqual(0, result.ChangedButNotInPartial.Count); + + var vim = VimScene.LoadVim(outPath); + vim.Validate(); + var dm = vim.DocumentModel; + Assert.AreEqual(1, dm.NumBimDocument, "one host document"); + Assert.AreEqual("Model", dm.GetBimDocument(0).Title, "the base's document row is kept"); + var uids = Enumerable.Range(0, dm.NumElement).Select(i => dm.GetElement(i).UniqueId).ToList(); + Assert.AreEqual(1, uids.Count(u => u == "wall")); + var marks = Enumerable.Range(0, dm.NumParameter).Select(i => dm.GetParameter(i)).ToDictionary(x => x.Element.UniqueId, x => x.Value); + Assert.AreEqual("B", marks["wall"]); + Assert.AreEqual("D", marks["door"]); + } + + /// + /// The activity log is history: two syncs in a row add their rows to the ones already in the file, the open + /// session is one row with a moving end rather than one row per sync, and a change about an element that has + /// just been deleted survives - that row is the whole point of the feature. + /// + [Test] + public static void TestPatchAccumulatesTheActivityLog() + { + var ctx = new CallerTestContext(); + var dir = ctx.PrepareDirectory(); + const string s1 = "11111111-1111-1111-1111-111111111111"; + const string s2 = "22222222-2222-2222-2222-222222222222"; + + // The base: a full export which already recorded two of Sam's changes. + var b = new Doc("view-all", "VIM-ALL"); + b.AddBox(1, "wall", 0, "A"); + b.AddBox(2, "door", 5, "D"); + b.AddBox(3, "old", 9, "O"); + var baseLog = new ActivityLog + { + Sessions = { SessionOf("s.arsenault", s1, "2026-10-12T08:00:00.0000000Z", "2026-10-12T09:00:00.0000000Z") }, + Changes = + { + Watched("s.arsenault", s1, "2026-10-12T08:10:00.0000000Z", ElementChangeKind.Added, "wall", "Walls"), + Watched("s.arsenault", s1, "2026-10-12T08:20:00.0000000Z", ElementChangeKind.Added, "door", "Doors"), + }, + }; + var basePath = b.Write(dir, "base.vim", baseLog); + + // The first sync: the same session, still open (a later end), one modified wall and one deleted element - + // the deleted one has no Element row to point at, in this file or any other. + var p = new Doc("temp-view", "_VIM_EXPORT_1234"); + p.AddBox(1, "wall", 2, "B"); + var delta1 = new ActivityLog + { + Sessions = { SessionOf("s.arsenault", s1, "2026-10-12T08:00:00.0000000Z", "2026-10-12T10:30:00.0000000Z") }, + Changes = + { + Watched("s.arsenault", s1, "2026-10-12T10:05:00.0000000Z", ElementChangeKind.Modified, "wall", "Walls"), + Watched("s.arsenault", s1, "2026-10-12T10:20:00.0000000Z", ElementChangeKind.Deleted, "old", "Generic Models"), + }, + Notes = + { + new ActivityNote + { + OccurredUtc = "2026-10-12T10:30:00.0000000Z", + Kind = ActivityMarkerKind.HistoryReset, + Description = "This model looks like it was re-saved as a new central.", + }, + }, + }; + var once = Path.Combine(dir, "patched-1.vim"); + VimPatchService.Patch(basePath, p.Write(dir, "partial-1.vim", delta1), once, new VimPatchOptions + { + ChangedUniqueIds = new HashSet { "wall" }, + DeletedUniqueIds = new HashSet { "old" }, + UniqueIdAliases = new Dictionary { { "temp-view", "view-all" } }, + }); + + var afterOne = ActivityReport.Read(once); + Assert.AreEqual(4, afterOne.Changes.Count, "the base's two rows plus this sync's two"); + Assert.AreEqual(1, afterOne.Sessions.Count, "one session, not one per sync"); + Assert.AreEqual("2026-10-12T10:30:00.0000000Z", afterOne.Sessions[0].EndedUtc, "the fresher reading of the session wins"); + var deletedRow = afterOne.Changes.Single(c => c.Kind == ElementChangeKind.Deleted); + Assert.AreEqual("old", deletedRow.UniqueId); + Assert.AreEqual("Generic Models", deletedRow.Category, "a deletion still says what it was"); + CollectionAssert.DoesNotContain( + Enumerable.Range(0, VimScene.LoadVim(once).DocumentModel.NumElement) + .Select(i => VimScene.LoadVim(once).DocumentModel.GetElement(i).UniqueId).ToList(), + "old"); + Assert.AreEqual(1, afterOne.Markers.Count); + Assert.AreEqual(ActivityMarkerKind.HistoryReset, afterOne.Markers[0].Kind); + + // A second sync, by somebody else, in a second session. + var p2 = new Doc("temp-view-2", "_VIM_EXPORT_5678"); + p2.AddBox(2, "door", 6, "D2"); + var delta2 = new ActivityLog + { + Sessions = { SessionOf("j.tremblay", s2, "2026-10-12T11:00:00.0000000Z", "2026-10-12T11:40:00.0000000Z") }, + Changes = { Watched("j.tremblay", s2, "2026-10-12T11:30:00.0000000Z", ElementChangeKind.Modified, "door", "Doors") }, + }; + var twice = Path.Combine(dir, "patched-2.vim"); + VimPatchService.Patch(once, p2.Write(dir, "partial-2.vim", delta2), twice, new VimPatchOptions + { + ChangedUniqueIds = new HashSet { "door" }, + UniqueIdAliases = new Dictionary { { "temp-view-2", "view-all" } }, + }); + + VimScene.LoadVim(twice).Validate(); + var afterTwo = ActivityReport.Read(twice); + Assert.AreEqual(5, afterTwo.Changes.Count); + Assert.AreEqual(2, afterTwo.Sessions.Count); + CollectionAssert.AreEquivalent(new[] { "s.arsenault", "j.tremblay" }, afterTwo.Sessions.Select(x => x.Person).ToList()); + Assert.AreEqual(1, afterTwo.Markers.Count, "the marker written by the first sync is still there"); + Assert.IsTrue(afterTwo.Changes.All(c => c.VimRevision.Length > 0), "every row names the export that wrote it"); + + // The billing summary: one row per person per day per category. + var summary = ActivityReport.Summarize(afterTwo); + Assert.AreEqual(1, summary.Count(r => r.Person == "j.tremblay")); + var sam = summary.Where(r => r.Person == "s.arsenault").ToList(); + Assert.AreEqual(2, sam.Sum(r => r.Added)); + Assert.AreEqual(1, sam.Sum(r => r.Changed)); + Assert.AreEqual(1, sam.Sum(r => r.Deleted)); + Assert.AreEqual(2.5, sam.First().HoursInModelOnThatDay, 0.01, "08:00 to 10:30"); + + // Retention, and "strip activity on export". + var trimmed = Path.Combine(dir, "patched-trimmed.vim"); + var trimResult = VimPatchService.Patch(once, p2.Write(dir, "partial-3.vim", delta2), trimmed, new VimPatchOptions + { + ChangedUniqueIds = new HashSet { "door" }, + UniqueIdAliases = new Dictionary { { "temp-view-2", "view-all" } }, + ActivityOldestUtc = "2026-10-12T10:00:00.0000000Z", + }); + Assert.AreEqual(2, trimResult.ActivityRowsTrimmed, "the two 08:xx rows are older than the cut-off"); + Assert.AreEqual(3, ActivityReport.Read(trimmed).Changes.Count); + + var stripped = Path.Combine(dir, "patched-stripped.vim"); + VimPatchService.Patch(once, p2.Write(dir, "partial-4.vim", delta2), stripped, new VimPatchOptions + { + ChangedUniqueIds = new HashSet { "door" }, + UniqueIdAliases = new Dictionary { { "temp-view-2", "view-all" } }, + KeepActivityLog = false, + }); + Assert.IsTrue(ActivityReport.Read(stripped).IsEmpty, "nothing of the log is left in a stripped file"); + VimScene.LoadVim(stripped).Validate(); + } + } +} diff --git a/src/cs/vim/Vim.Format/Activity/ActivityLog.cs b/src/cs/vim/Vim.Format/Activity/ActivityLog.cs new file mode 100644 index 0000000..1f31bae --- /dev/null +++ b/src/cs/vim/Vim.Format/Activity/ActivityLog.cs @@ -0,0 +1,339 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using Vim.Format.ObjectModel; + +namespace Vim.Format.Activity +{ + /// One person, as one source names them. Two sources naming the same person are two contributors. + public sealed class ActivityContributor : IEquatable + { + public string Name = ""; + public ContributorSource Source; + + public ActivityContributor() { } + + public ActivityContributor(string name, ContributorSource source) + { + Name = name ?? ""; + Source = source; + } + + public bool Equals(ActivityContributor other) + => other != null && Source == other.Source && string.Equals(Name, other.Name, StringComparison.Ordinal); + + public override bool Equals(object obj) => Equals(obj as ActivityContributor); + + public override int GetHashCode() => (Name ?? "").GetHashCode() ^ (int) Source; + } + + /// One Revit session VIM watched. Written again at every sync it feeds, with a later . + public sealed class ActivitySession + { + public string SessionGuid = "", MachineName = "", RevitVersion = "", StartedUtc = "", EndedUtc = ""; + public ActivityContributor Contributor = new ActivityContributor(); + } + + /// One element changed once. + public sealed class ActivityChange + { + public string ChangedUtc = ""; + /// The session which made it; empty when the change arrived from the central model. + public string SessionGuid = ""; + public ActivityContributor Contributor = new ActivityContributor(); + public ElementChangeKind Kind; + public ElementChangeConfidence Confidence; + public string UniqueId = "", CategoryName = "", FamilyName = "", TypeName = "", ElementName = "", LevelName = "", WorksetName = ""; + } + + /// Something that happened to the log itself (see ). + public sealed class ActivityNote + { + public string OccurredUtc = "", Description = "", SessionGuid = ""; + public ActivityContributor Contributor = new ActivityContributor(); + public ActivityMarkerKind Kind; + } + + /// + /// The activity log as plain data: what a writer collects and what it writes into a .vim as the Contributor, + /// SyncSession, ElementChange and ActivityMarker tables (object model 5.11.0). It also reads and writes itself as + /// text, which is how the Revit exporter keeps it in a sidecar file next to the .vim: a full export rewrites the + /// whole .vim, so without that sidecar every full export would start the history again from nothing. + /// + public sealed class ActivityLog + { + /// The text format version of the sidecar file. A later one is not read. + public const int TextFormatVersion = 1; + + public List Sessions = new List(); + public List Changes = new List(); + public List Notes = new List(); + + public bool IsEmpty => Sessions.Count == 0 && Changes.Count == 0 && Notes.Count == 0; + + /// + /// Adds everything of that this log does not already hold: a session is the same + /// session when the guid matches (the later reading of its start and end wins), a change and a note are the + /// same when every field matches. + /// + public void Merge(ActivityLog other) + { + if (other == null) return; + + var byGuid = Sessions.Where(s => s.SessionGuid.Length > 0).GroupBy(s => s.SessionGuid) + .ToDictionary(g => g.Key, g => g.First(), StringComparer.Ordinal); + foreach (var s in other.Sessions) + { + if (s.SessionGuid.Length > 0 && byGuid.TryGetValue(s.SessionGuid, out var mine)) + { + if (string.CompareOrdinal(s.StartedUtc, mine.StartedUtc) < 0 && s.StartedUtc.Length > 0) mine.StartedUtc = s.StartedUtc; + if (string.CompareOrdinal(s.EndedUtc, mine.EndedUtc) > 0) mine.EndedUtc = s.EndedUtc; + if (mine.MachineName.Length == 0) mine.MachineName = s.MachineName; + if (mine.RevitVersion.Length == 0) mine.RevitVersion = s.RevitVersion; + continue; + } + Sessions.Add(s); + if (s.SessionGuid.Length > 0) byGuid[s.SessionGuid] = s; + } + + var seenChanges = new HashSet(Changes.Select(ChangeKey), StringComparer.Ordinal); + foreach (var c in other.Changes) + if (seenChanges.Add(ChangeKey(c))) + Changes.Add(c); + + var seenNotes = new HashSet(Notes.Select(NoteKey), StringComparer.Ordinal); + foreach (var n in other.Notes) + if (seenNotes.Add(NoteKey(n))) + Notes.Add(n); + } + + /// + /// Drops what is older than and, past , the + /// oldest changes, so a year of logging does not quietly grow the .vim. A session nothing points at any more + /// goes with them. Returns how many change rows were dropped (0 = nothing was trimmed). + /// Either cap is off when it is 0 or less. + /// + public int Trim(DateTime nowUtc, int retentionDays, int maxChangeRows) + { + var before = Changes.Count; + + if (retentionDays > 0) + { + var oldest = nowUtc.AddDays(-retentionDays); + Changes = Changes.Where(c => !IsOlderThan(c.ChangedUtc, oldest)).ToList(); + Notes = Notes.Where(n => !IsOlderThan(n.OccurredUtc, oldest)).ToList(); + } + + if (maxChangeRows > 0 && Changes.Count > maxChangeRows) + Changes = Changes.OrderBy(c => c.ChangedUtc, StringComparer.Ordinal).Skip(Changes.Count - maxChangeRows).ToList(); + + var used = new HashSet(Changes.Select(c => c.SessionGuid).Concat(Notes.Select(n => n.SessionGuid)), StringComparer.Ordinal); + Sessions = Sessions.Where(s => used.Contains(s.SessionGuid)).ToList(); + + return before - Changes.Count; + } + + /// + /// Writes the four activity tables into the builder (nothing when the log is empty). The sessions are bound to + /// ; the changes and the markers are left pointing at no ExportRevision + /// (-1), which reads as "the export that wrote this file" and is resolved to a row the first time the file is + /// patched (). + /// + public void WriteInto(DocumentBuilder db, int bimDocumentIndex) + { + if (db == null || IsEmpty) + return; + + var contributors = new List(); + var contributorIndex = new Dictionary(); + int Person(ActivityContributor c) + { + c = c ?? new ActivityContributor(); + if (contributorIndex.TryGetValue(c, out var i)) return i; + contributors.Add(c); + return contributorIndex[c] = contributors.Count - 1; + } + + var sessionIndex = new Dictionary(StringComparer.Ordinal); + for (var i = 0; i < Sessions.Count; ++i) + if (Sessions[i].SessionGuid.Length > 0 && !sessionIndex.ContainsKey(Sessions[i].SessionGuid)) + sessionIndex[Sessions[i].SessionGuid] = i; + int SessionOf(string guid) + => guid != null && sessionIndex.TryGetValue(guid, out var i) ? i : -1; + + // The contributors have to be numbered before the tables that point at them are written. + var sessionPeople = Sessions.Select(s => Person(s.Contributor)).ToArray(); + var changePeople = Changes.Select(c => Person(c.Contributor)).ToArray(); + var notePeople = Notes.Select(n => Person(n.Contributor)).ToArray(); + + if (contributors.Count > 0) + { + var t = Table(db,TableNames.Contributor); + t.AddStringColumn("string:Name", contributors.Select(c => c.Name ?? "")); + t.AddDataColumn("int:Source", contributors.Select(c => (int) c.Source)); + } + + if (Sessions.Count > 0) + { + var t = Table(db,TableNames.SyncSession); + t.AddStringColumn("string:SessionGuid", Sessions.Select(s => s.SessionGuid ?? "")); + t.AddStringColumn("string:MachineName", Sessions.Select(s => s.MachineName ?? "")); + t.AddStringColumn("string:RevitVersion", Sessions.Select(s => s.RevitVersion ?? "")); + t.AddStringColumn("string:StartedUtc", Sessions.Select(s => s.StartedUtc ?? "")); + t.AddStringColumn("string:EndedUtc", Sessions.Select(s => s.EndedUtc ?? "")); + t.AddIndexColumn("index:" + TableNames.Contributor + ":Contributor", sessionPeople); + t.AddIndexColumn("index:" + TableNames.BimDocument + ":BimDocument", Sessions.Select(_ => bimDocumentIndex)); + } + + if (Changes.Count > 0) + { + var t = Table(db,TableNames.ElementChange); + t.AddStringColumn("string:ChangedUtc", Changes.Select(c => c.ChangedUtc ?? "")); + t.AddStringColumn("string:UniqueId", Changes.Select(c => c.UniqueId ?? "")); + t.AddStringColumn("string:CategoryName", Changes.Select(c => c.CategoryName ?? "")); + t.AddStringColumn("string:FamilyName", Changes.Select(c => c.FamilyName ?? "")); + t.AddStringColumn("string:TypeName", Changes.Select(c => c.TypeName ?? "")); + t.AddStringColumn("string:ElementName", Changes.Select(c => c.ElementName ?? "")); + t.AddStringColumn("string:LevelName", Changes.Select(c => c.LevelName ?? "")); + t.AddStringColumn("string:WorksetName", Changes.Select(c => c.WorksetName ?? "")); + t.AddDataColumn("int:ChangeKind", Changes.Select(c => (int) c.Kind)); + t.AddDataColumn("int:Confidence", Changes.Select(c => (int) c.Confidence)); + t.AddIndexColumn("index:" + TableNames.Contributor + ":Contributor", changePeople); + t.AddIndexColumn("index:" + TableNames.SyncSession + ":SyncSession", Changes.Select(c => SessionOf(c.SessionGuid))); + t.AddIndexColumn("index:" + TableNames.ExportRevision + ":ExportRevision", Changes.Select(_ => -1)); + } + + if (Notes.Count > 0) + { + var t = Table(db,TableNames.ActivityMarker); + t.AddStringColumn("string:OccurredUtc", Notes.Select(n => n.OccurredUtc ?? "")); + t.AddStringColumn("string:Description", Notes.Select(n => n.Description ?? "")); + t.AddDataColumn("int:Kind", Notes.Select(n => (int) n.Kind)); + t.AddIndexColumn("index:" + TableNames.Contributor + ":Contributor", notePeople); + t.AddIndexColumn("index:" + TableNames.SyncSession + ":SyncSession", Notes.Select(n => SessionOf(n.SessionGuid))); + t.AddIndexColumn("index:" + TableNames.ExportRevision + ":ExportRevision", Notes.Select(_ => -1)); + } + } + + // ------------------------------------------------------------------------------------------------------- + // The sidecar text file + // ------------------------------------------------------------------------------------------------------- + + public string ToText() + { + var sb = new StringBuilder(); + sb.Append("vim-activity=").Append(TextFormatVersion).Append('\n'); + sb.Append("#sessions sessionGuid\tmachine\trevitVersion\tstartedUtc\tendedUtc\tcontributor\tsource\n"); + foreach (var s in Sessions) + Line(sb, s.SessionGuid, s.MachineName, s.RevitVersion, s.StartedUtc, s.EndedUtc, + s.Contributor?.Name, Num((int?) s.Contributor?.Source ?? 0)); + sb.Append("#changes changedUtc\tsessionGuid\tcontributor\tsource\tkind\tconfidence\tuniqueId\tcategory\tfamily\ttype\tname\tlevel\tworkset\n"); + foreach (var c in Changes) + Line(sb, c.ChangedUtc, c.SessionGuid, c.Contributor?.Name, Num((int?) c.Contributor?.Source ?? 0), + Num((int) c.Kind), Num((int) c.Confidence), c.UniqueId, + c.CategoryName, c.FamilyName, c.TypeName, c.ElementName, c.LevelName, c.WorksetName); + sb.Append("#notes occurredUtc\tkind\tcontributor\tsource\tsessionGuid\tdescription\n"); + foreach (var n in Notes) + Line(sb, n.OccurredUtc, Num((int) n.Kind), n.Contributor?.Name, Num((int?) n.Contributor?.Source ?? 0), + n.SessionGuid, n.Description); + return sb.ToString(); + } + + /// Reads the sidecar text; null when it is unreadable or written by a later version. + public static ActivityLog FromText(string text) + { + if (string.IsNullOrEmpty(text)) + return null; + var log = new ActivityLog(); + var section = 0; // 1 sessions, 2 changes, 3 notes + foreach (var raw in text.Split('\n')) + { + var line = raw.TrimEnd('\r'); + if (line.Length == 0) continue; + if (line.StartsWith("#sessions")) { section = 1; continue; } + if (line.StartsWith("#changes")) { section = 2; continue; } + if (line.StartsWith("#notes")) { section = 3; continue; } + if (section == 0) + { + var eq = line.IndexOf('='); + if (eq < 0) continue; + if (line.Substring(0, eq) != "vim-activity") continue; + if (!int.TryParse(line.Substring(eq + 1), NumberStyles.Integer, CultureInfo.InvariantCulture, out var v) || v < 1 || v > TextFormatVersion) + return null; + continue; + } + var t = line.Split('\t'); + string At(int i) => i < t.Length ? t[i] : ""; + int Int(int i) { int.TryParse(At(i), NumberStyles.Integer, CultureInfo.InvariantCulture, out var n); return n; } + switch (section) + { + case 1: + log.Sessions.Add(new ActivitySession + { + SessionGuid = At(0), MachineName = At(1), RevitVersion = At(2), + StartedUtc = At(3), EndedUtc = At(4), + Contributor = new ActivityContributor(At(5), (ContributorSource) Int(6)), + }); + break; + case 2: + log.Changes.Add(new ActivityChange + { + ChangedUtc = At(0), SessionGuid = At(1), + Contributor = new ActivityContributor(At(2), (ContributorSource) Int(3)), + Kind = (ElementChangeKind) Int(4), Confidence = (ElementChangeConfidence) Int(5), + UniqueId = At(6), CategoryName = At(7), FamilyName = At(8), TypeName = At(9), + ElementName = At(10), LevelName = At(11), WorksetName = At(12), + }); + break; + default: + log.Notes.Add(new ActivityNote + { + OccurredUtc = At(0), Kind = (ActivityMarkerKind) Int(1), + Contributor = new ActivityContributor(At(2), (ContributorSource) Int(3)), + SessionGuid = At(4), Description = At(5), + }); + break; + } + } + return log; + } + + /// + /// The builder's table for this entity, emptied: an exporter's document builder already carries one row-less + /// table per entity of the object model, and these four are filled here rather than through the entity + /// builders (there is nothing in the Revit document to build them from). + /// + private static EntityTableBuilder Table(DocumentBuilder db, string name) + { + var t = db.GetTableBuilderOrCreate(name); + t.Clear(); + return t; + } + + private static string Num(int n) + => n.ToString(CultureInfo.InvariantCulture); + + private static void Line(StringBuilder sb, params string[] cells) + { + for (var i = 0; i < cells.Length; ++i) + { + if (i > 0) sb.Append('\t'); + sb.Append((cells[i] ?? "").Replace('\t', ' ').Replace('\n', ' ').Replace('\r', ' ')); + } + sb.Append('\n'); + } + + private static bool IsOlderThan(string utc, DateTime oldest) + => DateTime.TryParse(utc, CultureInfo.InvariantCulture, + DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out var t) && t < oldest; + + private static string ChangeKey(ActivityChange c) + => string.Join("\u001f", c.ChangedUtc, c.SessionGuid, c.Contributor?.Name, ((int?) c.Contributor?.Source ?? 0).ToString(), + ((int) c.Kind).ToString(), ((int) c.Confidence).ToString(), c.UniqueId); + + private static string NoteKey(ActivityNote n) + => string.Join("\u001f", n.OccurredUtc, ((int) n.Kind).ToString(), n.Contributor?.Name, n.SessionGuid, n.Description); + } +} diff --git a/src/cs/vim/Vim.Format/Activity/ActivityReport.cs b/src/cs/vim/Vim.Format/Activity/ActivityReport.cs new file mode 100644 index 0000000..8d1b2ac --- /dev/null +++ b/src/cs/vim/Vim.Format/Activity/ActivityReport.cs @@ -0,0 +1,333 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using Vim.Format.Merge; +using Vim.Format.ObjectModel; + +namespace Vim.Format.Activity +{ + /// + /// Reads the activity log of a .vim file (object model 5.11.0: Contributor, SyncSession, ElementChange, + /// ActivityMarker) and writes it out as CSV, for billing and for anyone who wants to dig. + /// + /// Three files, because they are three different shapes (C:\VimFlexLocal\work\exporter\LIVE-SYNC-UX.md, 4.3): + /// "[name].activity-summary.csv" is the billing deliverable - one row per person per day per category, with the + /// added / changed / deleted counts and that person's sessions and hours in the model that day; + /// "[name].activity-changes.csv" is one row per change; "[name].activity-notes.csv" holds the markers, the lines + /// that say the log itself cannot be read at face value (a model re-saved as a new central, a reloaded link). + /// + /// + /// Read it as the log reads itself: a name with Confidence = Observed is a person VIM watched work; FromCentral is + /// Revit's word, second-hand, and a re-save as a new central rewrites those names for every element at once; + /// Unknown is a deletion in somebody else's local, which Revit records nothing about. + /// + /// + public static class ActivityReport + { + public sealed class Change + { + public string ChangedUtc = ""; + public string Person = ""; + public ContributorSource Source; + public ElementChangeKind Kind; + public ElementChangeConfidence Confidence; + public string SessionGuid = "", MachineName = "", RevitVersion = ""; + public string VimRevision = ""; + public string UniqueId = "", Category = "", Family = "", Type = "", Name = "", Level = "", Workset = ""; + + /// The calendar day of the change, in UTC ("yyyy-MM-dd"); "" when the time is unreadable. + public string Day + => TryParseUtc(ChangedUtc, out var t) ? t.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture) : ""; + } + + public sealed class Session + { + public string SessionGuid = "", MachineName = "", RevitVersion = "", StartedUtc = "", EndedUtc = ""; + public string Person = ""; + public ContributorSource Source; + + /// Start to end, in hours; 0 when either end is unreadable or the end is before the start. + public double Hours + { + get + { + if (!TryParseUtc(StartedUtc, out var a) || !TryParseUtc(EndedUtc, out var b)) return 0; + var h = (b - a).TotalHours; + return h > 0 ? h : 0; + } + } + } + + public sealed class Marker + { + public string OccurredUtc = "", Description = "", Person = "", SessionGuid = "", VimRevision = ""; + public ActivityMarkerKind Kind; + } + + public sealed class Log + { + public List Changes = new List(); + public List Sessions = new List(); + public List Markers = new List(); + public bool IsEmpty => Changes.Count == 0 && Sessions.Count == 0 && Markers.Count == 0; + } + + /// One row of the billing CSV: one person, one day, one category. + public sealed class SummaryRow + { + public string Person = "", Day = "", Category = ""; + public ContributorSource Source; + public int Added, Changed, Deleted; + /// Per person and day, not per category: the same number repeats on that person's rows for the day. + public int SessionsOnThatDay; + /// Per person and day, not per category (see ). + public double HoursInModelOnThatDay; + } + + public static Log Read(string vimFilePath) + => Read(VimTables.Load(vimFilePath)); + + public static Log Read(VimTables vim) + { + var log = new Log(); + + var contributorNames = vim.Strs(TableNames.Contributor, "string:Name") ?? Array.Empty(); + var contributorSources = vim.Data(TableNames.Contributor, "int:Source") ?? Array.Empty(); + string PersonName(int i) => i >= 0 && i < contributorNames.Length ? contributorNames[i] ?? "" : ""; + ContributorSource PersonSource(int i) + => (ContributorSource)(i >= 0 && i < contributorSources.Length ? contributorSources[i] : 0); + + var revisions = vim.Strs(TableNames.ExportRevision, "string:VimRevision") ?? Array.Empty(); + // No revision row: the exporter writes -1 for "the export that wrote this file", so answer with this file. + var ownRevision = vim.Header?.PersistingId ?? ""; + string Revision(int i) => i >= 0 && i < revisions.Length ? revisions[i] ?? "" : ownRevision; + + var nSessions = vim.NumRows(TableNames.SyncSession); + var sessionGuids = vim.Strs(TableNames.SyncSession, "string:SessionGuid") ?? new string[nSessions]; + var sessionMachines = vim.Strs(TableNames.SyncSession, "string:MachineName") ?? new string[nSessions]; + var sessionVersions = vim.Strs(TableNames.SyncSession, "string:RevitVersion") ?? new string[nSessions]; + var sessionStarts = vim.Strs(TableNames.SyncSession, "string:StartedUtc") ?? new string[nSessions]; + var sessionEnds = vim.Strs(TableNames.SyncSession, "string:EndedUtc") ?? new string[nSessions]; + var sessionContributors = vim.Index(TableNames.SyncSession, "index:" + TableNames.Contributor + ":Contributor"); + for (var i = 0; i < nSessions; ++i) + { + var c = sessionContributors == null ? -1 : sessionContributors[i]; + log.Sessions.Add(new Session + { + SessionGuid = sessionGuids[i] ?? "", + MachineName = sessionMachines[i] ?? "", + RevitVersion = sessionVersions[i] ?? "", + StartedUtc = sessionStarts[i] ?? "", + EndedUtc = sessionEnds[i] ?? "", + Person = PersonName(c), + Source = PersonSource(c), + }); + } + + var nChanges = vim.NumRows(TableNames.ElementChange); + if (nChanges > 0) + { + string[] Col(string name) => vim.Strs(TableNames.ElementChange, name) ?? new string[nChanges]; + var changedUtc = Col("string:ChangedUtc"); + var uniqueIds = Col("string:UniqueId"); + var categories = Col("string:CategoryName"); + var families = Col("string:FamilyName"); + var types = Col("string:TypeName"); + var names = Col("string:ElementName"); + var levels = Col("string:LevelName"); + var worksets = Col("string:WorksetName"); + var kinds = vim.Data(TableNames.ElementChange, "int:ChangeKind") ?? new int[nChanges]; + var confidences = vim.Data(TableNames.ElementChange, "int:Confidence") ?? new int[nChanges]; + var changeContributors = vim.Index(TableNames.ElementChange, "index:" + TableNames.Contributor + ":Contributor"); + var changeSessions = vim.Index(TableNames.ElementChange, "index:" + TableNames.SyncSession + ":SyncSession"); + var changeRevisions = vim.Index(TableNames.ElementChange, "index:" + TableNames.ExportRevision + ":ExportRevision"); + + for (var i = 0; i < nChanges; ++i) + { + var c = changeContributors == null ? -1 : changeContributors[i]; + var s = changeSessions == null ? -1 : changeSessions[i]; + log.Changes.Add(new Change + { + ChangedUtc = changedUtc[i] ?? "", + Person = PersonName(c), + Source = PersonSource(c), + Kind = (ElementChangeKind)kinds[i], + Confidence = (ElementChangeConfidence)confidences[i], + SessionGuid = s >= 0 && s < log.Sessions.Count ? log.Sessions[s].SessionGuid : "", + MachineName = s >= 0 && s < log.Sessions.Count ? log.Sessions[s].MachineName : "", + RevitVersion = s >= 0 && s < log.Sessions.Count ? log.Sessions[s].RevitVersion : "", + VimRevision = Revision(changeRevisions == null ? -1 : changeRevisions[i]), + UniqueId = uniqueIds[i] ?? "", + Category = categories[i] ?? "", + Family = families[i] ?? "", + Type = types[i] ?? "", + Name = names[i] ?? "", + Level = levels[i] ?? "", + Workset = worksets[i] ?? "", + }); + } + } + + var nMarkers = vim.NumRows(TableNames.ActivityMarker); + if (nMarkers > 0) + { + var occurred = vim.Strs(TableNames.ActivityMarker, "string:OccurredUtc") ?? new string[nMarkers]; + var descriptions = vim.Strs(TableNames.ActivityMarker, "string:Description") ?? new string[nMarkers]; + var markerKinds = vim.Data(TableNames.ActivityMarker, "int:Kind") ?? new int[nMarkers]; + var markerContributors = vim.Index(TableNames.ActivityMarker, "index:" + TableNames.Contributor + ":Contributor"); + var markerSessions = vim.Index(TableNames.ActivityMarker, "index:" + TableNames.SyncSession + ":SyncSession"); + var markerRevisions = vim.Index(TableNames.ActivityMarker, "index:" + TableNames.ExportRevision + ":ExportRevision"); + for (var i = 0; i < nMarkers; ++i) + { + var s = markerSessions == null ? -1 : markerSessions[i]; + log.Markers.Add(new Marker + { + OccurredUtc = occurred[i] ?? "", + Description = descriptions[i] ?? "", + Kind = (ActivityMarkerKind)markerKinds[i], + Person = PersonName(markerContributors == null ? -1 : markerContributors[i]), + SessionGuid = s >= 0 && s < log.Sessions.Count ? log.Sessions[s].SessionGuid : "", + VimRevision = Revision(markerRevisions == null ? -1 : markerRevisions[i]), + }); + } + } + + return log; + } + + /// One row per person per day per category, sorted by day, person then category. + public static List Summarize(Log log) + { + // A person's sessions and hours for a day: a session counts for every day it changed something on. + var hoursOf = new Dictionary(StringComparer.Ordinal); + var sessionsOf = new Dictionary>(StringComparer.Ordinal); + var sessionByGuid = log.Sessions.GroupBy(s => s.SessionGuid ?? "").ToDictionary(g => g.Key, g => g.First(), StringComparer.Ordinal); + foreach (var g in log.Changes.Where(c => c.SessionGuid.Length > 0).GroupBy(c => c.Person + "\u001f" + c.Day)) + { + var guids = new HashSet(g.Select(c => c.SessionGuid), StringComparer.Ordinal); + sessionsOf[g.Key] = guids; + hoursOf[g.Key] = guids.Sum(guid => sessionByGuid.TryGetValue(guid, out var s) ? s.Hours : 0); + } + + var rows = new List(); + foreach (var g in log.Changes.GroupBy(c => new { c.Person, c.Source, c.Day, c.Category })) + { + var dayKey = g.Key.Person + "\u001f" + g.Key.Day; + rows.Add(new SummaryRow + { + Person = g.Key.Person, + Source = g.Key.Source, + Day = g.Key.Day, + Category = g.Key.Category, + Added = g.Count(c => c.Kind == ElementChangeKind.Added), + Changed = g.Count(c => c.Kind == ElementChangeKind.Modified), + Deleted = g.Count(c => c.Kind == ElementChangeKind.Deleted), + SessionsOnThatDay = sessionsOf.TryGetValue(dayKey, out var s) ? s.Count : 0, + HoursInModelOnThatDay = hoursOf.TryGetValue(dayKey, out var h) ? Math.Round(h, 2) : 0, + }); + } + return rows + .OrderBy(r => r.Day, StringComparer.Ordinal) + .ThenBy(r => r.Person, StringComparer.Ordinal) + .ThenBy(r => r.Category, StringComparer.Ordinal) + .ToList(); + } + + /// + /// Writes the three CSV files for a .vim, next to it or into . + /// Returns the paths written, in the order summary, changes, notes. The files are written even when the log is + /// empty (a header line only), so that "nothing was recorded" is a readable answer. + /// + public static string[] WriteCsv(string vimFilePath, string outputDirectory = null) + => WriteCsv(Read(vimFilePath), vimFilePath, outputDirectory); + + /// Same, for a log already read (the .vim is only used for the file names). + public static string[] WriteCsv(Log log, string vimFilePath, string outputDirectory = null) + { + var dir = string.IsNullOrEmpty(outputDirectory) ? Path.GetDirectoryName(Path.GetFullPath(vimFilePath)) : outputDirectory; + if (!string.IsNullOrEmpty(dir)) + Directory.CreateDirectory(dir); + var stem = Path.Combine(dir ?? "", Path.GetFileNameWithoutExtension(vimFilePath)); + + var summaryPath = stem + ".activity-summary.csv"; + var changesPath = stem + ".activity-changes.csv"; + var notesPath = stem + ".activity-notes.csv"; + File.WriteAllText(summaryPath, SummaryCsv(log), Utf8); + File.WriteAllText(changesPath, ChangesCsv(log), Utf8); + File.WriteAllText(notesPath, NotesCsv(log), Utf8); + return new[] { summaryPath, changesPath, notesPath }; + } + + public static string SummaryCsv(Log log) + { + var sb = new StringBuilder(); + Row(sb, "Day", "Person", "NameFrom", "Category", "Added", "Changed", "Deleted", "SessionsOnThatDay", "HoursInModelOnThatDay"); + foreach (var r in Summarize(log)) + Row(sb, r.Day, r.Person, SourceText(r.Source), r.Category, + r.Added.ToString(CultureInfo.InvariantCulture), + r.Changed.ToString(CultureInfo.InvariantCulture), + r.Deleted.ToString(CultureInfo.InvariantCulture), + r.SessionsOnThatDay.ToString(CultureInfo.InvariantCulture), + r.HoursInModelOnThatDay.ToString("0.##", CultureInfo.InvariantCulture)); + return sb.ToString(); + } + + public static string ChangesCsv(Log log) + { + var sb = new StringBuilder(); + Row(sb, "ChangedUtc", "Person", "NameFrom", "Confidence", "Change", "Category", "Family", "Type", "Name", + "Level", "Workset", "UniqueId", "SessionGuid", "Machine", "RevitVersion", "VimRevision"); + foreach (var c in log.Changes.OrderBy(c => c.ChangedUtc, StringComparer.Ordinal)) + Row(sb, c.ChangedUtc, c.Person, SourceText(c.Source), c.Confidence.ToString(), c.Kind.ToString(), + c.Category, c.Family, c.Type, c.Name, c.Level, c.Workset, c.UniqueId, c.SessionGuid, + c.MachineName, c.RevitVersion, c.VimRevision); + return sb.ToString(); + } + + public static string NotesCsv(Log log) + { + var sb = new StringBuilder(); + Row(sb, "OccurredUtc", "Kind", "Person", "Description", "SessionGuid", "VimRevision"); + foreach (var m in log.Markers.OrderBy(m => m.OccurredUtc, StringComparer.Ordinal)) + Row(sb, m.OccurredUtc, m.Kind.ToString(), m.Person, m.Description, m.SessionGuid, m.VimRevision); + return sb.ToString(); + } + + /// Where the name came from, in words rather than in an enum name. + public static string SourceText(ContributorSource source) + { + switch (source) + { + case ContributorSource.Session: return "VIM watched it"; + case ContributorSource.Central: return "Revit (last changed by)"; + case ContributorSource.Link: return "a linked model"; + default: return "author unknown"; + } + } + + private static readonly UTF8Encoding Utf8 = new UTF8Encoding(false); + + private static bool TryParseUtc(string s, out DateTime value) + => DateTime.TryParse(s, CultureInfo.InvariantCulture, + DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out value); + + private static void Row(StringBuilder sb, params string[] cells) + { + for (var i = 0; i < cells.Length; ++i) + { + if (i > 0) sb.Append(','); + sb.Append(Cell(cells[i])); + } + sb.Append("\r\n"); // Excel reads a CSV with CRLF on every machine. + } + + private static string Cell(string s) + { + s = s ?? ""; + return s.IndexOfAny(new[] { ',', '"', '\n', '\r' }) < 0 ? s : "\"" + s.Replace("\"", "\"\"") + "\""; + } + } +} diff --git a/src/cs/vim/Vim.Format/Merge/RowPlanWriter.cs b/src/cs/vim/Vim.Format/Merge/RowPlanWriter.cs new file mode 100644 index 0000000..cb67d3b --- /dev/null +++ b/src/cs/vim/Vim.Format/Merge/RowPlanWriter.cs @@ -0,0 +1,437 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Vim.BFast; +using Vim.Format.ObjectModel; +using Vim.G3d; +using Vim.LinqArray; +using Vim.Math3d; + +namespace Vim.Format.Merge +{ + /// + /// The rows one output table takes from one or more source files, in output order, and where every source row + /// went (-1 = not written). Relations are re-pointed through when the table is written. + /// + public sealed class TablePlan + { + public string Name { get; } + public List Source { get; } = new List(); + public List Row { get; } = new List(); + public int[][] Map { get; } + + public TablePlan(string name, IReadOnlyList sources) + { + Name = name; + Map = new int[sources.Count][]; + for (var s = 0; s < sources.Count; ++s) + { + var m = new int[sources[s].NumRows(name)]; + for (var i = 0; i < m.Length; ++i) + m[i] = -1; + Map[s] = m; + } + } + + public int Count => Source.Count; + + /// Appends a row whose content comes from (source, row); maps that source row to it. + public int Add(int source, int row) + { + var i = Source.Count; + Source.Add(source); + Row.Add(row); + Map[source][row] = i; + return i; + } + + /// Maps another source row to an existing output row (its content is not used). + public void Alias(int source, int row, int outRow) + => Map[source][row] = outRow; + + /// Removes the output rows for which keep is false; the rows after them move up, and Map follows. + public int RemoveWhere(Func remove) + { + var newIndex = new int[Source.Count]; + var src = new List(); + var row = new List(); + for (var i = 0; i < Source.Count; ++i) + { + if (remove(i)) { newIndex[i] = -1; continue; } + newIndex[i] = src.Count; + src.Add(Source[i]); + row.Add(Row[i]); + } + var removed = Source.Count - src.Count; + if (removed == 0) return 0; + Source.Clear(); Source.AddRange(src); + Row.Clear(); Row.AddRange(row); + foreach (var m in Map) + for (var i = 0; i < m.Length; ++i) + if (m[i] >= 0) m[i] = newIndex[m[i]]; + return removed; + } + + /// The output row of a source row, or -1. + public int Lookup(int source, int row) + { + if (row < 0) return -1; + var m = Map[source]; + return row < m.Length ? m[row] : -1; + } + } + + public sealed class RowPlanWriteStats + { + public int MeshesFromSource0; + public int MeshesFromOtherSources; + public int MeshesReused; + public int Instances; + public int Shapes; + public int Materials; + } + + /// + /// Writes table plans over several source files into a DocumentBuilder: every column of every table (the union of + /// the sources' columns, missing ones filled with defaults), every relation re-pointed, and the G3D geometry kept + /// aligned with its tables (Node rows = instances, Shape rows = shapes, Material rows = materials; meshes are the + /// ones the written instances use). + /// + public static class RowPlanWriter + { + public static DocumentBuilder Write( + IReadOnlyList sources, + IReadOnlyDictionary plans, + SerializableHeader header, + bool deduplicateMeshes, + RowPlanWriteStats stats = null) + { + stats = stats ?? new RowPlanWriteStats(); + var db = new DocumentBuilder(header); + + foreach (var plan in plans.Values) + { + if (VimConstants.ComputedTableNames.Contains(plan.Name)) + continue; + db.Tables.Add(plan.Name, WriteTable(sources, plans, plan)); + } + + WriteGeometry(sources, plans, db, deduplicateMeshes, stats); + + // Assets: every source's buffers, the first source winning on a repeated name. + foreach (var src in sources) + foreach (var asset in src.Document.Assets ?? Array.Empty()) + db.AddAsset(asset); + + return db; + } + + private static EntityTableBuilder WriteTable(IReadOnlyList sources, IReadOnlyDictionary plans, TablePlan plan) + { + var tb = new EntityTableBuilder(plan.Name); + var tables = sources.Select(s => s.GetTable(plan.Name)).ToArray(); + var n = plan.Count; + var src = plan.Source; + var rows = plan.Row; + + // Index columns + foreach (var name in tables.Where(t => t != null).SelectMany(t => t.IndexColumns.Select(c => c.Name)).Distinct().OrderBy(x => x, StringComparer.Ordinal)) + { + var cols = tables.Select(t => t?.IndexColumns.FirstOrDefault(c => c.Name == name)?.Array).ToArray(); + var related = ColumnExtensions.GetRelatedTableNameFromColumnName(name); + plans.TryGetValue(related, out var relatedPlan); + var values = new int[n]; + for (var i = 0; i < n; ++i) + { + var col = cols[src[i]]; + var v = col == null ? -1 : col[rows[i]]; + values[i] = v < 0 ? v : relatedPlan?.Lookup(src[i], v) ?? -1; + } + tb.AddIndexColumn(name, values); + } + + // String columns + foreach (var name in tables.Where(t => t != null).SelectMany(t => t.StringColumns.Select(c => c.Name)).Distinct().OrderBy(x => x, StringComparer.Ordinal)) + { + var cols = tables.Select(t => t?.StringColumns.FirstOrDefault(c => c.Name == name)?.Array).ToArray(); + var values = new string[n]; + for (var i = 0; i < n; ++i) + { + var col = cols[src[i]]; + values[i] = col == null ? "" : sources[src[i]].Str(col[rows[i]]); + } + tb.AddStringColumn(name, values); + } + + // Data columns + foreach (var name in tables.Where(t => t != null).SelectMany(t => t.DataColumns.Select(c => c.Name)).Distinct().OrderBy(x => x, StringComparer.Ordinal)) + { + var cols = tables.Select(t => t?.DataColumns.FirstOrDefault(c => c.Name == name)?.Data).ToArray(); + tb.AddDataColumn(name, GatherData(name.GetTypePrefix(), cols, src, rows)); + } + + return tb; + } + + private static IBuffer GatherData(string typePrefix, Array[] cols, List src, List rows) + { + switch (typePrefix) + { + case VimConstants.IntColumnNameTypePrefix: return Gather(cols, src, rows).ToBuffer(); + case VimConstants.LongColumnNameTypePrefix: return Gather(cols, src, rows).ToBuffer(); + case VimConstants.ByteColumnNameTypePrefix: return Gather(cols, src, rows).ToBuffer(); + case VimConstants.FloatColumnNameTypePrefix: return Gather(cols, src, rows).ToBuffer(); + case VimConstants.DoubleColumnNameTypePrefix: return Gather(cols, src, rows).ToBuffer(); + default: throw new Exception($"Unknown data column type prefix {typePrefix}"); + } + } + + private static T[] Gather(Array[] cols, List src, List rows) where T : unmanaged + { + var typed = cols.Select(c => c as T[]).ToArray(); + var r = new T[src.Count]; + for (var i = 0; i < r.Length; ++i) + { + var col = typed[src[i]]; + r[i] = col == null ? default : col[rows[i]]; + } + return r; + } + + // --------------------------------------------------------------------------------------------------------- + // Geometry + // --------------------------------------------------------------------------------------------------------- + + private sealed class G3dArrays + { + public Vector3[] Vertices; + public int[] Indices; + public int[] MeshVertexOffsets, MeshIndexOffsets, MeshSubmeshOffsets; + public int[] SubmeshIndexOffsets, SubmeshMaterials; + public int NumMeshes; + public Matrix4x4[] InstanceTransforms; + public int[] InstanceMeshes, InstanceParents; + public ushort[] InstanceFlags; + public Vector3[] ShapeVertices; + public int[] ShapeVertexOffsets; + public Vector4[] ShapeColors; + public float[] ShapeWidths; + public Vector4[] MaterialColors; + public float[] MaterialGlossiness, MaterialSmoothness; + + public G3dArrays(G3D g) + { + Vertices = g?.Vertices?.ToArray() ?? Array.Empty(); + Indices = g?.Indices?.ToArray() ?? Array.Empty(); + MeshSubmeshOffsets = g?.MeshSubmeshOffset?.ToArray() ?? Array.Empty(); + NumMeshes = MeshSubmeshOffsets.Length; + MeshVertexOffsets = g?.MeshVertexOffsets?.ToArray() ?? Array.Empty(); + MeshIndexOffsets = g?.MeshIndexOffsets?.ToArray() ?? Array.Empty(); + SubmeshIndexOffsets = g?.SubmeshIndexOffsets?.ToArray() ?? Array.Empty(); + SubmeshMaterials = g?.SubmeshMaterials?.ToArray() ?? Array.Empty(); + InstanceTransforms = g?.InstanceTransforms?.ToArray() ?? Array.Empty(); + InstanceMeshes = g?.InstanceMeshes?.ToArray() ?? Array.Empty(); + InstanceParents = g?.InstanceParents?.ToArray() ?? Array.Empty(); + InstanceFlags = g?.InstanceFlags?.ToArray() ?? Array.Empty(); + ShapeVertices = g?.ShapeVertices?.ToArray() ?? Array.Empty(); + ShapeVertexOffsets = g?.ShapeVertexOffsets?.ToArray() ?? Array.Empty(); + ShapeColors = g?.ShapeColors?.ToArray() ?? Array.Empty(); + ShapeWidths = g?.ShapeWidths?.ToArray() ?? Array.Empty(); + MaterialColors = g?.MaterialColors?.ToArray() ?? Array.Empty(); + MaterialGlossiness = g?.MaterialGlossiness?.ToArray() ?? Array.Empty(); + MaterialSmoothness = g?.MaterialSmoothness?.ToArray() ?? Array.Empty(); + } + + public (int Start, int End) VertexRange(int m) + => (MeshVertexOffsets[m], m + 1 < NumMeshes ? MeshVertexOffsets[m + 1] : Vertices.Length); + + public (int Start, int End) IndexRange(int m) + => (MeshIndexOffsets[m], m + 1 < NumMeshes ? MeshIndexOffsets[m + 1] : Indices.Length); + + public (int Start, int End) SubmeshRange(int m) + => (MeshSubmeshOffsets[m], m + 1 < NumMeshes ? MeshSubmeshOffsets[m + 1] : SubmeshIndexOffsets.Length); + + public (int Start, int End) ShapeRange(int s) + => (ShapeVertexOffsets[s], s + 1 < ShapeVertexOffsets.Length ? ShapeVertexOffsets[s + 1] : ShapeVertices.Length); + } + + private static DocumentBuilder.SubdividedMesh ToSubdividedMesh(G3dArrays g, int m, TablePlan materialPlan, int source) + { + var (v0, v1) = g.VertexRange(m); + var (i0, i1) = g.IndexRange(m); + var (s0, s1) = g.SubmeshRange(m); + var vertices = new List(v1 - v0); + for (var i = v0; i < v1; ++i) vertices.Add(g.Vertices[i]); + var indices = new List(i1 - i0); + for (var i = i0; i < i1; ++i) indices.Add(g.Indices[i] - v0); + var offsets = new List(s1 - s0); + var materials = new List(s1 - s0); + for (var i = s0; i < s1; ++i) + { + offsets.Add(g.SubmeshIndexOffsets[i] - i0); + var mat = g.SubmeshMaterials[i]; + materials.Add(mat < 0 || materialPlan == null ? mat : materialPlan.Lookup(source, mat)); + } + return new DocumentBuilder.SubdividedMesh(indices, vertices, offsets, materials); + } + + private static unsafe int Bits(float f) => *(int*)&f; + + private static ulong MeshHash(DocumentBuilder.SubdividedMesh m) + { + const ulong prime = 1099511628211UL; + var h = 14695981039346656037UL; + void Add(int x) { h = (h ^ (uint)x) * prime; } + Add(m.Vertices.Count); + foreach (var v in m.Vertices) + { + Add(Bits(v.X)); + Add(Bits(v.Y)); + Add(Bits(v.Z)); + } + Add(m.Indices.Count); + foreach (var i in m.Indices) Add(i); + foreach (var i in m.SubmeshesIndexOffset) Add(i); + foreach (var i in m.SubmeshMaterials) Add(i); + return h; + } + + private static void WriteGeometry(IReadOnlyList sources, IReadOnlyDictionary plans, DocumentBuilder db, bool dedupe, RowPlanWriteStats stats) + { + var g3ds = sources.Select(s => new G3dArrays(s.Geometry)).ToArray(); + plans.TryGetValue(TableNames.Material, out var materialPlan); + plans.TryGetValue(TableNames.Node, out var nodePlan); + plans.TryGetValue(TableNames.Shape, out var shapePlan); + + // Materials: one renderable material per Material row, from the row's source. + if (materialPlan != null) + { + for (var i = 0; i < materialPlan.Count; ++i) + { + var s = materialPlan.Source[i]; + var r = materialPlan.Row[i]; + var g = g3ds[s]; + if (g.MaterialColors.Length == sources[s].NumRows(TableNames.Material) && r < g.MaterialColors.Length) + { + db.Materials.Add(new DocumentBuilder.Material + { + Color = g.MaterialColors[r], + Glossiness = r < g.MaterialGlossiness.Length ? g.MaterialGlossiness[r] : 0, + Smoothness = r < g.MaterialSmoothness.Length ? g.MaterialSmoothness[r] : 0, + }); + } + else + { + double D(string col) { var d = sources[s].Data(TableNames.Material, col); return d != null && r < d.Length ? d[r] : 0; } + db.Materials.Add(ObjectModelStore.ConvertMaterialEntityFieldsToRenderableMaterial( + D("double:Color.X"), D("double:Color.Y"), D("double:Color.Z"), + D("double:Transparency"), D("double:Glossiness"), D("double:Smoothness"))); + } + } + } + stats.Materials = db.Materials.Count; + + // Meshes used by the written instances, in source order (source 0 first, then the others). + var meshMaps = new int[sources.Count][]; + var hashToMeshes = dedupe ? new Dictionary>() : null; + for (var s = 0; s < sources.Count; ++s) + { + var g = g3ds[s]; + var used = new bool[g.NumMeshes]; + if (nodePlan != null) + { + for (var i = 0; i < nodePlan.Count; ++i) + { + if (nodePlan.Source[i] != s) continue; + var r = nodePlan.Row[i]; + var mesh = r < g.InstanceMeshes.Length ? g.InstanceMeshes[r] : -1; + if (mesh >= 0 && mesh < used.Length) used[mesh] = true; + } + } + var map = new int[g.NumMeshes]; + for (var m = 0; m < g.NumMeshes; ++m) + { + map[m] = -1; + if (!used[m]) continue; + var sm = ToSubdividedMesh(g, m, materialPlan, s); + if (dedupe) + { + var h = MeshHash(sm); + if (s > 0 && hashToMeshes.TryGetValue(h, out var candidates)) + { + var found = -1; + foreach (var c in candidates) + if (db.Meshes[c].IsEquivalentTo(sm)) { found = c; break; } + if (found >= 0) + { + map[m] = found; + stats.MeshesReused++; + continue; + } + } + if (!hashToMeshes.TryGetValue(h, out var list)) + hashToMeshes[h] = list = new List(); + list.Add(db.Meshes.Count); + } + map[m] = db.Meshes.Count; + db.Meshes.Add(sm); + if (s == 0) stats.MeshesFromSource0++; else stats.MeshesFromOtherSources++; + } + meshMaps[s] = map; + } + + // Instances: one per Node row, in Node order. + if (nodePlan != null) + { + for (var i = 0; i < nodePlan.Count; ++i) + { + var s = nodePlan.Source[i]; + var r = nodePlan.Row[i]; + var g = g3ds[s]; + if (r >= g.InstanceTransforms.Length) + { + // A Node row without an instance: keep the alignment with an empty instance. + db.AddInstance(Matrix4x4.Identity, -1, 0); + continue; + } + var mesh = g.InstanceMeshes[r]; + var parent = r < g.InstanceParents.Length ? g.InstanceParents[r] : -1; + db.Instances.Add(new DocumentBuilder.Instance + { + Transform = g.InstanceTransforms[r], + MeshIndex = mesh < 0 || mesh >= meshMaps[s].Length ? -1 : meshMaps[s][mesh], + ParentIndex = parent < 0 ? -1 : nodePlan.Lookup(s, parent), + InstanceFlags = (InstanceFlags)(r < g.InstanceFlags.Length ? g.InstanceFlags[r] : (ushort)0), + }); + } + } + stats.Instances = db.Instances.Count; + + // Shapes: one per Shape row, in Shape order. + if (shapePlan != null) + { + for (var i = 0; i < shapePlan.Count; ++i) + { + var s = shapePlan.Source[i]; + var r = shapePlan.Row[i]; + var g = g3ds[s]; + if (r >= g.ShapeVertexOffsets.Length) + { + db.Shapes.Add(new DocumentBuilder.Shape { Vertices = new List(), Color = Vector4.Zero, Width = 0 }); + continue; + } + var (a, b) = g.ShapeRange(r); + var pts = new List(b - a); + for (var k = a; k < b; ++k) pts.Add(g.ShapeVertices[k]); + db.Shapes.Add(new DocumentBuilder.Shape + { + Vertices = pts, + Color = r < g.ShapeColors.Length ? g.ShapeColors[r] : Vector4.Zero, + Width = r < g.ShapeWidths.Length ? g.ShapeWidths[r] : 0, + }); + } + } + stats.Shapes = db.Shapes.Count; + } + } +} diff --git a/src/cs/vim/Vim.Format/Merge/VimExtractService.cs b/src/cs/vim/Vim.Format/Merge/VimExtractService.cs new file mode 100644 index 0000000..8bbfe7e --- /dev/null +++ b/src/cs/vim/Vim.Format/Merge/VimExtractService.cs @@ -0,0 +1,163 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using Vim.Format.ObjectModel; +using Vim.LinqArray; + +namespace Vim.Format.Merge +{ + public class VimExtractOptions + { + /// Only Shape rows of these kinds are written for the selected elements (null = all kinds). + public ICollection ShapeKinds { get; set; } + + /// Only ElementInView rows for views with these UniqueIds are written (null = all views). + public ICollection ElementInViewUniqueIds { get; set; } + + public string Generator { get; set; } = "Vim.Format.Extract"; + } + + /// + /// Writes a VIM file holding a subset of the elements of another one, the way a partial export of those elements + /// would: the selected elements with every row they own, plus every row those rows point at (types, families, + /// levels, materials, views, documents, hosts...) with the rows those elements own. Used to build patch fixtures and + /// to test . + /// + public static class VimExtractService + { + private static readonly Regex AssociationTableName = new Regex(@"^Vim\.\w+In\w+$", RegexOptions.Compiled); + + public static DocumentBuilder Extract(VimTables src, ICollection uniqueIds, VimExtractOptions options = null) + { + options = options ?? new VimExtractOptions(); + var sources = new[] { src }; + var tables = src.Tables.Keys.Where(n => !VimConstants.ComputedTableNames.Contains(n)).OrderBy(n => n, StringComparer.Ordinal).ToArray(); + var included = tables.ToDictionary(t => t, t => new bool[src.NumRows(t)]); + var relations = tables.ToDictionary(t => t, t => src.IndexRelations(t).Select(r => (Col: src.Index(t, r.Column), r.Related)).ToArray()); + + // Rows owned by each element, per element-owned table (CSR). + var owned = new Dictionary(); + var numElements = src.NumRows(TableNames.Element); + foreach (var t in tables.Where(src.IsElementOwned)) + { + var owner = src.Index(t, VimTables.ElementColumn); + var lists = new List[numElements]; + for (var r = 0; r < owner.Length; ++r) + { + var e = owner[r]; + if (e < 0 || e >= numElements) continue; + (lists[e] = lists[e] ?? new List()).Add(r); + } + owned[t] = lists.Select(l => l?.ToArray()).ToArray(); + } + + var shapeKinds = options.ShapeKinds == null ? null : new HashSet(options.ShapeKinds); + var kinds = src.Data(TableNames.Shape, "int:Kind"); + var uids = src.Strs(TableNames.Element, "string:UniqueId") ?? new string[numElements]; + HashSet eivViews = null; + if (options.ElementInViewUniqueIds != null) + { + var wanted = new HashSet(options.ElementInViewUniqueIds); + var viewOwner = src.Index(TableNames.View, VimTables.ElementColumn) ?? Array.Empty(); + eivViews = new HashSet(Enumerable.Range(0, viewOwner.Length).Where(v => viewOwner[v] >= 0 && wanted.Contains(uids[viewOwner[v]]))); + } + var eivView = src.Index(TableNames.ElementInView, "index:" + TableNames.View + ":View"); + + bool OwnedRowWanted(string table, int r) + { + if (table == TableNames.Shape && shapeKinds != null) + return shapeKinds.Contains(kinds == null ? 0 : kinds[r]); + if (table == TableNames.ElementInView && eivViews != null) + return eivView != null && eivViews.Contains(eivView[r]); + return true; + } + + var work = new Stack<(string Table, int Row)>(); + void Include(string t, int r) + { + if (r < 0 || !included.TryGetValue(t, out var inc) || r >= inc.Length || inc[r]) return; + inc[r] = true; + work.Push((t, r)); + } + + var wantedUids = new HashSet(uniqueIds); + for (var e = 0; e < numElements; ++e) + if (wantedUids.Contains(uids[e] ?? "")) + Include(TableNames.Element, e); + + // Materials used by the meshes of the written nodes (no table row has to point at them). + var g = src.Geometry; + var instanceMeshes = g?.InstanceMeshes?.ToArray() ?? Array.Empty(); + var meshSubmeshOffsets = g?.MeshSubmeshOffset?.ToArray() ?? Array.Empty(); + var submeshMaterials = g?.SubmeshMaterials?.ToArray() ?? Array.Empty(); + + while (work.Count > 0) + { + var (t, r) = work.Pop(); + if (t == TableNames.Node && r < instanceMeshes.Length) + { + var m = instanceMeshes[r]; + if (m >= 0 && m < meshSubmeshOffsets.Length) + { + var end = m + 1 < meshSubmeshOffsets.Length ? meshSubmeshOffsets[m + 1] : submeshMaterials.Length; + for (var s = meshSubmeshOffsets[m]; s < end; ++s) + Include(TableNames.Material, submeshMaterials[s]); + } + } + foreach (var (col, related) in relations[t]) + if (col != null) + Include(related, col[r]); + if (t == TableNames.Element) + foreach (var kv in owned) + { + var rows = kv.Value[r]; + if (rows == null) continue; + foreach (var orow in rows) + if (OwnedRowWanted(kv.Key, orow)) + Include(kv.Key, orow); + } + } + + // Associations (and rows owned through their first relation) whose ends are all written. + var assoc = tables.Where(t => !src.IsElementOwned(t) && t != TableNames.Element + && (AssociationTableName.IsMatch(t) || t == TableNames.ScheduleColumn || t == TableNames.ScheduleCell || t == TableNames.CompoundStructureLayer)).ToArray(); + bool changed; + do + { + changed = false; + foreach (var t in assoc) + { + var inc = included[t]; + for (var r = 0; r < inc.Length; ++r) + { + if (inc[r]) continue; + var ok = true; + var any = false; + foreach (var (col, related) in relations[t]) + { + if (col == null || col[r] < 0) continue; + any = true; + if (!included.TryGetValue(related, out var ri) || col[r] >= ri.Length || !ri[col[r]]) { ok = false; break; } + } + if (ok && any) { inc[r] = true; changed = true; } + } + } + } while (changed); + + var plans = new Dictionary(); + foreach (var t in tables) + { + var plan = new TablePlan(t, sources); + var inc = included[t]; + for (var r = 0; r < inc.Length; ++r) + if (inc[r]) + plan.Add(0, r); + plans[t] = plan; + } + + var header = new SerializableHeader(options.Generator, src.Header?.Schema ?? SchemaVersion.Current, ""); + return RowPlanWriter.Write(sources, plans, header, false); + } + } +} diff --git a/src/cs/vim/Vim.Format/Merge/VimPatchService.cs b/src/cs/vim/Vim.Format/Merge/VimPatchService.cs new file mode 100644 index 0000000..f6ca1b1 --- /dev/null +++ b/src/cs/vim/Vim.Format/Merge/VimPatchService.cs @@ -0,0 +1,895 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using Vim.Format.ObjectModel; +using Vim.Util; + +namespace Vim.Format.Merge +{ + /// How a BimDocument row of the partial file is recognised in the base file. + public enum PatchDocumentMatch + { + /// Title + PathName + ordinal among the rows with the same Title and PathName (the default). + TitleAndPath = 0, + /// Title + ordinal: for a file whose path differs between the two exports (another local copy). + Title = 1, + /// + /// Every document that is not a link (BimDocument.IsLinked = 0) is the host, whatever its title, path or version: + /// one identity for all of them. Linked documents are matched by Title + ordinal. For "Sync to VIM": the same + /// central model is worked on through several local files (V4-GOOD.rvt, V4-CENTRAL_sam.rvt...), each with its + /// own Title and PathName, and a sync from any of them must replace the rows the others wrote, not add a second + /// copy of every element next to them (measured 2026-10-12). + /// + HostAndLinkTitles = 2, + } + + public class VimPatchOptions + { + /// + /// Revit UniqueIds of the elements that changed (added or modified). Null = every element of the partial file + /// counts as changed. Elements of the partial file not in this list are context (their types, levels, views...): + /// the base keeps its own rows for them and the partial's rows only serve to re-point relations. + /// + public ICollection ChangedUniqueIds { get; set; } + + /// Revit UniqueIds of the elements deleted since the base was written. + public ICollection DeletedUniqueIds { get; set; } = new HashSet(); + + /// + /// Titles of BimDocuments (linked models) exported whole in the partial file: every base element of such a + /// document is replaced by the partial's, and the ones the partial does not have are deleted. + /// + public ICollection ReplacedDocumentTitles { get; set; } = new HashSet(); + + /// + /// UniqueId of a partial element -> UniqueId of the base element it stands for. Used for the temporary 3D view + /// of an incremental export: its rows (ElementInView, ShapeInView) land on the base's export view. + /// + public IDictionary UniqueIdAliases { get; set; } = new Dictionary(); + + /// UniqueIds of partial elements never written (with every row they own). + public ICollection ExcludedUniqueIds { get; set; } = new HashSet(); + + /// + /// The Shape kinds the partial export produces. A changed element's base shapes of these kinds are replaced by + /// the partial's; its shapes of other kinds (by default the 2D view linework, Kind 1, and pre-5.9 shapes, + /// Kind 0) are kept from the base, and the partial's shapes of those kinds are not written. + /// + public ICollection ReplacedShapeKinds { get; set; } = new HashSet + { + (int) ShapeKind.ModelCurve, (int) ShapeKind.SymbolicCurve, (int) ShapeKind.VoidEdge, + (int) ShapeKind.LocationCurve, (int) ShapeKind.Grid, + }; + + /// + /// UniqueIds of the views whose ElementInView rows the partial export produces. Null = the views the partial's + /// own ElementInView rows point at (after aliases). A changed element keeps its base rows for other views (2D). + /// + public ICollection ReplacedViewUniqueIds { get; set; } + + public PatchDocumentMatch DocumentMatch { get; set; } = PatchDocumentMatch.TitleAndPath; + + /// A partial mesh equal to one the base already has is not written twice. + public bool DeduplicateMeshes { get; set; } = true; + + /// + /// Writes ExportRevision + ElementInExportRevision (object model 5.10.0): which export of which document wrote each + /// element's current data. A file without them gets one revision per document from its own header (the base as a + /// full export, the partial as a partial export). + /// + public bool RecordExportRevisions { get; set; } = true; + + /// + /// Keeps the activity log (object model 5.11.0: Contributor, SyncSession, ElementChange, ActivityMarker) of + /// both files. False drops every activity row from the output - "strip activity on export", for a file that + /// goes to a client, and the way to clear the log of a file. + /// + public bool KeepActivityLog { get; set; } = true; + + /// + /// Drops the activity rows older than this (UTC, ISO 8601); null or empty keeps everything. The caller gives + /// the cut-off rather than a number of days so that a patch of the same two files always gives the same file. + /// + public string ActivityOldestUtc { get; set; } + + /// Keeps at most this many element-change rows, the newest; 0 = no cap. + public int ActivityMaxChangeRows { get; set; } + + public string Generator { get; set; } = "Vim.Format.Patch"; + } + + public class VimPatchResult + { + public int BaseElements, PartialElements, OutputElements; + public int Kept, Changed, Added, Deleted, Context, Excluded; + /// UniqueIds listed as changed which the partial file does not hold: their base rows are kept. + public List ChangedButNotInPartial = new List(); + /// UniqueIds listed as deleted which the base file does not hold. + public List DeletedButNotInBase = new List(); + /// Base Element row -> output Element row (-1 = deleted). Element rows never move except for deletions. + public int[] ElementIndexMap; + public Dictionary TableRows = new Dictionary(); + public int PartialRowsWithoutElementDropped; + /// Wall layer structures and cameras nothing points at any more, removed. + public int OrphanRowsRemoved; + /// Activity rows dropped by the retention caps. + public int ActivityRowsTrimmed; + public RowPlanWriteStats Geometry = new RowPlanWriteStats(); + public long LoadMs, PlanMs, BuildMs, WriteMs; + + public override string ToString() + { + var sb = new StringBuilder(); + sb.AppendLine($"Elements: base {BaseElements}, partial {PartialElements}, output {OutputElements}"); + sb.AppendLine($" kept {Kept}, changed {Changed}, added {Added}, deleted {Deleted}, context {Context}, excluded {Excluded}"); + if (ChangedButNotInPartial.Count > 0) sb.AppendLine($" changed but not in the partial (base kept): {ChangedButNotInPartial.Count}"); + if (DeletedButNotInBase.Count > 0) sb.AppendLine($" deleted but not in the base: {DeletedButNotInBase.Count}"); + sb.AppendLine($"Geometry: meshes from base {Geometry.MeshesFromSource0}, from partial {Geometry.MeshesFromOtherSources}, reused {Geometry.MeshesReused}; instances {Geometry.Instances}, shapes {Geometry.Shapes}, materials {Geometry.Materials}"); + sb.AppendLine($"Timings (ms): load {LoadMs}, plan {PlanMs}, build {BuildMs}, write {WriteMs}"); + foreach (var kv in TableRows.OrderBy(k => k.Key, StringComparer.Ordinal)) + sb.AppendLine($" {kv.Key}: base {kv.Value.Base}, partial {kv.Value.Partial}, output {kv.Value.Output}"); + return sb.ToString(); + } + } + + /// + /// Patches a base VIM file with a partial VIM file exported from the same Revit document (only the changed + /// elements, for example through a temporary 3D view): changed elements are replaced, new ones appended, deleted + /// ones removed; every other row of the base (geometry, materials, parameters, shapes and their Kind, lights, 2D + /// linework, assets) is kept, and every relation is re-pointed. See INCREMENTAL-EXPORT.md in Vim.Revit/Core. + /// + /// Rules, per table: + /// + /// Element: identity = BimDocument (title + path) + UniqueId. A changed element keeps its row index (its content + /// comes from the partial); new elements are appended; deleted ones are removed (later rows move up). + /// Tables owned by an element (an "Element" relation) with at most one row per element (FamilyInstance, Level, + /// Room, View, Material...): the row follows its element, in place. + /// Tables with many rows per element (Parameter, Node, Shape, ElementInView, MaterialInElement, ElementInSystem, + /// LightSource...): a changed element's base rows are replaced by the partial's, within the replace scope (Shape kinds, + /// views). + /// Other tables (Category, DisplayUnit, ParameterDescriptor, Workset, Asset, associations): the base rows stay; + /// a partial row equal to a base row (by key or by content) is the same row, otherwise it is appended. An association + /// row (XInY) whose end is gone is removed. + /// Geometry: Node rows = instances, Shape rows = shapes, Material rows = materials; meshes are the ones the + /// written instances use (a base mesh no instance uses any more is dropped). + /// + /// + /// + public static class VimPatchService + { + private enum Role { None = 0, Changed, Context, New, Excluded } + + private static readonly Regex AssociationTableName = new Regex(@"^Vim\.\w+In\w+$", RegexOptions.Compiled); + + /// Non-element tables whose rows belong to the row their first relation points at. + private static readonly HashSet OwnerChainedTables = new HashSet + { + TableNames.ScheduleColumn, TableNames.ScheduleCell, TableNames.CompoundStructureLayer, + }; + + /// + /// The activity log tables (object model 5.11.0), in the order they are planned. They are history: a partial + /// export never replaces what the base already recorded, whichever elements changed. Contributor and + /// SyncSession rows are merged on the identity below (the partial's content wins: it is the fresher reading of + /// the same person or the same still-open session); ElementChange and ActivityMarker rows are appended, the + /// ones about elements that no longer exist included. + /// + private static readonly string[] ActivityTables = + { + TableNames.Contributor, TableNames.SyncSession, TableNames.ElementChange, TableNames.ActivityMarker, + }; + + /// What makes two activity rows the same row. A table absent from this map is append-only. + private static readonly Dictionary ActivityIdentityColumns = new Dictionary + { + { TableNames.Contributor, new[] { "string:Name", "int:Source" } }, + { TableNames.SyncSession, new[] { "string:SessionGuid" } }, + }; + + /// Element-owned tables which can hold several rows per element, whatever a given file holds. + private static readonly HashSet ManyRowsPerElementTables = new HashSet + { + TableNames.Node, TableNames.Shape, TableNames.Parameter, TableNames.ElementInView, TableNames.MaterialInElement, + TableNames.ElementInSystem, TableNames.ElementInWarning, TableNames.LightSource, TableNames.ShapeCollection, + }; + + /// Patches the files and writes the result. Returns the counts and timings. + public static VimPatchResult Patch(string baseVimPath, string partialVimPath, string outputVimPath, VimPatchOptions options = null) + { + var sw = Stopwatch.StartNew(); + var baseTables = VimTables.Load(baseVimPath); + var partialTables = VimTables.Load(partialVimPath); + var loadMs = sw.ElapsedMilliseconds; + + var (db, result) = Patch(baseTables, partialTables, options); + result.LoadMs = loadMs; + + sw.Restart(); + db.Write(outputVimPath); + result.WriteMs = sw.ElapsedMilliseconds; + return result; + } + + /// Patches in memory; the returned builder writes the patched file. + public static (DocumentBuilder Builder, VimPatchResult Result) Patch(VimTables baseVim, VimTables partialVim, VimPatchOptions options = null) + { + options = options ?? new VimPatchOptions(); + var result = new VimPatchResult(); + var sw = Stopwatch.StartNew(); + + var baseMajor = baseVim.Header?.Schema?.Major; + var partialMajor = partialVim.Header?.Schema?.Major; + if (baseMajor != null && partialMajor != null && baseMajor != partialMajor) + throw new HResultException((int)ErrorCode.VimMergeObjectModelMajorVersionMismatch, + $"Object model major version mismatch: base {baseVim.Header.Schema}, partial {partialVim.Header.Schema}"); + + // Provenance: every element keeps (or takes, when changed) the export revision its data comes from. + if (options.RecordExportRevisions) + { + baseVim.EnsureExportRevisions(ExportRevisionKind.FullExport); + partialVim.EnsureExportRevisions(ExportRevisionKind.PartialExport); + } + + var sources = new[] { baseVim, partialVim }; + var activityTables = new HashSet(options.KeepActivityLog ? ActivityTables : Array.Empty()); + var tableNames = baseVim.Tables.Keys.Concat(partialVim.Tables.Keys) + .Where(n => !VimConstants.ComputedTableNames.Contains(n)) + .Where(n => options.KeepActivityLog || !ActivityTables.Contains(n)) // "strip activity on export" + .Distinct() + .OrderBy(n => n, StringComparer.Ordinal) + .ToArray(); + var plans = tableNames.ToDictionary(n => n, n => new TablePlan(n, sources)); + + // --------------------------------------------------------------------------------------------- + // 1. Elements + // --------------------------------------------------------------------------------------------- + var aliases = options.UniqueIdAliases ?? new Dictionary(); + Func alias = u => u != null && aliases.TryGetValue(u, out var a) ? a : u; + var baseKeys = ElementKeys(baseVim, null, options.DocumentMatch); + var partialKeys = ElementKeys(partialVim, alias, options.DocumentMatch); + var baseUids = baseVim.Strs(TableNames.Element, "string:UniqueId") ?? new string[baseKeys.Length]; + var partialUids = partialVim.Strs(TableNames.Element, "string:UniqueId") ?? new string[partialKeys.Length]; + var baseDocTitles = baseVim.ElementDocumentTitles(); + var partialDocTitles = partialVim.ElementDocumentTitles(); + + var changedSet = options.ChangedUniqueIds == null ? null : new HashSet(options.ChangedUniqueIds); + var deletedSet = new HashSet(options.DeletedUniqueIds ?? Array.Empty()); + var excludedSet = new HashSet(options.ExcludedUniqueIds ?? Array.Empty()); + var replacedDocs = new HashSet(options.ReplacedDocumentTitles ?? Array.Empty()); + var aliasTargets = new HashSet(aliases.Values); + + var partialByKey = new Dictionary(); + for (var i = 0; i < partialKeys.Length; ++i) + partialByKey[partialKeys[i]] = i; + + var partialRole = new Role[partialKeys.Length]; + var baseChanged = new bool[baseKeys.Length]; + var elementPlan = plans[TableNames.Element]; + var seenDeleted = new HashSet(); + var seenChanged = new HashSet(); + + for (var b = 0; b < baseKeys.Length; ++b) + { + var uid = baseUids[b] ?? ""; + var inReplacedDoc = replacedDocs.Contains(baseDocTitles[b]); + var hasPartial = partialByKey.TryGetValue(baseKeys[b], out var p); + var deleted = deletedSet.Contains(uid) || (inReplacedDoc && !hasPartial); + if (deleted) + { + if (deletedSet.Contains(uid)) seenDeleted.Add(uid); + result.Deleted++; + continue; + } + if (!hasPartial) + { + if (changedSet != null && changedSet.Contains(uid)) result.ChangedButNotInPartial.Add(uid); + elementPlan.Add(0, b); + result.Kept++; + continue; + } + var isAliasTarget = aliasTargets.Contains(uid) && aliases.ContainsKey(partialUids[p] ?? "") ; + var changed = !isAliasTarget && !excludedSet.Contains(partialUids[p] ?? "") + && (changedSet == null || changedSet.Contains(uid) || inReplacedDoc); + if (changed) + { + seenChanged.Add(uid); + baseChanged[b] = true; + partialRole[p] = Role.Changed; + var o = elementPlan.Add(1, p); + elementPlan.Alias(0, b, o); + result.Changed++; + } + else + { + partialRole[p] = Role.Context; + var o = elementPlan.Add(0, b); + elementPlan.Alias(1, p, o); + result.Context++; + result.Kept++; + } + } + for (var p = 0; p < partialKeys.Length; ++p) + { + if (partialRole[p] != Role.None) continue; + var uid = partialUids[p] ?? ""; + if (excludedSet.Contains(uid) || aliases.ContainsKey(uid)) + { + // An alias without its target in the base, or an excluded element: not written. + partialRole[p] = Role.Excluded; + result.Excluded++; + continue; + } + if (deletedSet.Contains(uid)) + { + partialRole[p] = Role.Excluded; + result.Excluded++; + continue; + } + partialRole[p] = Role.New; + elementPlan.Add(1, p); + result.Added++; + } + result.DeletedButNotInBase = deletedSet.Where(u => !seenDeleted.Contains(u)).ToList(); + result.BaseElements = baseKeys.Length; + result.PartialElements = partialKeys.Length; + result.OutputElements = elementPlan.Count; + result.ElementIndexMap = (int[])elementPlan.Map[0].Clone(); + + // --------------------------------------------------------------------------------------------- + // 2. Element-owned tables (ElementInView last: its scope needs the View rows). + // --------------------------------------------------------------------------------------------- + var elementOwned = tableNames.Where(n => baseVim.IsElementOwned(n) || partialVim.IsElementOwned(n)).ToList(); + var ordered = elementOwned.Where(n => n != TableNames.ElementInView).ToList(); + if (elementOwned.Contains(TableNames.ElementInView)) ordered.Add(TableNames.ElementInView); + + var shapeKinds = new HashSet(options.ReplacedShapeKinds ?? Array.Empty()); + HashSet scopeViews = null; + + foreach (var table in ordered) + { + var plan = plans[table]; + var baseOwner = baseVim.Index(table, VimTables.ElementColumn) ?? new int[baseVim.NumRows(table)]; + var partialOwner = partialVim.Index(table, VimTables.ElementColumn) ?? new int[partialVim.NumRows(table)]; + + Func inScope = (s, r) => true; + if (table == TableNames.Shape) + { + var kb = baseVim.Data(table, "int:Kind"); + var kp = partialVim.Data(table, "int:Kind"); + inScope = (s, r) => + { + var k = s == 0 ? kb : kp; + return shapeKinds.Contains(k == null ? 0 : k[r]); + }; + } + else if (table == TableNames.ElementInView) + { + var viewPlan = plans.TryGetValue(TableNames.View, out var vp) ? vp : null; + var vb = baseVim.Index(table, "index:" + TableNames.View + ":View"); + var vpCol = partialVim.Index(table, "index:" + TableNames.View + ":View"); + scopeViews = ComputeScopeViews(options, baseVim, partialVim, plans, partialOwner, partialRole, vpCol); + inScope = (s, r) => + { + var col = s == 0 ? vb : vpCol; + var v = col == null ? -1 : col[r]; + var outView = viewPlan?.Lookup(s, v) ?? -1; + return outView >= 0 && scopeViews.Contains(outView); + }; + } + + // At most one row per element in both files -> rows follow their element in place. + var keyed = !ManyRowsPerElementTables.Contains(table) && IsOneRowPerElement(baseOwner) && IsOneRowPerElement(partialOwner); + if (keyed) + { + var partialRowByOutElement = new Dictionary(); + for (var r = 0; r < partialOwner.Length; ++r) + { + var e = partialOwner[r]; + if (e < 0) continue; + var oe = elementPlan.Lookup(1, e); + if (oe >= 0) partialRowByOutElement[oe] = r; + } + for (var r = 0; r < baseOwner.Length; ++r) + { + var e = baseOwner[r]; + if (e < 0) { plan.Add(0, r); continue; } + var oe = elementPlan.Lookup(0, e); + if (oe < 0) continue; // element deleted + if (partialRowByOutElement.TryGetValue(oe, out var pr)) + { + if (baseChanged[e]) + { + var o = plan.Add(1, pr); + plan.Alias(0, r, o); + } + else + { + var o = plan.Add(0, r); + plan.Alias(1, pr, o); + } + } + else + { + plan.Add(0, r); + } + } + for (var r = 0; r < partialOwner.Length; ++r) + { + if (plan.Map[1][r] >= 0) continue; + var e = partialOwner[r]; + if (e < 0) { plan.Add(1, r); continue; } + var role = partialRole[e]; + if (role == Role.Excluded || role == Role.None) continue; + if (elementPlan.Lookup(1, e) < 0) continue; + plan.Add(1, r); + } + } + else + { + for (var r = 0; r < baseOwner.Length; ++r) + { + var e = baseOwner[r]; + if (e < 0) { plan.Add(0, r); continue; } + if (elementPlan.Lookup(0, e) < 0) continue; // element deleted + if (baseChanged[e] && inScope(0, r)) continue; // replaced by the partial's rows + plan.Add(0, r); + } + for (var r = 0; r < partialOwner.Length; ++r) + { + var e = partialOwner[r]; + if (e < 0) { result.PartialRowsWithoutElementDropped++; continue; } + var role = partialRole[e]; + if (role == Role.New || (role == Role.Changed && inScope(1, r))) + { + if (table == TableNames.ElementInView && !inScope(1, r) && role == Role.New) + { + // A new element's row for a view that is not written (excluded, no alias). + var vcol = partialVim.Index(table, "index:" + TableNames.View + ":View"); + var v = vcol == null ? -1 : vcol[r]; + if ((plans.TryGetValue(TableNames.View, out var vp2) ? vp2.Lookup(1, v) : -1) < 0) continue; + } + plan.Add(1, r); + } + } + } + } + + // --------------------------------------------------------------------------------------------- + // 3. Other tables, referees before referrers. + // --------------------------------------------------------------------------------------------- + var others = tableNames.Where(n => n != TableNames.Element && !elementOwned.Contains(n) && !activityTables.Contains(n)).ToList(); + var (order, cyclic) = TopologicalOrder(others, sources); + foreach (var table in order) + PlanDictionaryTable(table, sources, plans, cyclic.Contains(table)); + + // --------------------------------------------------------------------------------------------- + // 3b. The activity log, before the pruning below: an ExportRevision the history still points at stays. + // --------------------------------------------------------------------------------------------- + foreach (var table in ActivityTables) + if (activityTables.Contains(table) && plans.ContainsKey(table)) + PlanActivityTable(table, sources, plans); + result.ActivityRowsTrimmed += TrimActivity(options, sources, plans); + + // Rows nothing points at any more (a replaced type's wall layers, a camera of an excluded view): removed, as + // the format's validation requires every CompoundStructure to belong to a FamilyType. + result.OrphanRowsRemoved += PruneUnreferenced(TableNames.CompoundStructure, TableNames.CompoundStructureLayer, sources, plans); + if (plans.TryGetValue(TableNames.CompoundStructureLayer, out var layerPlan) && plans.TryGetValue(TableNames.CompoundStructure, out var csPlan)) + { + var csCol = sources.Select(s => s.Index(TableNames.CompoundStructureLayer, "index:" + TableNames.CompoundStructure + ":CompoundStructure")).ToArray(); + result.OrphanRowsRemoved += layerPlan.RemoveWhere(i => + { + var c = csCol[layerPlan.Source[i]]; + var v = c == null ? -1 : c[layerPlan.Row[i]]; + return v >= 0 && csPlan.Lookup(layerPlan.Source[i], v) < 0; + }); + } + result.OrphanRowsRemoved += PruneUnreferenced(TableNames.Camera, null, sources, plans); + // A session and a person nothing in the history mentions any more go with the rows that mentioned them. + result.OrphanRowsRemoved += PruneUnreferenced(TableNames.SyncSession, null, sources, plans); + result.OrphanRowsRemoved += PruneUnreferenced(TableNames.Contributor, null, sources, plans); + result.OrphanRowsRemoved += PruneUnreferenced(TableNames.ExportRevision, null, sources, plans); + + result.PlanMs = sw.ElapsedMilliseconds; + sw.Restart(); + + // --------------------------------------------------------------------------------------------- + // 4. Write. + // --------------------------------------------------------------------------------------------- + var schema = baseVim.Header?.Schema; + if (schema == null || (partialVim.Header?.Schema != null && partialVim.Header.Schema.Minor > schema.Minor && partialVim.Header.Schema.Major == schema.Major)) + schema = partialVim.Header?.Schema ?? SchemaVersion.Current; + var header = baseVim.Header != null + ? SerializableHeader.CreateRevisionOf(baseVim.Header, options.Generator, schema, new Dictionary + { + { "patched", DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture) }, + }) + : new SerializableHeader(options.Generator, schema, ""); + + var db = RowPlanWriter.Write(sources, plans, header, options.DeduplicateMeshes, result.Geometry); + foreach (var kv in plans) + result.TableRows[kv.Key] = (baseVim.NumRows(kv.Key), partialVim.NumRows(kv.Key), kv.Value.Count); + result.BuildMs = sw.ElapsedMilliseconds; + return (db, result); + } + + internal static string[] ElementKeys(VimTables t, Func alias, PatchDocumentMatch match) + { + var keys = t.ElementKeys(alias); + if (match == PatchDocumentMatch.TitleAndPath) + return keys; + + // Title only: rebuild the document part of the key without the path. + var n = t.NumRows(TableNames.BimDocument); + var titles = t.Strs(TableNames.BimDocument, "string:Title") ?? new string[n]; + var seen = new Dictionary(); + var docKeys = new string[n]; + var hostKeys = match == PatchDocumentMatch.HostAndLinkTitles ? t.HostBimDocuments() : null; + for (var i = 0; i < n; ++i) + { + if (hostKeys != null && hostKeys[i]) + { + docKeys[i] = "\u0001host"; + continue; + } + seen.TryGetValue(titles[i] ?? "", out var ordinal); + seen[titles[i] ?? ""] = ordinal + 1; + docKeys[i] = (titles[i] ?? "") + "|" + ordinal; + } + var docs = t.Index(TableNames.Element, "index:" + TableNames.BimDocument + ":BimDocument"); + var uids = t.Strs(TableNames.Element, "string:UniqueId") ?? new string[keys.Length]; + var dup = new Dictionary(); + for (var i = 0; i < keys.Length; ++i) + { + var d = docs != null && docs[i] >= 0 && docs[i] < n ? docKeys[docs[i]] : ""; + var u = uids[i] ?? ""; + if (alias != null) u = alias(u) ?? u; + var k = d + "\u001f" + u; + if (dup.TryGetValue(k, out var c)) { dup[k] = c + 1; k = k + "#" + c; } else dup[k] = 1; + keys[i] = k; + } + return keys; + } + + private static HashSet ComputeScopeViews(VimPatchOptions options, VimTables baseVim, VimTables partialVim, + Dictionary plans, int[] partialOwner, Role[] partialRole, int[] partialViewCol) + { + var scope = new HashSet(); + if (!plans.TryGetValue(TableNames.View, out var viewPlan)) + return scope; + if (options.ReplacedViewUniqueIds != null) + { + // Out View row -> its element's UniqueId. + var wanted = new HashSet(options.ReplacedViewUniqueIds); + for (var i = 0; i < viewPlan.Count; ++i) + { + var src = viewPlan.Source[i] == 0 ? baseVim : partialVim; + var owner = src.Index(TableNames.View, VimTables.ElementColumn); + var e = owner == null ? -1 : owner[viewPlan.Row[i]]; + var uids = src.Strs(TableNames.Element, "string:UniqueId"); + if (e >= 0 && uids != null && wanted.Contains(uids[e])) scope.Add(i); + } + return scope; + } + if (partialViewCol == null) + return scope; + for (var r = 0; r < partialOwner.Length; ++r) + { + var e = partialOwner[r]; + if (e < 0 || (partialRole[e] != Role.Changed && partialRole[e] != Role.New)) continue; + var ov = viewPlan.Lookup(1, partialViewCol[r]); + if (ov >= 0) scope.Add(ov); + } + return scope; + } + + /// Removes the rows of a table which no written row of another table (except ignoredReferrer) points at. + private static int PruneUnreferenced(string table, string ignoredReferrer, VimTables[] sources, Dictionary plans) + { + if (!plans.TryGetValue(table, out var plan) || plan.Count == 0) return 0; + var used = new bool[plan.Count]; + foreach (var referrer in plans.Values) + { + if (referrer.Name == table || referrer.Name == ignoredReferrer) continue; + foreach (var (column, related) in sources.SelectMany(s => s.IndexRelations(referrer.Name)).Distinct()) + { + if (related != table) continue; + var cols = sources.Select(s => s.Index(referrer.Name, column)).ToArray(); + for (var i = 0; i < referrer.Count; ++i) + { + var c = cols[referrer.Source[i]]; + var v = c == null ? -1 : c[referrer.Row[i]]; + var o = plan.Lookup(referrer.Source[i], v); + if (o >= 0) used[o] = true; + } + } + } + return plan.RemoveWhere(i => !used[i]); + } + + /// + /// An activity log table: the base's rows all stay (history is never rewritten), the partial's are appended, + /// and a partial row which is the same Contributor or the same SyncSession as a base row takes that row's + /// place instead of being added next to it - with the partial's content, which is the fresher reading. + /// + private static void PlanActivityTable(string table, VimTables[] sources, Dictionary plans) + { + var plan = plans[table]; + var nb = sources[0].NumRows(table); + var np = sources[1].NumRows(table); + + if (!ActivityIdentityColumns.TryGetValue(table, out var identityColumns)) + { + // Append-only: one row per change, per marker. Two identical rows are two facts, not one. + for (var r = 0; r < nb; ++r) plan.Add(0, r); + for (var r = 0; r < np; ++r) plan.Add(1, r); + return; + } + + var key = ActivityKeyFunction(table, sources, identityColumns); + var firstPartialOfKey = new Dictionary(); + for (var r = np - 1; r >= 0; --r) + firstPartialOfKey[key(1, r)] = r; + + for (var b = 0; b < nb; ++b) + { + var k = key(0, b); + if (firstPartialOfKey.TryGetValue(k, out var p) && plan.Map[1][p] < 0) + { + var o = plan.Add(1, p); + plan.Alias(0, b, o); + } + else + { + plan.Add(0, b); + } + } + + for (var r = 0; r < np; ++r) + { + if (plan.Map[1][r] >= 0) continue; + var first = firstPartialOfKey[key(1, r)]; + if (first != r && plan.Map[1][first] >= 0) + plan.Alias(1, r, plan.Map[1][first]); // the partial repeats itself + else + plan.Add(1, r); + } + } + + /// + /// Applies the retention caps to the planned activity rows: what is older than the cut-off goes, and past the + /// row cap the oldest changes go. Returns how many rows were dropped. + /// + private static int TrimActivity(VimPatchOptions options, VimTables[] sources, Dictionary plans) + { + var hasCutoff = !string.IsNullOrEmpty(options.ActivityOldestUtc); + if (!hasCutoff && options.ActivityMaxChangeRows <= 0) + return 0; + + var dropped = 0; + dropped += TrimActivityTable(TableNames.ElementChange, "string:ChangedUtc", options, sources, plans); + dropped += TrimActivityTable(TableNames.ActivityMarker, "string:OccurredUtc", options, sources, plans); + return dropped; + } + + private static int TrimActivityTable(string table, string timeColumn, VimPatchOptions options, + VimTables[] sources, Dictionary plans) + { + if (!plans.TryGetValue(table, out var plan) || plan.Count == 0) + return 0; + var times = sources.Select(s => s.Strs(table, timeColumn)).ToArray(); + string TimeOf(int i) + { + var c = times[plan.Source[i]]; + return c == null || plan.Row[i] >= c.Length ? "" : c[plan.Row[i]] ?? ""; + } + + var remove = new bool[plan.Count]; + var kept = plan.Count; + if (!string.IsNullOrEmpty(options.ActivityOldestUtc)) + { + for (var i = 0; i < plan.Count; ++i) + { + var t = TimeOf(i); + // An unreadable or empty time is kept: dropping a row because we cannot read its date would be + // the one way to lose history silently. + if (t.Length > 0 && string.CompareOrdinal(t, options.ActivityOldestUtc) < 0) + { + remove[i] = true; + kept--; + } + } + } + + // The row cap applies to the changes only; the markers are few and each one says something. + if (table == TableNames.ElementChange && options.ActivityMaxChangeRows > 0 && kept > options.ActivityMaxChangeRows) + { + var oldestFirst = Enumerable.Range(0, plan.Count).Where(i => !remove[i]) + .OrderBy(TimeOf, StringComparer.Ordinal).Take(kept - options.ActivityMaxChangeRows); + foreach (var i in oldestFirst) + remove[i] = true; + } + + return plan.RemoveWhere(i => remove[i]); + } + + /// The identity of an activity row, as the text of the given string and int columns. + private static Func ActivityKeyFunction(string table, VimTables[] sources, string[] columns) + { + var values = new string[sources.Length][][]; + for (var s = 0; s < sources.Length; ++s) + { + values[s] = new string[columns.Length][]; + for (var c = 0; c < columns.Length; ++c) + { + if (columns[c].StartsWith(VimConstants.StringColumnNameTypePrefix)) + { + values[s][c] = sources[s].Strs(table, columns[c]); + } + else + { + var d = sources[s].Data(table, columns[c]); + values[s][c] = d?.Select(x => x.ToString(CultureInfo.InvariantCulture)).ToArray(); + } + } + } + return (s, r) => + { + var sb = new StringBuilder(); + foreach (var column in values[s]) + sb.Append(column == null || r >= column.Length ? "" : column[r] ?? "").Append('\u001f'); + return sb.ToString(); + }; + } + + private static bool IsOneRowPerElement(int[] owner) + { + var seen = new HashSet(); + foreach (var e in owner) + if (e >= 0 && !seen.Add(e)) + return false; + return true; + } + + private static (List Order, HashSet Cyclic) TopologicalOrder(List tables, VimTables[] sources) + { + var set = new HashSet(tables); + var deps = tables.ToDictionary(t => t, t => new HashSet( + sources.SelectMany(s => s.IndexRelations(t)).Select(r => r.Related).Where(r => r != t && set.Contains(r)))); + var order = new List(); + var done = new HashSet(); + bool progress; + do + { + progress = false; + foreach (var t in tables) + { + if (done.Contains(t)) continue; + if (deps[t].All(done.Contains)) + { + order.Add(t); + done.Add(t); + progress = true; + } + } + } while (progress); + var cyclic = new HashSet(tables.Where(t => !done.Contains(t))); + order.AddRange(tables.Where(cyclic.Contains)); + return (order, cyclic); + } + + private static bool IsAssociation(string table) + => AssociationTableName.IsMatch(table) || OwnerChainedTables.Contains(table); + + /// Base rows stay (associations lose rows whose ends are gone); partial rows dedupe by key/content. + private static void PlanDictionaryTable(string table, VimTables[] sources, Dictionary plans, bool cyclic) + { + var plan = plans[table]; + var assoc = IsAssociation(table); + var relations = sources.SelectMany(s => s.IndexRelations(table)).Distinct().OrderBy(r => r.Column, StringComparer.Ordinal).ToList(); + + bool EndsAlive(int s, int r) + { + if (!assoc) return true; + foreach (var (col, related) in relations) + { + var c = sources[s].Index(table, col); + if (c == null) continue; + var v = c[r]; + if (v < 0) continue; + if (related == table) continue; + if (!plans.TryGetValue(related, out var rp) || rp.Lookup(s, v) < 0) return false; + } + return true; + } + + var nb = sources[0].NumRows(table); + for (var r = 0; r < nb; ++r) + if (EndsAlive(0, r)) + plan.Add(0, r); + + var np = sources[1].NumRows(table); + if (np == 0) return; + + if (cyclic) + { + for (var r = 0; r < np; ++r) + if (EndsAlive(1, r)) + plan.Add(1, r); + return; + } + + var keyOf = KeyFunction(table, sources, plans, relations); + Dictionary baseIndex = null; + var partialIndex = new Dictionary(); + for (var r = 0; r < np; ++r) + { + if (!EndsAlive(1, r)) continue; + var k = keyOf(1, r); + if (baseIndex == null) + { + baseIndex = new Dictionary(); + for (var i = 0; i < plan.Count; ++i) + { + var bk = keyOf(0, plan.Row[i]); + if (!baseIndex.ContainsKey(bk)) baseIndex[bk] = i; + } + } + if (baseIndex.TryGetValue(k, out var o) || partialIndex.TryGetValue(k, out o)) + { + plan.Alias(1, r, o); + continue; + } + partialIndex[k] = plan.Add(1, r); + } + } + + private static Func KeyFunction(string table, VimTables[] sources, Dictionary plans, + List<(string Column, string Related)> relations) + { + string[] keyStringColumns = null; + switch (table) + { + case TableNames.Category: keyStringColumns = new[] { "string:Name", "string:BuiltInCategory" }; break; + case TableNames.DisplayUnit: keyStringColumns = new[] { "string:Spec", "string:Type", "string:Label" }; break; + case TableNames.Asset: keyStringColumns = new[] { "string:BufferName" }; break; + } + + var stringCols = keyStringColumns ?? sources.SelectMany(s => s.GetTable(table)?.StringColumns.Select(c => c.Name) ?? Enumerable.Empty()) + .Distinct().OrderBy(x => x, StringComparer.Ordinal).ToArray(); + var dataCols = keyStringColumns != null ? Array.Empty() : sources.SelectMany(s => s.GetTable(table)?.DataColumns.Select(c => c.Name) ?? Enumerable.Empty()) + .Distinct().OrderBy(x => x, StringComparer.Ordinal).ToArray(); + var indexCols = keyStringColumns != null ? new List<(string Column, string Related)>() : relations.Where(r => r.Related != table).ToList(); + + var strs = sources.Select(s => stringCols.Select(c => s.StringIndices(table, c)).ToArray()).ToArray(); + var datas = sources.Select(s => dataCols.Select(c => s.Data(table, c)?.Data).ToArray()).ToArray(); + var idxs = sources.Select(s => indexCols.Select(c => s.Index(table, c.Column)).ToArray()).ToArray(); + + return (s, r) => + { + var sb = new StringBuilder(); + for (var i = 0; i < stringCols.Length; ++i) + { + var c = strs[s][i]; + sb.Append(c == null ? "" : sources[s].Str(c[r])).Append('\u001f'); + } + for (var i = 0; i < dataCols.Length; ++i) + { + var c = datas[s][i]; + sb.Append(c == null ? "0" : Convert.ToString(c.GetValue(r), CultureInfo.InvariantCulture)).Append('\u001f'); + } + for (var i = 0; i < indexCols.Count; ++i) + { + var c = idxs[s][i]; + var v = c == null ? -1 : c[r]; + var o = v < 0 ? v : (plans.TryGetValue(indexCols[i].Related, out var rp) ? rp.Lookup(s, v) : -1); + sb.Append(o).Append('\u001f'); + } + return sb.ToString(); + }; + } + } +} diff --git a/src/cs/vim/Vim.Format/Merge/VimTables.cs b/src/cs/vim/Vim.Format/Merge/VimTables.cs new file mode 100644 index 0000000..6830b89 --- /dev/null +++ b/src/cs/vim/Vim.Format/Merge/VimTables.cs @@ -0,0 +1,259 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Vim.BFast; +using Vim.Format.ObjectModel; +using Vim.G3d; +using Vim.LinqArray; + +namespace Vim.Format.Merge +{ + /// + /// A VIM file held as its raw entity tables, string table, assets and G3D geometry - no DocumentModel is built, + /// so loading a large file costs only the deserialization. Used by and + /// . + /// + public sealed class VimTables + { + public const string ElementColumn = "index:" + TableNames.Element + ":Element"; + + public SerializableDocument Document { get; } + public string[] Strings { get; private set; } + public Dictionary Tables { get; } + public G3D Geometry { get; } + + private readonly Dictionary _numRows = new Dictionary(); + + public VimTables(SerializableDocument document) + { + Document = document ?? throw new ArgumentNullException(nameof(document)); + Strings = document.StringTable ?? Array.Empty(); + Tables = new Dictionary(); + foreach (var t in document.EntityTables) + Tables[t.Name] = t; + Geometry = document.Geometry; + } + + public static VimTables Load(string vimFilePath) + => new VimTables(Serializer.Deserialize(vimFilePath)); + + public SerializableHeader Header => Document.Header; + + public bool HasTable(string table) => Tables.ContainsKey(table); + + public SerializableEntityTable GetTable(string table) + => Tables.TryGetValue(table, out var t) ? t : null; + + public int NumRows(string table) + { + if (_numRows.TryGetValue(table, out var n)) + return n; + var t = GetTable(table); + n = 0; + if (t != null) + { + var first = t.IndexColumns.Cast() + .Concat(t.StringColumns) + .Concat(t.DataColumns) + .FirstOrDefault(); + n = first?.NumElements() ?? 0; + } + _numRows[table] = n; + return n; + } + + /// The values of an index column, or null when the table or the column is absent. + public int[] Index(string table, string column) + => GetTable(table)?.IndexColumns.FirstOrDefault(c => c.Name == column)?.Array; + + /// The string indices of a string column, or null. + public int[] StringIndices(string table, string column) + => GetTable(table)?.StringColumns.FirstOrDefault(c => c.Name == column)?.Array; + + /// The resolved values of a string column, or null. + public string[] Strs(string table, string column) + { + var idx = StringIndices(table, column); + if (idx == null) + return null; + var r = new string[idx.Length]; + for (var i = 0; i < idx.Length; ++i) + r[i] = Str(idx[i]); + return r; + } + + public string Str(int stringIndex) + => stringIndex >= 0 && stringIndex < Strings.Length ? Strings[stringIndex] ?? "" : ""; + + public INamedBuffer Data(string table, string column) + => GetTable(table)?.DataColumns.FirstOrDefault(c => c.Name == column); + + public T[] Data(string table, string column) where T : unmanaged + => Data(table, column)?.Data as T[]; + + /// Element-owned tables are the ones with an "index:Vim.Element:Element" column (Element itself excluded). + public bool IsElementOwned(string table) + => table != TableNames.Element && GetTable(table)?.IndexColumns.Any(c => c.Name == ElementColumn) == true; + + /// + /// One key per BimDocument row: Title | PathName | n, n being the ordinal of that row among the rows with the + /// same Title and PathName. The Guid is not used: Revit exports different Guids for the same document (the + /// 2D export even writes a second BimDocument row of the same file with another Guid). + /// + public string[] BimDocumentKeys() + { + var n = NumRows(TableNames.BimDocument); + var titles = Strs(TableNames.BimDocument, "string:Title") ?? new string[n]; + var paths = Strs(TableNames.BimDocument, "string:PathName") ?? new string[n]; + var seen = new Dictionary(); + var r = new string[n]; + for (var i = 0; i < n; ++i) + { + var k = (titles[i] ?? "") + "|" + (paths[i] ?? ""); + seen.TryGetValue(k, out var ordinal); + seen[k] = ordinal + 1; + r[i] = k + "|" + ordinal; + } + return r; + } + + /// + /// One identity per Element row: BimDocument key + UniqueId (after ). + /// A key repeated in the same file gets "#n" appended to its later occurrences. + /// + public string[] ElementKeys(Func uniqueIdAlias = null) + { + var n = NumRows(TableNames.Element); + var docKeys = BimDocumentKeys(); + var docs = Index(TableNames.Element, "index:" + TableNames.BimDocument + ":BimDocument"); + var uids = Strs(TableNames.Element, "string:UniqueId") ?? new string[n]; + var seen = new Dictionary(); + var r = new string[n]; + for (var i = 0; i < n; ++i) + { + var d = docs != null && docs[i] >= 0 && docs[i] < docKeys.Length ? docKeys[docs[i]] : ""; + var u = uids[i] ?? ""; + if (uniqueIdAlias != null) + u = uniqueIdAlias(u) ?? u; + var k = d + "\u001f" + u; + if (seen.TryGetValue(k, out var count)) + { + seen[k] = count + 1; + k = k + "#" + count; + } + else + { + seen[k] = 1; + } + r[i] = k; + } + return r; + } + + /// + /// For each BimDocument row: true when it is the host model rather than a linked one (IsLinked = 0; without that + /// column, a row with no parent document). A file can hold several host rows: one per local file of the same + /// model a sync was run from, and the exporter's own second row when a document's version moved mid-export. + /// + public bool[] HostBimDocuments() + { + var n = NumRows(TableNames.BimDocument); + var linked = Data(TableNames.BimDocument, "byte:IsLinked"); + var parents = Index(TableNames.BimDocument, "index:" + TableNames.BimDocument + ":Parent"); + var r = new bool[n]; + for (var i = 0; i < n; ++i) + r[i] = linked != null && i < linked.Length ? linked[i] == 0 : parents == null || i >= parents.Length || parents[i] < 0; + return r; + } + + /// The BimDocument title of each Element row ("" when none). + public string[] ElementDocumentTitles() + { + var n = NumRows(TableNames.Element); + var titles = Strs(TableNames.BimDocument, "string:Title") ?? Array.Empty(); + var docs = Index(TableNames.Element, "index:" + TableNames.BimDocument + ":BimDocument"); + var r = new string[n]; + for (var i = 0; i < n; ++i) + r[i] = docs != null && docs[i] >= 0 && docs[i] < titles.Length ? titles[docs[i]] ?? "" : ""; + return r; + } + + /// + /// Files written before object model 5.10.0 (or by an exporter which does not record it) have no ExportRevision: + /// adds one row per BimDocument, built from this file's header and the document's NumSaves / Guid, and binds every + /// element of a document to it. Does nothing when the file already binds elements to export revisions. + /// + public void EnsureExportRevisions(ExportRevisionKind kind) + { + if (NumRows(TableNames.ElementInExportRevision) > 0) + return; + + var nDocs = NumRows(TableNames.BimDocument); + var strings = new List(Strings); + int S(string s) { strings.Add(s ?? ""); return strings.Count - 1; } + + var persistingId = Header?.PersistingId ?? ""; + var generator = Header?.Generator ?? ""; + var created = Header == null ? "" : Header.CreationDate.ToUniversalTime().ToString("o", System.Globalization.CultureInfo.InvariantCulture); + var numSaves = Data(TableNames.BimDocument, "int:NumSaves"); + var guids = Strs(TableNames.BimDocument, "string:Guid"); + + var revision = new SerializableEntityTable { Name = TableNames.ExportRevision }; + revision.StringColumns.Add(Enumerable.Range(0, nDocs).Select(_ => S(persistingId)).ToArray().ToNamedBuffer("string:VimRevision")); + revision.StringColumns.Add(Enumerable.Range(0, nDocs).Select(_ => S(generator)).ToArray().ToNamedBuffer("string:Generator")); + revision.StringColumns.Add(Enumerable.Range(0, nDocs).Select(_ => S(created)).ToArray().ToNamedBuffer("string:CreatedUtc")); + revision.StringColumns.Add(Enumerable.Range(0, nDocs).Select(d => S(guids?[d])).ToArray().ToNamedBuffer("string:DocumentVersionGuid")); + revision.DataColumns.Add(Enumerable.Repeat((int)kind, nDocs).ToArray().ToNamedBuffer("int:Kind")); + revision.DataColumns.Add(Enumerable.Range(0, nDocs).Select(d => numSaves?[d] ?? 0).ToArray().ToNamedBuffer("int:DocumentNumSaves")); + revision.IndexColumns.Add(Enumerable.Range(0, nDocs).ToArray().ToNamedBuffer("index:" + TableNames.BimDocument + ":BimDocument")); + + var docs = Index(TableNames.Element, "index:" + TableNames.BimDocument + ":BimDocument") ?? Array.Empty(); + var elements = Enumerable.Range(0, docs.Length).Where(e => docs[e] >= 0 && docs[e] < nDocs).ToArray(); + var binding = new SerializableEntityTable { Name = TableNames.ElementInExportRevision }; + binding.IndexColumns.Add(elements.Select(e => docs[e]).ToArray().ToNamedBuffer("index:" + TableNames.ExportRevision + ":ExportRevision")); + binding.IndexColumns.Add(elements.ToArray().ToNamedBuffer(ElementColumn)); + + Strings = strings.ToArray(); + Tables[TableNames.ExportRevision] = revision; + Tables[TableNames.ElementInExportRevision] = binding; + _numRows.Remove(TableNames.ExportRevision); + _numRows.Remove(TableNames.ElementInExportRevision); + + // The exporter writes the activity log without knowing which ExportRevision row will stand for this + // export (there is none in the file it writes), so it leaves -1 = "the export that wrote this file". + // Now that the row exists, point those rows at it. + var rootDocument = RootBimDocument(nDocs); + ResolveActivityExportRevision(TableNames.ElementChange, rootDocument); + ResolveActivityExportRevision(TableNames.ActivityMarker, rootDocument); + } + + /// The BimDocument row of the host document: the one with no parent, else the first. + private int RootBimDocument(int numDocuments) + { + var parents = Index(TableNames.BimDocument, "index:" + TableNames.BimDocument + ":Parent"); + if (parents != null) + for (var i = 0; i < parents.Length; ++i) + if (parents[i] < 0) + return i; + return numDocuments > 0 ? 0 : -1; + } + + private void ResolveActivityExportRevision(string table, int revisionIndex) + { + if (revisionIndex < 0) + return; + var column = GetTable(table)?.IndexColumns.FirstOrDefault(c => c.Name == "index:" + TableNames.ExportRevision + ":ExportRevision"); + var values = column?.Array; + if (values == null) + return; + for (var i = 0; i < values.Length; ++i) + if (values[i] < 0) + values[i] = revisionIndex; + } + + /// The related table name of each index column of the table. + public IEnumerable<(string Column, string Related)> IndexRelations(string table) + => (GetTable(table)?.IndexColumns ?? new List>()) + .Select(c => (c.Name, ColumnExtensions.GetRelatedTableNameFromColumnName(c.Name))); + } +} diff --git a/src/cs/vim/Vim.Format/ObjectModel/ObjectModel.cs b/src/cs/vim/Vim.Format/ObjectModel/ObjectModel.cs index 667c59c..cd91394 100644 --- a/src/cs/vim/Vim.Format/ObjectModel/ObjectModel.cs +++ b/src/cs/vim/Vim.Format/ObjectModel/ObjectModel.cs @@ -14,6 +14,49 @@ public static class SchemaVersion // ReSharper disable MemberHidesStaticFromOuterClass public static class History { + // Schema additions (the activity log: who changed what, written at the moment the change happens) + // Vim.Contributor__string:Name + // Vim.Contributor__int:Source + // Vim.SyncSession__string:SessionGuid + // Vim.SyncSession__string:MachineName + // Vim.SyncSession__string:RevitVersion + // Vim.SyncSession__string:StartedUtc + // Vim.SyncSession__string:EndedUtc + // Vim.SyncSession__index:Vim.Contributor:Contributor + // Vim.SyncSession__index:Vim.BimDocument:BimDocument + // Vim.ElementChange__string:ChangedUtc + // Vim.ElementChange__string:UniqueId + // Vim.ElementChange__string:CategoryName + // Vim.ElementChange__string:FamilyName + // Vim.ElementChange__string:TypeName + // Vim.ElementChange__string:ElementName + // Vim.ElementChange__string:LevelName + // Vim.ElementChange__string:WorksetName + // Vim.ElementChange__int:ChangeKind + // Vim.ElementChange__int:Confidence + // Vim.ElementChange__index:Vim.ExportRevision:ExportRevision + // Vim.ElementChange__index:Vim.SyncSession:SyncSession + // Vim.ElementChange__index:Vim.Contributor:Contributor + // Vim.ActivityMarker__string:OccurredUtc + // Vim.ActivityMarker__string:Description + // Vim.ActivityMarker__int:Kind + // Vim.ActivityMarker__index:Vim.ExportRevision:ExportRevision + // Vim.ActivityMarker__index:Vim.SyncSession:SyncSession + // Vim.ActivityMarker__index:Vim.Contributor:Contributor + public const string v5_11_0 = "5.11.0"; + + // Schema additions (where each element's data comes from through incremental exports and patches) + // Vim.ExportRevision__string:VimRevision + // Vim.ExportRevision__int:Kind + // Vim.ExportRevision__string:Generator + // Vim.ExportRevision__string:CreatedUtc + // Vim.ExportRevision__int:DocumentNumSaves + // Vim.ExportRevision__string:DocumentVersionGuid + // Vim.ExportRevision__index:Vim.BimDocument:BimDocument + // Vim.ElementInExportRevision__index:Vim.ExportRevision:ExportRevision + // Vim.ElementInExportRevision__index:Vim.Element:Element + public const string v5_10_0 = "5.10.0"; + // Schema additions (something drawable for the elements that have no triangles: model lines, symbolic lines, // void outlines and location curves as Shapes of a given kind, and where each element's Location comes from) // Vim.Element__int:LocationSource @@ -246,7 +289,9 @@ public static class History // ReSharper enable MemberHidesStaticFromOuterClass // [MAINTAIN] Add more object model SerializableVersions below and update the current one. - public static SerializableVersion Current => v5_9_0; + public static SerializableVersion Current => v5_11_0; + public static SerializableVersion v5_11_0 => SerializableVersion.Parse(History.v5_11_0); + public static SerializableVersion v5_10_0 => SerializableVersion.Parse(History.v5_10_0); public static SerializableVersion v5_9_0 => SerializableVersion.Parse(History.v5_9_0); public static SerializableVersion v5_8_0 => SerializableVersion.Parse(History.v5_8_0); public static SerializableVersion v5_7_0 => SerializableVersion.Parse(History.v5_7_0); @@ -2620,6 +2665,246 @@ public partial class SunAndShadowSettings : Entity public Relation _View; } + /// + /// What wrote an ExportRevision (ExportRevision.Kind). + /// + public enum ExportRevisionKind + { + /// Not recorded. + Unknown = 0, + /// A full export of the document. + FullExport = 1, + /// A partial (incremental) export: only the changed elements of the document. + PartialExport = 2, + } + + /// + /// One export of one source document (BimDocument) which wrote element data now in this file. A file written by one + /// export has one row per document; a file patched or merged since keeps the rows of every export whose data is still + /// in it, and ElementInExportRevision says which export each element's data comes from. + /// + [TableName(TableNames.ExportRevision)] + public partial class ExportRevision : Entity + { + /// The persisting id (header "id::revision") of the VIM file the export wrote. + public string VimRevision; + + /// The integer representation of the ExportRevisionKind enum. + public int Kind; + + /// The generator of that VIM file (header "generator"), for example "Vim.Revit.Exporter". + public string Generator; + + /// When that VIM file was written (UTC, ISO 8601). + public string CreatedUtc; + + /// The document's number of saves at export time (Revit: DocumentVersion.NumberOfSaves). + public int DocumentNumSaves; + + /// The document's version id at export time (Revit: DocumentVersion.VersionGUID, new after any change). + public string DocumentVersionGuid; + + /// The source document. + public Relation _BimDocument; + + public ExportRevisionKind GetKind() + => (ExportRevisionKind) Kind; + } + + /// + /// Binds an Element to the ExportRevision which wrote its current data (at most one per Element). + /// + [TableName(TableNames.ElementInExportRevision)] + [CascadeElementRemap] + public partial class ElementInExportRevision : EntityWithElement, IStorageKey + { + public Relation _ExportRevision; + + public object GetStorageKey() + => _Element.CombineAsStorageKey(_ExportRevision); + } + + // --------------------------------------------------------------------------------------------------------------- + // The activity log ("who changed what"): Contributor, SyncSession, ElementChange, ActivityMarker. + // + // These rows are VIM's own record, written at the moment a change happens and keyed on the element's Revit + // UniqueId, because Revit's own record cannot be relied on: it says nothing at all about a deleted element, and + // re-saving a model as a new central resets "Created by" / "Last changed by" for every element to one person. + // See C:\VimFlexLocal\work\exporter\LIVE-SYNC-UX.md and Vim.Revit/Core/INCREMENTAL-EXPORT.md. + // --------------------------------------------------------------------------------------------------------------- + + /// Where a Contributor's name was read (Contributor.Source). + public enum ContributorSource + { + /// Nobody can say: Revit records no author for a deletion made in somebody else's local. + Unknown = 0, + /// A Revit session VIM watched: Application.Username at the moment of the change. + Session = 1, + /// Revit's worksharing "last changed by", read once when the change arrived from the central model. + Central = 2, + /// A linked model: VIM saw the link reload, not the person who changed it. + Link = 3, + } + + /// + /// One person the activity log names, per source. The same person can hold two rows - one Session, one Central - + /// on purpose: a name VIM watched is a different quality of fact from a name Revit handed over. + /// + [TableName(TableNames.Contributor)] + public partial class Contributor : Entity + { + /// Revit's Application.Username (self-declared off ACC), or the worksharing LastChangedBy. + public string Name; + + /// The integer representation of the ContributorSource enum. + public int Source; + + public ContributorSource GetSource() + => (ContributorSource) Source; + } + + /// + /// One Revit session VIM watched, in one document. A session usually spans several syncs: its row is written again + /// at each of them, with a later EndedUtc, and a patch merges the two on SessionGuid. + /// + [TableName(TableNames.SyncSession)] + public partial class SyncSession : Entity + { + /// Identifies the session across the syncs it wrote (a GUID made when VIM first saw the document change). + public string SessionGuid; + + public string MachineName; + + /// The Revit version the session ran in, for example "2027". + public string RevitVersion; + + /// When VIM first saw this session touch the document (UTC, ISO 8601). + public string StartedUtc; + + /// When the session last wrote into this file (UTC, ISO 8601); it may still be open. + public string EndedUtc; + + public Relation _Contributor; + + public Relation _BimDocument; + } + + /// What happened to the element (ElementChange.ChangeKind). + public enum ElementChangeKind + { + Unknown = 0, + Added = 1, + Modified = 2, + Deleted = 3, + } + + /// How much the name on an ElementChange row is worth (ElementChange.Confidence). + public enum ElementChangeConfidence + { + /// It vanished between two syncs and nobody can say who did it. + Unknown = 0, + /// VIM watched this person do it. Survives anything, including a re-save as a new central. + Observed = 1, + /// + /// It arrived at a sync and the name is Revit's worksharing "last changed by", read once at that moment: + /// second-hand, possibly stale, and re-writable in bulk and retroactively by a re-save as a new central. + /// + FromCentral = 2, + } + + /// + /// One element changed once, as one sync recorded it. The row carries the element's names itself instead of + /// pointing at an Element row: a deleted element has no Element row, and this table has to outlive its elements. + /// Join on UniqueId to reach the element while it still exists. + /// + [TableName(TableNames.ElementChange)] + public partial class ElementChange : Entity + { + /// + /// The export which wrote this row into this file - not the export the change happened in: when it happened + /// is . A full export rewrites the file, so it rewrites its history under one revision. + /// + public Relation _ExportRevision; + + /// The session which made the change; none when the change arrived from the central model. + public Relation _SyncSession; + + /// Who the log names for this change (see Confidence for what that name is worth). + public Relation _Contributor; + + /// The integer representation of the ElementChangeKind enum. + public int ChangeKind; + + /// The integer representation of the ElementChangeConfidence enum. + public int Confidence; + + /// When the change happened for an observed row, when it arrived for a row from central (UTC, ISO 8601). + public string ChangedUtc; + + /// The Revit UniqueId of the element. The only key that survives the element's deletion. + public string UniqueId; + + // The names Revit showed for the element at the moment of the change, so a deletion still reads + // "Doors / M_Single-Flush / 0915x2134 on Level 4". Empty when VIM never met the element. + public string CategoryName; + public string FamilyName; + public string TypeName; + public string ElementName; + public string LevelName; + public string WorksetName; + + public ElementChangeKind GetChangeKind() + => (ElementChangeKind) ChangeKind; + + public ElementChangeConfidence GetConfidence() + => (ElementChangeConfidence) Confidence; + } + + /// What an ActivityMarker records (ActivityMarker.Kind). + public enum ActivityMarkerKind + { + Unknown = 0, + /// + /// This model was re-saved as a new central: Revit's own "created by" / "last changed by" was reset to one + /// person at this point, so every FromCentral row written after it is unreliable. VIM's own rows are not. + /// + HistoryReset = 1, + /// A linked model reloaded: its elements were re-exported whole, and VIM cannot name who changed them. + LinkReloaded = 2, + /// Activity recording was off for a while, so there is a hole in the log. + LoggingGap = 3, + /// Older activity was dropped to keep the file within the retention cap. + ActivityTrimmed = 4, + /// Somebody holds a whole user workset (Sam 2026-10-12: taking a whole workset is a practice to detect). + WorksetHeld = 5, + } + + /// + /// Something that happened to the log itself rather than to an element: shown as a banner above the activity view + /// so nobody reads a wiped history, or a hole in the log, as somebody's work. + /// + [TableName(TableNames.ActivityMarker)] + public partial class ActivityMarker : Entity + { + public Relation _ExportRevision; + + public Relation _SyncSession; + + /// Who did it, when VIM can say (the person who re-saved the central, for example). + public Relation _Contributor; + + /// The integer representation of the ActivityMarkerKind enum. + public int Kind; + + public string OccurredUtc; + + /// One plain sentence, ready to show. + public string Description; + + public ActivityMarkerKind GetKind() + => (ActivityMarkerKind) Kind; + } + /// /// Helper functions for performing reflection over the object model /// diff --git a/src/cs/vim/Vim.Format/ObjectModel/ObjectModelGenerated.cs b/src/cs/vim/Vim.Format/ObjectModel/ObjectModelGenerated.cs index 5b0a191..822d48e 100644 --- a/src/cs/vim/Vim.Format/ObjectModel/ObjectModelGenerated.cs +++ b/src/cs/vim/Vim.Format/ObjectModel/ObjectModelGenerated.cs @@ -2139,6 +2139,226 @@ public override bool FieldsAreEqual(object obj) } // end of class + // AUTO-GENERATED + public partial class ExportRevision + { + public Vim.Format.ObjectModel.BimDocument BimDocument => _BimDocument?.Value; + public int BimDocumentIndex => _BimDocument?.Index ?? EntityRelation.None; + public ExportRevision() + { + _BimDocument = new Relation(); + } + + public override bool FieldsAreEqual(object obj) + { + if ((obj is ExportRevision other)) + { + var fieldsAreEqual = + (Index == other.Index) && + (VimRevision == other.VimRevision) && + (Kind == other.Kind) && + (Generator == other.Generator) && + (CreatedUtc == other.CreatedUtc) && + (DocumentNumSaves == other.DocumentNumSaves) && + (DocumentVersionGuid == other.DocumentVersionGuid) && + (_BimDocument?.Index == other._BimDocument?.Index); + if (!fieldsAreEqual) + { + return false; + } + + return true; + } + return false; + } + + } // end of class + + // AUTO-GENERATED + public partial class ElementInExportRevision + { + public Vim.Format.ObjectModel.ExportRevision ExportRevision => _ExportRevision?.Value; + public int ExportRevisionIndex => _ExportRevision?.Index ?? EntityRelation.None; + public Vim.Format.ObjectModel.Element Element => _Element?.Value; + public int ElementIndex => _Element?.Index ?? EntityRelation.None; + public ElementInExportRevision() + { + _ExportRevision = new Relation(); + _Element = new Relation(); + } + + public override bool FieldsAreEqual(object obj) + { + if ((obj is ElementInExportRevision other)) + { + var fieldsAreEqual = + (Index == other.Index) && + (_ExportRevision?.Index == other._ExportRevision?.Index) && + (_Element?.Index == other._Element?.Index); + if (!fieldsAreEqual) + { + return false; + } + + return true; + } + return false; + } + + } // end of class + + // AUTO-GENERATED + public partial class Contributor + { + public Contributor() + { + } + + public override bool FieldsAreEqual(object obj) + { + if ((obj is Contributor other)) + { + var fieldsAreEqual = + (Index == other.Index) && + (Name == other.Name) && + (Source == other.Source); + if (!fieldsAreEqual) + { + return false; + } + + return true; + } + return false; + } + + } // end of class + + // AUTO-GENERATED + public partial class SyncSession + { + public Vim.Format.ObjectModel.Contributor Contributor => _Contributor?.Value; + public int ContributorIndex => _Contributor?.Index ?? EntityRelation.None; + public Vim.Format.ObjectModel.BimDocument BimDocument => _BimDocument?.Value; + public int BimDocumentIndex => _BimDocument?.Index ?? EntityRelation.None; + public SyncSession() + { + _Contributor = new Relation(); + _BimDocument = new Relation(); + } + + public override bool FieldsAreEqual(object obj) + { + if ((obj is SyncSession other)) + { + var fieldsAreEqual = + (Index == other.Index) && + (SessionGuid == other.SessionGuid) && + (MachineName == other.MachineName) && + (RevitVersion == other.RevitVersion) && + (StartedUtc == other.StartedUtc) && + (EndedUtc == other.EndedUtc) && + (_Contributor?.Index == other._Contributor?.Index) && + (_BimDocument?.Index == other._BimDocument?.Index); + if (!fieldsAreEqual) + { + return false; + } + + return true; + } + return false; + } + + } // end of class + + // AUTO-GENERATED + public partial class ElementChange + { + public Vim.Format.ObjectModel.ExportRevision ExportRevision => _ExportRevision?.Value; + public int ExportRevisionIndex => _ExportRevision?.Index ?? EntityRelation.None; + public Vim.Format.ObjectModel.SyncSession SyncSession => _SyncSession?.Value; + public int SyncSessionIndex => _SyncSession?.Index ?? EntityRelation.None; + public Vim.Format.ObjectModel.Contributor Contributor => _Contributor?.Value; + public int ContributorIndex => _Contributor?.Index ?? EntityRelation.None; + public ElementChange() + { + _ExportRevision = new Relation(); + _SyncSession = new Relation(); + _Contributor = new Relation(); + } + + public override bool FieldsAreEqual(object obj) + { + if ((obj is ElementChange other)) + { + var fieldsAreEqual = + (Index == other.Index) && + (ChangeKind == other.ChangeKind) && + (Confidence == other.Confidence) && + (ChangedUtc == other.ChangedUtc) && + (UniqueId == other.UniqueId) && + (CategoryName == other.CategoryName) && + (FamilyName == other.FamilyName) && + (TypeName == other.TypeName) && + (ElementName == other.ElementName) && + (LevelName == other.LevelName) && + (WorksetName == other.WorksetName) && + (_ExportRevision?.Index == other._ExportRevision?.Index) && + (_SyncSession?.Index == other._SyncSession?.Index) && + (_Contributor?.Index == other._Contributor?.Index); + if (!fieldsAreEqual) + { + return false; + } + + return true; + } + return false; + } + + } // end of class + + // AUTO-GENERATED + public partial class ActivityMarker + { + public Vim.Format.ObjectModel.ExportRevision ExportRevision => _ExportRevision?.Value; + public int ExportRevisionIndex => _ExportRevision?.Index ?? EntityRelation.None; + public Vim.Format.ObjectModel.SyncSession SyncSession => _SyncSession?.Value; + public int SyncSessionIndex => _SyncSession?.Index ?? EntityRelation.None; + public Vim.Format.ObjectModel.Contributor Contributor => _Contributor?.Value; + public int ContributorIndex => _Contributor?.Index ?? EntityRelation.None; + public ActivityMarker() + { + _ExportRevision = new Relation(); + _SyncSession = new Relation(); + _Contributor = new Relation(); + } + + public override bool FieldsAreEqual(object obj) + { + if ((obj is ActivityMarker other)) + { + var fieldsAreEqual = + (Index == other.Index) && + (Kind == other.Kind) && + (OccurredUtc == other.OccurredUtc) && + (Description == other.Description) && + (_ExportRevision?.Index == other._ExportRevision?.Index) && + (_SyncSession?.Index == other._SyncSession?.Index) && + (_Contributor?.Index == other._Contributor?.Index); + if (!fieldsAreEqual) + { + return false; + } + + return true; + } + return false; + } + + } // end of class + public partial class DocumentModel { public ElementIndexMaps ElementIndexMaps { get; } @@ -4224,6 +4444,213 @@ public SunAndShadowSettings GetSunAndShadowSettings(int n) return r; } + + // ExportRevision + + public EntityTable ExportRevisionEntityTable { get; } + + public IArray ExportRevisionVimRevision { get; } + public String GetExportRevisionVimRevision(int index, String defaultValue = "") => ExportRevisionVimRevision?.ElementAtOrDefault(index, defaultValue) ?? defaultValue; + public IArray ExportRevisionKind { get; } + public Int32 GetExportRevisionKind(int index, Int32 defaultValue = default) => ExportRevisionKind?.ElementAtOrDefault(index, defaultValue) ?? defaultValue; + public IArray ExportRevisionGenerator { get; } + public String GetExportRevisionGenerator(int index, String defaultValue = "") => ExportRevisionGenerator?.ElementAtOrDefault(index, defaultValue) ?? defaultValue; + public IArray ExportRevisionCreatedUtc { get; } + public String GetExportRevisionCreatedUtc(int index, String defaultValue = "") => ExportRevisionCreatedUtc?.ElementAtOrDefault(index, defaultValue) ?? defaultValue; + public IArray ExportRevisionDocumentNumSaves { get; } + public Int32 GetExportRevisionDocumentNumSaves(int index, Int32 defaultValue = default) => ExportRevisionDocumentNumSaves?.ElementAtOrDefault(index, defaultValue) ?? defaultValue; + public IArray ExportRevisionDocumentVersionGuid { get; } + public String GetExportRevisionDocumentVersionGuid(int index, String defaultValue = "") => ExportRevisionDocumentVersionGuid?.ElementAtOrDefault(index, defaultValue) ?? defaultValue; + public IArray ExportRevisionBimDocumentIndex { get; } + public int GetExportRevisionBimDocumentIndex(int index) => ExportRevisionBimDocumentIndex?.ElementAtOrDefault(index, EntityRelation.None) ?? EntityRelation.None; + public int NumExportRevision => ExportRevisionEntityTable?.NumRows ?? 0; + public IArray ExportRevisionList { get; } + public ExportRevision GetExportRevision(int n) + { + if (n < 0) return null; + var r = new ExportRevision(); + r.Document = Document; + r.Index = n; + r.VimRevision = ExportRevisionVimRevision.ElementAtOrDefault(n); + r.Kind = ExportRevisionKind.ElementAtOrDefault(n); + r.Generator = ExportRevisionGenerator.ElementAtOrDefault(n); + r.CreatedUtc = ExportRevisionCreatedUtc.ElementAtOrDefault(n); + r.DocumentNumSaves = ExportRevisionDocumentNumSaves.ElementAtOrDefault(n); + r.DocumentVersionGuid = ExportRevisionDocumentVersionGuid.ElementAtOrDefault(n); + r._BimDocument = new Relation(GetExportRevisionBimDocumentIndex(n), GetBimDocument); + return r; + } + + + // ElementInExportRevision + + public EntityTable ElementInExportRevisionEntityTable { get; } + + public IArray ElementInExportRevisionExportRevisionIndex { get; } + public int GetElementInExportRevisionExportRevisionIndex(int index) => ElementInExportRevisionExportRevisionIndex?.ElementAtOrDefault(index, EntityRelation.None) ?? EntityRelation.None; + public IArray ElementInExportRevisionElementIndex { get; } + public int GetElementInExportRevisionElementIndex(int index) => ElementInExportRevisionElementIndex?.ElementAtOrDefault(index, EntityRelation.None) ?? EntityRelation.None; + public int NumElementInExportRevision => ElementInExportRevisionEntityTable?.NumRows ?? 0; + public IArray ElementInExportRevisionList { get; } + public ElementInExportRevision GetElementInExportRevision(int n) + { + if (n < 0) return null; + var r = new ElementInExportRevision(); + r.Document = Document; + r.Index = n; + r._ExportRevision = new Relation(GetElementInExportRevisionExportRevisionIndex(n), GetExportRevision); + r._Element = new Relation(GetElementInExportRevisionElementIndex(n), GetElement); + return r; + } + + + // Contributor + + public EntityTable ContributorEntityTable { get; } + + public IArray ContributorName { get; } + public String GetContributorName(int index, String defaultValue = "") => ContributorName?.ElementAtOrDefault(index, defaultValue) ?? defaultValue; + public IArray ContributorSource { get; } + public Int32 GetContributorSource(int index, Int32 defaultValue = default) => ContributorSource?.ElementAtOrDefault(index, defaultValue) ?? defaultValue; + public int NumContributor => ContributorEntityTable?.NumRows ?? 0; + public IArray ContributorList { get; } + public Contributor GetContributor(int n) + { + if (n < 0) return null; + var r = new Contributor(); + r.Document = Document; + r.Index = n; + r.Name = ContributorName.ElementAtOrDefault(n); + r.Source = ContributorSource.ElementAtOrDefault(n); + return r; + } + + + // SyncSession + + public EntityTable SyncSessionEntityTable { get; } + + public IArray SyncSessionSessionGuid { get; } + public String GetSyncSessionSessionGuid(int index, String defaultValue = "") => SyncSessionSessionGuid?.ElementAtOrDefault(index, defaultValue) ?? defaultValue; + public IArray SyncSessionMachineName { get; } + public String GetSyncSessionMachineName(int index, String defaultValue = "") => SyncSessionMachineName?.ElementAtOrDefault(index, defaultValue) ?? defaultValue; + public IArray SyncSessionRevitVersion { get; } + public String GetSyncSessionRevitVersion(int index, String defaultValue = "") => SyncSessionRevitVersion?.ElementAtOrDefault(index, defaultValue) ?? defaultValue; + public IArray SyncSessionStartedUtc { get; } + public String GetSyncSessionStartedUtc(int index, String defaultValue = "") => SyncSessionStartedUtc?.ElementAtOrDefault(index, defaultValue) ?? defaultValue; + public IArray SyncSessionEndedUtc { get; } + public String GetSyncSessionEndedUtc(int index, String defaultValue = "") => SyncSessionEndedUtc?.ElementAtOrDefault(index, defaultValue) ?? defaultValue; + public IArray SyncSessionContributorIndex { get; } + public int GetSyncSessionContributorIndex(int index) => SyncSessionContributorIndex?.ElementAtOrDefault(index, EntityRelation.None) ?? EntityRelation.None; + public IArray SyncSessionBimDocumentIndex { get; } + public int GetSyncSessionBimDocumentIndex(int index) => SyncSessionBimDocumentIndex?.ElementAtOrDefault(index, EntityRelation.None) ?? EntityRelation.None; + public int NumSyncSession => SyncSessionEntityTable?.NumRows ?? 0; + public IArray SyncSessionList { get; } + public SyncSession GetSyncSession(int n) + { + if (n < 0) return null; + var r = new SyncSession(); + r.Document = Document; + r.Index = n; + r.SessionGuid = SyncSessionSessionGuid.ElementAtOrDefault(n); + r.MachineName = SyncSessionMachineName.ElementAtOrDefault(n); + r.RevitVersion = SyncSessionRevitVersion.ElementAtOrDefault(n); + r.StartedUtc = SyncSessionStartedUtc.ElementAtOrDefault(n); + r.EndedUtc = SyncSessionEndedUtc.ElementAtOrDefault(n); + r._Contributor = new Relation(GetSyncSessionContributorIndex(n), GetContributor); + r._BimDocument = new Relation(GetSyncSessionBimDocumentIndex(n), GetBimDocument); + return r; + } + + + // ElementChange + + public EntityTable ElementChangeEntityTable { get; } + + public IArray ElementChangeChangeKind { get; } + public Int32 GetElementChangeChangeKind(int index, Int32 defaultValue = default) => ElementChangeChangeKind?.ElementAtOrDefault(index, defaultValue) ?? defaultValue; + public IArray ElementChangeConfidence { get; } + public Int32 GetElementChangeConfidence(int index, Int32 defaultValue = default) => ElementChangeConfidence?.ElementAtOrDefault(index, defaultValue) ?? defaultValue; + public IArray ElementChangeChangedUtc { get; } + public String GetElementChangeChangedUtc(int index, String defaultValue = "") => ElementChangeChangedUtc?.ElementAtOrDefault(index, defaultValue) ?? defaultValue; + public IArray ElementChangeUniqueId { get; } + public String GetElementChangeUniqueId(int index, String defaultValue = "") => ElementChangeUniqueId?.ElementAtOrDefault(index, defaultValue) ?? defaultValue; + public IArray ElementChangeCategoryName { get; } + public String GetElementChangeCategoryName(int index, String defaultValue = "") => ElementChangeCategoryName?.ElementAtOrDefault(index, defaultValue) ?? defaultValue; + public IArray ElementChangeFamilyName { get; } + public String GetElementChangeFamilyName(int index, String defaultValue = "") => ElementChangeFamilyName?.ElementAtOrDefault(index, defaultValue) ?? defaultValue; + public IArray ElementChangeTypeName { get; } + public String GetElementChangeTypeName(int index, String defaultValue = "") => ElementChangeTypeName?.ElementAtOrDefault(index, defaultValue) ?? defaultValue; + public IArray ElementChangeElementName { get; } + public String GetElementChangeElementName(int index, String defaultValue = "") => ElementChangeElementName?.ElementAtOrDefault(index, defaultValue) ?? defaultValue; + public IArray ElementChangeLevelName { get; } + public String GetElementChangeLevelName(int index, String defaultValue = "") => ElementChangeLevelName?.ElementAtOrDefault(index, defaultValue) ?? defaultValue; + public IArray ElementChangeWorksetName { get; } + public String GetElementChangeWorksetName(int index, String defaultValue = "") => ElementChangeWorksetName?.ElementAtOrDefault(index, defaultValue) ?? defaultValue; + public IArray ElementChangeExportRevisionIndex { get; } + public int GetElementChangeExportRevisionIndex(int index) => ElementChangeExportRevisionIndex?.ElementAtOrDefault(index, EntityRelation.None) ?? EntityRelation.None; + public IArray ElementChangeSyncSessionIndex { get; } + public int GetElementChangeSyncSessionIndex(int index) => ElementChangeSyncSessionIndex?.ElementAtOrDefault(index, EntityRelation.None) ?? EntityRelation.None; + public IArray ElementChangeContributorIndex { get; } + public int GetElementChangeContributorIndex(int index) => ElementChangeContributorIndex?.ElementAtOrDefault(index, EntityRelation.None) ?? EntityRelation.None; + public int NumElementChange => ElementChangeEntityTable?.NumRows ?? 0; + public IArray ElementChangeList { get; } + public ElementChange GetElementChange(int n) + { + if (n < 0) return null; + var r = new ElementChange(); + r.Document = Document; + r.Index = n; + r.ChangeKind = ElementChangeChangeKind.ElementAtOrDefault(n); + r.Confidence = ElementChangeConfidence.ElementAtOrDefault(n); + r.ChangedUtc = ElementChangeChangedUtc.ElementAtOrDefault(n); + r.UniqueId = ElementChangeUniqueId.ElementAtOrDefault(n); + r.CategoryName = ElementChangeCategoryName.ElementAtOrDefault(n); + r.FamilyName = ElementChangeFamilyName.ElementAtOrDefault(n); + r.TypeName = ElementChangeTypeName.ElementAtOrDefault(n); + r.ElementName = ElementChangeElementName.ElementAtOrDefault(n); + r.LevelName = ElementChangeLevelName.ElementAtOrDefault(n); + r.WorksetName = ElementChangeWorksetName.ElementAtOrDefault(n); + r._ExportRevision = new Relation(GetElementChangeExportRevisionIndex(n), GetExportRevision); + r._SyncSession = new Relation(GetElementChangeSyncSessionIndex(n), GetSyncSession); + r._Contributor = new Relation(GetElementChangeContributorIndex(n), GetContributor); + return r; + } + + + // ActivityMarker + + public EntityTable ActivityMarkerEntityTable { get; } + + public IArray ActivityMarkerKind { get; } + public Int32 GetActivityMarkerKind(int index, Int32 defaultValue = default) => ActivityMarkerKind?.ElementAtOrDefault(index, defaultValue) ?? defaultValue; + public IArray ActivityMarkerOccurredUtc { get; } + public String GetActivityMarkerOccurredUtc(int index, String defaultValue = "") => ActivityMarkerOccurredUtc?.ElementAtOrDefault(index, defaultValue) ?? defaultValue; + public IArray ActivityMarkerDescription { get; } + public String GetActivityMarkerDescription(int index, String defaultValue = "") => ActivityMarkerDescription?.ElementAtOrDefault(index, defaultValue) ?? defaultValue; + public IArray ActivityMarkerExportRevisionIndex { get; } + public int GetActivityMarkerExportRevisionIndex(int index) => ActivityMarkerExportRevisionIndex?.ElementAtOrDefault(index, EntityRelation.None) ?? EntityRelation.None; + public IArray ActivityMarkerSyncSessionIndex { get; } + public int GetActivityMarkerSyncSessionIndex(int index) => ActivityMarkerSyncSessionIndex?.ElementAtOrDefault(index, EntityRelation.None) ?? EntityRelation.None; + public IArray ActivityMarkerContributorIndex { get; } + public int GetActivityMarkerContributorIndex(int index) => ActivityMarkerContributorIndex?.ElementAtOrDefault(index, EntityRelation.None) ?? EntityRelation.None; + public int NumActivityMarker => ActivityMarkerEntityTable?.NumRows ?? 0; + public IArray ActivityMarkerList { get; } + public ActivityMarker GetActivityMarker(int n) + { + if (n < 0) return null; + var r = new ActivityMarker(); + r.Document = Document; + r.Index = n; + r.Kind = ActivityMarkerKind.ElementAtOrDefault(n); + r.OccurredUtc = ActivityMarkerOccurredUtc.ElementAtOrDefault(n); + r.Description = ActivityMarkerDescription.ElementAtOrDefault(n); + r._ExportRevision = new Relation(GetActivityMarkerExportRevisionIndex(n), GetExportRevision); + r._SyncSession = new Relation(GetActivityMarkerSyncSessionIndex(n), GetSyncSession); + r._Contributor = new Relation(GetActivityMarkerContributorIndex(n), GetContributor); + return r; + } + // All entity collections public Dictionary> AllEntities => new Dictionary>() { {"Vim.Asset", AssetList.ToEnumerable()}, @@ -4283,6 +4710,12 @@ public SunAndShadowSettings GetSunAndShadowSettings(int n) {"Vim.LightType", LightTypeList.ToEnumerable()}, {"Vim.LightSource", LightSourceList.ToEnumerable()}, {"Vim.SunAndShadowSettings", SunAndShadowSettingsList.ToEnumerable()}, + {"Vim.ExportRevision", ExportRevisionList.ToEnumerable()}, + {"Vim.ElementInExportRevision", ElementInExportRevisionList.ToEnumerable()}, + {"Vim.Contributor", ContributorList.ToEnumerable()}, + {"Vim.SyncSession", SyncSessionList.ToEnumerable()}, + {"Vim.ElementChange", ElementChangeList.ToEnumerable()}, + {"Vim.ActivityMarker", ActivityMarkerList.ToEnumerable()}, }; // Entity types from table names @@ -4344,6 +4777,12 @@ public SunAndShadowSettings GetSunAndShadowSettings(int n) {"Vim.LightType", typeof(LightType)}, {"Vim.LightSource", typeof(LightSource)}, {"Vim.SunAndShadowSettings", typeof(SunAndShadowSettings)}, + {"Vim.ExportRevision", typeof(ExportRevision)}, + {"Vim.ElementInExportRevision", typeof(ElementInExportRevision)}, + {"Vim.Contributor", typeof(Contributor)}, + {"Vim.SyncSession", typeof(SyncSession)}, + {"Vim.ElementChange", typeof(ElementChange)}, + {"Vim.ActivityMarker", typeof(ActivityMarker)}, }; public DocumentModel(Document d, bool inParallel = true) { @@ -4407,6 +4846,12 @@ public DocumentModel(Document d, bool inParallel = true) LightTypeEntityTable = Document.GetTable("Vim.LightType"); LightSourceEntityTable = Document.GetTable("Vim.LightSource"); SunAndShadowSettingsEntityTable = Document.GetTable("Vim.SunAndShadowSettings"); + ExportRevisionEntityTable = Document.GetTable("Vim.ExportRevision"); + ElementInExportRevisionEntityTable = Document.GetTable("Vim.ElementInExportRevision"); + ContributorEntityTable = Document.GetTable("Vim.Contributor"); + SyncSessionEntityTable = Document.GetTable("Vim.SyncSession"); + ElementChangeEntityTable = Document.GetTable("Vim.ElementChange"); + ActivityMarkerEntityTable = Document.GetTable("Vim.ActivityMarker"); // Initialize entity arrays AssetBufferName = AssetEntityTable?.GetStringColumnValues("string:BufferName") ?? Array.Empty().ToIArray(); @@ -4694,6 +5139,32 @@ public DocumentModel(Document d, bool inParallel = true) SunAndShadowSettingsAltitude = SunAndShadowSettingsEntityTable?.GetDataColumnValues("double:Altitude") ?? Array.Empty().ToIArray(); SunAndShadowSettingsAzimuth = SunAndShadowSettingsEntityTable?.GetDataColumnValues("double:Azimuth") ?? Array.Empty().ToIArray(); SunAndShadowSettingsRelativeToView = SunAndShadowSettingsEntityTable?.GetDataColumnValues("byte:RelativeToView") ?? Array.Empty().ToIArray(); + ExportRevisionVimRevision = ExportRevisionEntityTable?.GetStringColumnValues("string:VimRevision") ?? Array.Empty().ToIArray(); + ExportRevisionKind = ExportRevisionEntityTable?.GetDataColumnValues("int:Kind") ?? Array.Empty().ToIArray(); + ExportRevisionGenerator = ExportRevisionEntityTable?.GetStringColumnValues("string:Generator") ?? Array.Empty().ToIArray(); + ExportRevisionCreatedUtc = ExportRevisionEntityTable?.GetStringColumnValues("string:CreatedUtc") ?? Array.Empty().ToIArray(); + ExportRevisionDocumentNumSaves = ExportRevisionEntityTable?.GetDataColumnValues("int:DocumentNumSaves") ?? Array.Empty().ToIArray(); + ExportRevisionDocumentVersionGuid = ExportRevisionEntityTable?.GetStringColumnValues("string:DocumentVersionGuid") ?? Array.Empty().ToIArray(); + ContributorName = ContributorEntityTable?.GetStringColumnValues("string:Name") ?? Array.Empty().ToIArray(); + ContributorSource = ContributorEntityTable?.GetDataColumnValues("int:Source") ?? Array.Empty().ToIArray(); + SyncSessionSessionGuid = SyncSessionEntityTable?.GetStringColumnValues("string:SessionGuid") ?? Array.Empty().ToIArray(); + SyncSessionMachineName = SyncSessionEntityTable?.GetStringColumnValues("string:MachineName") ?? Array.Empty().ToIArray(); + SyncSessionRevitVersion = SyncSessionEntityTable?.GetStringColumnValues("string:RevitVersion") ?? Array.Empty().ToIArray(); + SyncSessionStartedUtc = SyncSessionEntityTable?.GetStringColumnValues("string:StartedUtc") ?? Array.Empty().ToIArray(); + SyncSessionEndedUtc = SyncSessionEntityTable?.GetStringColumnValues("string:EndedUtc") ?? Array.Empty().ToIArray(); + ElementChangeChangeKind = ElementChangeEntityTable?.GetDataColumnValues("int:ChangeKind") ?? Array.Empty().ToIArray(); + ElementChangeConfidence = ElementChangeEntityTable?.GetDataColumnValues("int:Confidence") ?? Array.Empty().ToIArray(); + ElementChangeChangedUtc = ElementChangeEntityTable?.GetStringColumnValues("string:ChangedUtc") ?? Array.Empty().ToIArray(); + ElementChangeUniqueId = ElementChangeEntityTable?.GetStringColumnValues("string:UniqueId") ?? Array.Empty().ToIArray(); + ElementChangeCategoryName = ElementChangeEntityTable?.GetStringColumnValues("string:CategoryName") ?? Array.Empty().ToIArray(); + ElementChangeFamilyName = ElementChangeEntityTable?.GetStringColumnValues("string:FamilyName") ?? Array.Empty().ToIArray(); + ElementChangeTypeName = ElementChangeEntityTable?.GetStringColumnValues("string:TypeName") ?? Array.Empty().ToIArray(); + ElementChangeElementName = ElementChangeEntityTable?.GetStringColumnValues("string:ElementName") ?? Array.Empty().ToIArray(); + ElementChangeLevelName = ElementChangeEntityTable?.GetStringColumnValues("string:LevelName") ?? Array.Empty().ToIArray(); + ElementChangeWorksetName = ElementChangeEntityTable?.GetStringColumnValues("string:WorksetName") ?? Array.Empty().ToIArray(); + ActivityMarkerKind = ActivityMarkerEntityTable?.GetDataColumnValues("int:Kind") ?? Array.Empty().ToIArray(); + ActivityMarkerOccurredUtc = ActivityMarkerEntityTable?.GetStringColumnValues("string:OccurredUtc") ?? Array.Empty().ToIArray(); + ActivityMarkerDescription = ActivityMarkerEntityTable?.GetStringColumnValues("string:Description") ?? Array.Empty().ToIArray(); // Initialize entity relational columns ParameterDescriptorDisplayUnitIndex = ParameterDescriptorEntityTable?.GetIndexColumnValues("index:Vim.DisplayUnit:DisplayUnit") ?? Array.Empty().ToIArray(); @@ -4801,6 +5272,17 @@ public DocumentModel(Document d, bool inParallel = true) LightSourceLightTypeIndex = LightSourceEntityTable?.GetIndexColumnValues("index:Vim.LightType:LightType") ?? Array.Empty().ToIArray(); LightSourceElementIndex = LightSourceEntityTable?.GetIndexColumnValues("index:Vim.Element:Element") ?? Array.Empty().ToIArray(); SunAndShadowSettingsViewIndex = SunAndShadowSettingsEntityTable?.GetIndexColumnValues("index:Vim.View:View") ?? Array.Empty().ToIArray(); + ExportRevisionBimDocumentIndex = ExportRevisionEntityTable?.GetIndexColumnValues("index:Vim.BimDocument:BimDocument") ?? Array.Empty().ToIArray(); + ElementInExportRevisionExportRevisionIndex = ElementInExportRevisionEntityTable?.GetIndexColumnValues("index:Vim.ExportRevision:ExportRevision") ?? Array.Empty().ToIArray(); + ElementInExportRevisionElementIndex = ElementInExportRevisionEntityTable?.GetIndexColumnValues("index:Vim.Element:Element") ?? Array.Empty().ToIArray(); + SyncSessionContributorIndex = SyncSessionEntityTable?.GetIndexColumnValues("index:Vim.Contributor:Contributor") ?? Array.Empty().ToIArray(); + SyncSessionBimDocumentIndex = SyncSessionEntityTable?.GetIndexColumnValues("index:Vim.BimDocument:BimDocument") ?? Array.Empty().ToIArray(); + ElementChangeExportRevisionIndex = ElementChangeEntityTable?.GetIndexColumnValues("index:Vim.ExportRevision:ExportRevision") ?? Array.Empty().ToIArray(); + ElementChangeSyncSessionIndex = ElementChangeEntityTable?.GetIndexColumnValues("index:Vim.SyncSession:SyncSession") ?? Array.Empty().ToIArray(); + ElementChangeContributorIndex = ElementChangeEntityTable?.GetIndexColumnValues("index:Vim.Contributor:Contributor") ?? Array.Empty().ToIArray(); + ActivityMarkerExportRevisionIndex = ActivityMarkerEntityTable?.GetIndexColumnValues("index:Vim.ExportRevision:ExportRevision") ?? Array.Empty().ToIArray(); + ActivityMarkerSyncSessionIndex = ActivityMarkerEntityTable?.GetIndexColumnValues("index:Vim.SyncSession:SyncSession") ?? Array.Empty().ToIArray(); + ActivityMarkerContributorIndex = ActivityMarkerEntityTable?.GetIndexColumnValues("index:Vim.Contributor:Contributor") ?? Array.Empty().ToIArray(); // Initialize entity collections AssetList = NumAsset.Select(i => GetAsset(i)); @@ -4860,6 +5342,12 @@ public DocumentModel(Document d, bool inParallel = true) LightTypeList = NumLightType.Select(i => GetLightType(i)); LightSourceList = NumLightSource.Select(i => GetLightSource(i)); SunAndShadowSettingsList = NumSunAndShadowSettings.Select(i => GetSunAndShadowSettings(i)); + ExportRevisionList = NumExportRevision.Select(i => GetExportRevision(i)); + ElementInExportRevisionList = NumElementInExportRevision.Select(i => GetElementInExportRevision(i)); + ContributorList = NumContributor.Select(i => GetContributor(i)); + SyncSessionList = NumSyncSession.Select(i => GetSyncSession(i)); + ElementChangeList = NumElementChange.Select(i => GetElementChange(i)); + ActivityMarkerList = NumActivityMarker.Select(i => GetActivityMarker(i)); // Initialize element index maps ElementIndexMaps = new ElementIndexMaps(this, inParallel); @@ -4944,6 +5432,12 @@ public EntityTableSet(SerializableEntityTable[] rawTables, string[] stringTable, Tables[TableNames.LightType] = LightTypeTable = new LightTypeTable(GetSerializableTableOrEmpty(TableNames.LightType), stringTable, this); Tables[TableNames.LightSource] = LightSourceTable = new LightSourceTable(GetSerializableTableOrEmpty(TableNames.LightSource), stringTable, this); Tables[TableNames.SunAndShadowSettings] = SunAndShadowSettingsTable = new SunAndShadowSettingsTable(GetSerializableTableOrEmpty(TableNames.SunAndShadowSettings), stringTable, this); + Tables[TableNames.ExportRevision] = ExportRevisionTable = new ExportRevisionTable(GetSerializableTableOrEmpty(TableNames.ExportRevision), stringTable, this); + Tables[TableNames.ElementInExportRevision] = ElementInExportRevisionTable = new ElementInExportRevisionTable(GetSerializableTableOrEmpty(TableNames.ElementInExportRevision), stringTable, this); + Tables[TableNames.Contributor] = ContributorTable = new ContributorTable(GetSerializableTableOrEmpty(TableNames.Contributor), stringTable, this); + Tables[TableNames.SyncSession] = SyncSessionTable = new SyncSessionTable(GetSerializableTableOrEmpty(TableNames.SyncSession), stringTable, this); + Tables[TableNames.ElementChange] = ElementChangeTable = new ElementChangeTable(GetSerializableTableOrEmpty(TableNames.ElementChange), stringTable, this); + Tables[TableNames.ActivityMarker] = ActivityMarkerTable = new ActivityMarkerTable(GetSerializableTableOrEmpty(TableNames.ActivityMarker), stringTable, this); // Initialize element index maps ElementIndexMaps = new ElementIndexMaps(this, inParallel); @@ -5064,6 +5558,18 @@ public EntityTableSet(SerializableEntityTable[] rawTables, string[] stringTable, public LightSource GetLightSource(int index) => LightSourceTable?.Get(index); public SunAndShadowSettingsTable SunAndShadowSettingsTable { get; } // can be null public SunAndShadowSettings GetSunAndShadowSettings(int index) => SunAndShadowSettingsTable?.Get(index); + public ExportRevisionTable ExportRevisionTable { get; } // can be null + public ExportRevision GetExportRevision(int index) => ExportRevisionTable?.Get(index); + public ElementInExportRevisionTable ElementInExportRevisionTable { get; } // can be null + public ElementInExportRevision GetElementInExportRevision(int index) => ElementInExportRevisionTable?.Get(index); + public ContributorTable ContributorTable { get; } // can be null + public Contributor GetContributor(int index) => ContributorTable?.Get(index); + public SyncSessionTable SyncSessionTable { get; } // can be null + public SyncSession GetSyncSession(int index) => SyncSessionTable?.Get(index); + public ElementChangeTable ElementChangeTable { get; } // can be null + public ElementChange GetElementChange(int index) => ElementChangeTable?.Get(index); + public ActivityMarkerTable ActivityMarkerTable { get; } // can be null + public ActivityMarker GetActivityMarker(int index) => ActivityMarkerTable?.Get(index); public static HashSet GetElementKindTableNames() => new HashSet() @@ -8853,43 +9359,387 @@ public IEnumerator GetEnumerator() } } // class SunAndShadowSettingsTable - public static class DocumentBuilderExtensions + public partial class ExportRevisionTable : EntityTable_v2, IEnumerable { - public static EntityTableBuilder ToAssetTableBuilder(this EntitySetBuilder entitySet) + + public const string TableName = TableNames.ExportRevision; + + public EntityTableSet ParentTableSet { get; } // can be null + + public ExportRevisionTable(SerializableEntityTable rawTable, string[] stringTable, EntityTableSet parentTableSet = null) : base(rawTable, stringTable) { - var tb = new EntityTableBuilder(TableNames.Asset); - var entities = entitySet.Entities; - var entityCount = entities.Count; - { - var columnData = new String[entityCount]; - for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i].BufferName; } - tb.AddStringColumn("string:BufferName", columnData); - } - return tb; + ParentTableSet = parentTableSet; + Column_VimRevision = GetStringColumnValues("string:VimRevision") ?? Array.Empty(); + Column_Kind = GetDataColumnValues("int:Kind") ?? Array.Empty(); + Column_Generator = GetStringColumnValues("string:Generator") ?? Array.Empty(); + Column_CreatedUtc = GetStringColumnValues("string:CreatedUtc") ?? Array.Empty(); + Column_DocumentNumSaves = GetDataColumnValues("int:DocumentNumSaves") ?? Array.Empty(); + Column_DocumentVersionGuid = GetStringColumnValues("string:DocumentVersionGuid") ?? Array.Empty(); + Column_BimDocumentIndex = GetIndexColumnValues("index:Vim.BimDocument:BimDocument") ?? Array.Empty(); } - public static EntityTableBuilder ToDisplayUnitTableBuilder(this EntitySetBuilder entitySet) + + public String[] Column_VimRevision { get; } + public String GetVimRevision(int index, String @default = "") => Column_VimRevision.ElementAtOrDefault(index, @default); + public Int32[] Column_Kind { get; } + public Int32 GetKind(int index, Int32 @default = default) => Column_Kind.ElementAtOrDefault(index, @default); + public String[] Column_Generator { get; } + public String GetGenerator(int index, String @default = "") => Column_Generator.ElementAtOrDefault(index, @default); + public String[] Column_CreatedUtc { get; } + public String GetCreatedUtc(int index, String @default = "") => Column_CreatedUtc.ElementAtOrDefault(index, @default); + public Int32[] Column_DocumentNumSaves { get; } + public Int32 GetDocumentNumSaves(int index, Int32 @default = default) => Column_DocumentNumSaves.ElementAtOrDefault(index, @default); + public String[] Column_DocumentVersionGuid { get; } + public String GetDocumentVersionGuid(int index, String @default = "") => Column_DocumentVersionGuid.ElementAtOrDefault(index, @default); + public int[] Column_BimDocumentIndex { get; } + public int GetBimDocumentIndex(int index) => Column_BimDocumentIndex.ElementAtOrDefault(index, EntityRelation.None); + public BimDocument GetBimDocument(int index) => _GetReferencedBimDocument(GetBimDocumentIndex(index)); + private BimDocument _GetReferencedBimDocument(int referencedIndex) => ParentTableSet.GetBimDocument(referencedIndex); + // Object Getter + public ExportRevision Get(int index) { - var tb = new EntityTableBuilder(TableNames.DisplayUnit); - var entities = entitySet.Entities; - var entityCount = entities.Count; - { - var columnData = new String[entityCount]; - for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i].Spec; } - tb.AddStringColumn("string:Spec", columnData); - } - { - var columnData = new String[entityCount]; - for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i].Type; } - tb.AddStringColumn("string:Type", columnData); - } - { - var columnData = new String[entityCount]; - for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i].Label; } - tb.AddStringColumn("string:Label", columnData); - } - return tb; - } - public static EntityTableBuilder ToParameterDescriptorTableBuilder(this EntitySetBuilder entitySet) + if (index < 0) return null; + var r = new ExportRevision(); + r.Index = index; + r.VimRevision = GetVimRevision(index); + r.Kind = GetKind(index); + r.Generator = GetGenerator(index); + r.CreatedUtc = GetCreatedUtc(index); + r.DocumentNumSaves = GetDocumentNumSaves(index); + r.DocumentVersionGuid = GetDocumentVersionGuid(index); + r._BimDocument = new Relation(GetBimDocumentIndex(index), _GetReferencedBimDocument); + return r; + } + // Enumerator + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public IEnumerator GetEnumerator() + { + for (var i = 0; i < RowCount; ++i) + yield return Get(i); + } + } // class ExportRevisionTable + + public partial class ElementInExportRevisionTable : EntityTable_v2, IEnumerable + { + + public const string TableName = TableNames.ElementInExportRevision; + + public EntityTableSet ParentTableSet { get; } // can be null + + public ElementInExportRevisionTable(SerializableEntityTable rawTable, string[] stringTable, EntityTableSet parentTableSet = null) : base(rawTable, stringTable) + { + ParentTableSet = parentTableSet; + Column_ExportRevisionIndex = GetIndexColumnValues("index:Vim.ExportRevision:ExportRevision") ?? Array.Empty(); + Column_ElementIndex = GetIndexColumnValues("index:Vim.Element:Element") ?? Array.Empty(); + } + + public int[] Column_ExportRevisionIndex { get; } + public int GetExportRevisionIndex(int index) => Column_ExportRevisionIndex.ElementAtOrDefault(index, EntityRelation.None); + public ExportRevision GetExportRevision(int index) => _GetReferencedExportRevision(GetExportRevisionIndex(index)); + private ExportRevision _GetReferencedExportRevision(int referencedIndex) => ParentTableSet.GetExportRevision(referencedIndex); + public int[] Column_ElementIndex { get; } + public int GetElementIndex(int index) => Column_ElementIndex.ElementAtOrDefault(index, EntityRelation.None); + public Element GetElement(int index) => _GetReferencedElement(GetElementIndex(index)); + private Element _GetReferencedElement(int referencedIndex) => ParentTableSet.GetElement(referencedIndex); + // Object Getter + public ElementInExportRevision Get(int index) + { + if (index < 0) return null; + var r = new ElementInExportRevision(); + r.Index = index; + r._ExportRevision = new Relation(GetExportRevisionIndex(index), _GetReferencedExportRevision); + r._Element = new Relation(GetElementIndex(index), _GetReferencedElement); + return r; + } + // Enumerator + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public IEnumerator GetEnumerator() + { + for (var i = 0; i < RowCount; ++i) + yield return Get(i); + } + } // class ElementInExportRevisionTable + + public partial class ContributorTable : EntityTable_v2, IEnumerable + { + + public const string TableName = TableNames.Contributor; + + public EntityTableSet ParentTableSet { get; } // can be null + + public ContributorTable(SerializableEntityTable rawTable, string[] stringTable, EntityTableSet parentTableSet = null) : base(rawTable, stringTable) + { + ParentTableSet = parentTableSet; + Column_Name = GetStringColumnValues("string:Name") ?? Array.Empty(); + Column_Source = GetDataColumnValues("int:Source") ?? Array.Empty(); + } + + public String[] Column_Name { get; } + public String GetName(int index, String @default = "") => Column_Name.ElementAtOrDefault(index, @default); + public Int32[] Column_Source { get; } + public Int32 GetSource(int index, Int32 @default = default) => Column_Source.ElementAtOrDefault(index, @default); + // Object Getter + public Contributor Get(int index) + { + if (index < 0) return null; + var r = new Contributor(); + r.Index = index; + r.Name = GetName(index); + r.Source = GetSource(index); + return r; + } + // Enumerator + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public IEnumerator GetEnumerator() + { + for (var i = 0; i < RowCount; ++i) + yield return Get(i); + } + } // class ContributorTable + + public partial class SyncSessionTable : EntityTable_v2, IEnumerable + { + + public const string TableName = TableNames.SyncSession; + + public EntityTableSet ParentTableSet { get; } // can be null + + public SyncSessionTable(SerializableEntityTable rawTable, string[] stringTable, EntityTableSet parentTableSet = null) : base(rawTable, stringTable) + { + ParentTableSet = parentTableSet; + Column_SessionGuid = GetStringColumnValues("string:SessionGuid") ?? Array.Empty(); + Column_MachineName = GetStringColumnValues("string:MachineName") ?? Array.Empty(); + Column_RevitVersion = GetStringColumnValues("string:RevitVersion") ?? Array.Empty(); + Column_StartedUtc = GetStringColumnValues("string:StartedUtc") ?? Array.Empty(); + Column_EndedUtc = GetStringColumnValues("string:EndedUtc") ?? Array.Empty(); + Column_ContributorIndex = GetIndexColumnValues("index:Vim.Contributor:Contributor") ?? Array.Empty(); + Column_BimDocumentIndex = GetIndexColumnValues("index:Vim.BimDocument:BimDocument") ?? Array.Empty(); + } + + public String[] Column_SessionGuid { get; } + public String GetSessionGuid(int index, String @default = "") => Column_SessionGuid.ElementAtOrDefault(index, @default); + public String[] Column_MachineName { get; } + public String GetMachineName(int index, String @default = "") => Column_MachineName.ElementAtOrDefault(index, @default); + public String[] Column_RevitVersion { get; } + public String GetRevitVersion(int index, String @default = "") => Column_RevitVersion.ElementAtOrDefault(index, @default); + public String[] Column_StartedUtc { get; } + public String GetStartedUtc(int index, String @default = "") => Column_StartedUtc.ElementAtOrDefault(index, @default); + public String[] Column_EndedUtc { get; } + public String GetEndedUtc(int index, String @default = "") => Column_EndedUtc.ElementAtOrDefault(index, @default); + public int[] Column_ContributorIndex { get; } + public int GetContributorIndex(int index) => Column_ContributorIndex.ElementAtOrDefault(index, EntityRelation.None); + public Contributor GetContributor(int index) => _GetReferencedContributor(GetContributorIndex(index)); + private Contributor _GetReferencedContributor(int referencedIndex) => ParentTableSet.GetContributor(referencedIndex); + public int[] Column_BimDocumentIndex { get; } + public int GetBimDocumentIndex(int index) => Column_BimDocumentIndex.ElementAtOrDefault(index, EntityRelation.None); + public BimDocument GetBimDocument(int index) => _GetReferencedBimDocument(GetBimDocumentIndex(index)); + private BimDocument _GetReferencedBimDocument(int referencedIndex) => ParentTableSet.GetBimDocument(referencedIndex); + // Object Getter + public SyncSession Get(int index) + { + if (index < 0) return null; + var r = new SyncSession(); + r.Index = index; + r.SessionGuid = GetSessionGuid(index); + r.MachineName = GetMachineName(index); + r.RevitVersion = GetRevitVersion(index); + r.StartedUtc = GetStartedUtc(index); + r.EndedUtc = GetEndedUtc(index); + r._Contributor = new Relation(GetContributorIndex(index), _GetReferencedContributor); + r._BimDocument = new Relation(GetBimDocumentIndex(index), _GetReferencedBimDocument); + return r; + } + // Enumerator + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public IEnumerator GetEnumerator() + { + for (var i = 0; i < RowCount; ++i) + yield return Get(i); + } + } // class SyncSessionTable + + public partial class ElementChangeTable : EntityTable_v2, IEnumerable + { + + public const string TableName = TableNames.ElementChange; + + public EntityTableSet ParentTableSet { get; } // can be null + + public ElementChangeTable(SerializableEntityTable rawTable, string[] stringTable, EntityTableSet parentTableSet = null) : base(rawTable, stringTable) + { + ParentTableSet = parentTableSet; + Column_ChangeKind = GetDataColumnValues("int:ChangeKind") ?? Array.Empty(); + Column_Confidence = GetDataColumnValues("int:Confidence") ?? Array.Empty(); + Column_ChangedUtc = GetStringColumnValues("string:ChangedUtc") ?? Array.Empty(); + Column_UniqueId = GetStringColumnValues("string:UniqueId") ?? Array.Empty(); + Column_CategoryName = GetStringColumnValues("string:CategoryName") ?? Array.Empty(); + Column_FamilyName = GetStringColumnValues("string:FamilyName") ?? Array.Empty(); + Column_TypeName = GetStringColumnValues("string:TypeName") ?? Array.Empty(); + Column_ElementName = GetStringColumnValues("string:ElementName") ?? Array.Empty(); + Column_LevelName = GetStringColumnValues("string:LevelName") ?? Array.Empty(); + Column_WorksetName = GetStringColumnValues("string:WorksetName") ?? Array.Empty(); + Column_ExportRevisionIndex = GetIndexColumnValues("index:Vim.ExportRevision:ExportRevision") ?? Array.Empty(); + Column_SyncSessionIndex = GetIndexColumnValues("index:Vim.SyncSession:SyncSession") ?? Array.Empty(); + Column_ContributorIndex = GetIndexColumnValues("index:Vim.Contributor:Contributor") ?? Array.Empty(); + } + + public Int32[] Column_ChangeKind { get; } + public Int32 GetChangeKind(int index, Int32 @default = default) => Column_ChangeKind.ElementAtOrDefault(index, @default); + public Int32[] Column_Confidence { get; } + public Int32 GetConfidence(int index, Int32 @default = default) => Column_Confidence.ElementAtOrDefault(index, @default); + public String[] Column_ChangedUtc { get; } + public String GetChangedUtc(int index, String @default = "") => Column_ChangedUtc.ElementAtOrDefault(index, @default); + public String[] Column_UniqueId { get; } + public String GetUniqueId(int index, String @default = "") => Column_UniqueId.ElementAtOrDefault(index, @default); + public String[] Column_CategoryName { get; } + public String GetCategoryName(int index, String @default = "") => Column_CategoryName.ElementAtOrDefault(index, @default); + public String[] Column_FamilyName { get; } + public String GetFamilyName(int index, String @default = "") => Column_FamilyName.ElementAtOrDefault(index, @default); + public String[] Column_TypeName { get; } + public String GetTypeName(int index, String @default = "") => Column_TypeName.ElementAtOrDefault(index, @default); + public String[] Column_ElementName { get; } + public String GetElementName(int index, String @default = "") => Column_ElementName.ElementAtOrDefault(index, @default); + public String[] Column_LevelName { get; } + public String GetLevelName(int index, String @default = "") => Column_LevelName.ElementAtOrDefault(index, @default); + public String[] Column_WorksetName { get; } + public String GetWorksetName(int index, String @default = "") => Column_WorksetName.ElementAtOrDefault(index, @default); + public int[] Column_ExportRevisionIndex { get; } + public int GetExportRevisionIndex(int index) => Column_ExportRevisionIndex.ElementAtOrDefault(index, EntityRelation.None); + public ExportRevision GetExportRevision(int index) => _GetReferencedExportRevision(GetExportRevisionIndex(index)); + private ExportRevision _GetReferencedExportRevision(int referencedIndex) => ParentTableSet.GetExportRevision(referencedIndex); + public int[] Column_SyncSessionIndex { get; } + public int GetSyncSessionIndex(int index) => Column_SyncSessionIndex.ElementAtOrDefault(index, EntityRelation.None); + public SyncSession GetSyncSession(int index) => _GetReferencedSyncSession(GetSyncSessionIndex(index)); + private SyncSession _GetReferencedSyncSession(int referencedIndex) => ParentTableSet.GetSyncSession(referencedIndex); + public int[] Column_ContributorIndex { get; } + public int GetContributorIndex(int index) => Column_ContributorIndex.ElementAtOrDefault(index, EntityRelation.None); + public Contributor GetContributor(int index) => _GetReferencedContributor(GetContributorIndex(index)); + private Contributor _GetReferencedContributor(int referencedIndex) => ParentTableSet.GetContributor(referencedIndex); + // Object Getter + public ElementChange Get(int index) + { + if (index < 0) return null; + var r = new ElementChange(); + r.Index = index; + r.ChangeKind = GetChangeKind(index); + r.Confidence = GetConfidence(index); + r.ChangedUtc = GetChangedUtc(index); + r.UniqueId = GetUniqueId(index); + r.CategoryName = GetCategoryName(index); + r.FamilyName = GetFamilyName(index); + r.TypeName = GetTypeName(index); + r.ElementName = GetElementName(index); + r.LevelName = GetLevelName(index); + r.WorksetName = GetWorksetName(index); + r._ExportRevision = new Relation(GetExportRevisionIndex(index), _GetReferencedExportRevision); + r._SyncSession = new Relation(GetSyncSessionIndex(index), _GetReferencedSyncSession); + r._Contributor = new Relation(GetContributorIndex(index), _GetReferencedContributor); + return r; + } + // Enumerator + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public IEnumerator GetEnumerator() + { + for (var i = 0; i < RowCount; ++i) + yield return Get(i); + } + } // class ElementChangeTable + + public partial class ActivityMarkerTable : EntityTable_v2, IEnumerable + { + + public const string TableName = TableNames.ActivityMarker; + + public EntityTableSet ParentTableSet { get; } // can be null + + public ActivityMarkerTable(SerializableEntityTable rawTable, string[] stringTable, EntityTableSet parentTableSet = null) : base(rawTable, stringTable) + { + ParentTableSet = parentTableSet; + Column_Kind = GetDataColumnValues("int:Kind") ?? Array.Empty(); + Column_OccurredUtc = GetStringColumnValues("string:OccurredUtc") ?? Array.Empty(); + Column_Description = GetStringColumnValues("string:Description") ?? Array.Empty(); + Column_ExportRevisionIndex = GetIndexColumnValues("index:Vim.ExportRevision:ExportRevision") ?? Array.Empty(); + Column_SyncSessionIndex = GetIndexColumnValues("index:Vim.SyncSession:SyncSession") ?? Array.Empty(); + Column_ContributorIndex = GetIndexColumnValues("index:Vim.Contributor:Contributor") ?? Array.Empty(); + } + + public Int32[] Column_Kind { get; } + public Int32 GetKind(int index, Int32 @default = default) => Column_Kind.ElementAtOrDefault(index, @default); + public String[] Column_OccurredUtc { get; } + public String GetOccurredUtc(int index, String @default = "") => Column_OccurredUtc.ElementAtOrDefault(index, @default); + public String[] Column_Description { get; } + public String GetDescription(int index, String @default = "") => Column_Description.ElementAtOrDefault(index, @default); + public int[] Column_ExportRevisionIndex { get; } + public int GetExportRevisionIndex(int index) => Column_ExportRevisionIndex.ElementAtOrDefault(index, EntityRelation.None); + public ExportRevision GetExportRevision(int index) => _GetReferencedExportRevision(GetExportRevisionIndex(index)); + private ExportRevision _GetReferencedExportRevision(int referencedIndex) => ParentTableSet.GetExportRevision(referencedIndex); + public int[] Column_SyncSessionIndex { get; } + public int GetSyncSessionIndex(int index) => Column_SyncSessionIndex.ElementAtOrDefault(index, EntityRelation.None); + public SyncSession GetSyncSession(int index) => _GetReferencedSyncSession(GetSyncSessionIndex(index)); + private SyncSession _GetReferencedSyncSession(int referencedIndex) => ParentTableSet.GetSyncSession(referencedIndex); + public int[] Column_ContributorIndex { get; } + public int GetContributorIndex(int index) => Column_ContributorIndex.ElementAtOrDefault(index, EntityRelation.None); + public Contributor GetContributor(int index) => _GetReferencedContributor(GetContributorIndex(index)); + private Contributor _GetReferencedContributor(int referencedIndex) => ParentTableSet.GetContributor(referencedIndex); + // Object Getter + public ActivityMarker Get(int index) + { + if (index < 0) return null; + var r = new ActivityMarker(); + r.Index = index; + r.Kind = GetKind(index); + r.OccurredUtc = GetOccurredUtc(index); + r.Description = GetDescription(index); + r._ExportRevision = new Relation(GetExportRevisionIndex(index), _GetReferencedExportRevision); + r._SyncSession = new Relation(GetSyncSessionIndex(index), _GetReferencedSyncSession); + r._Contributor = new Relation(GetContributorIndex(index), _GetReferencedContributor); + return r; + } + // Enumerator + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public IEnumerator GetEnumerator() + { + for (var i = 0; i < RowCount; ++i) + yield return Get(i); + } + } // class ActivityMarkerTable + + public static class DocumentBuilderExtensions + { + public static EntityTableBuilder ToAssetTableBuilder(this EntitySetBuilder entitySet) + { + var tb = new EntityTableBuilder(TableNames.Asset); + var entities = entitySet.Entities; + var entityCount = entities.Count; + { + var columnData = new String[entityCount]; + for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i].BufferName; } + tb.AddStringColumn("string:BufferName", columnData); + } + return tb; + } + public static EntityTableBuilder ToDisplayUnitTableBuilder(this EntitySetBuilder entitySet) + { + var tb = new EntityTableBuilder(TableNames.DisplayUnit); + var entities = entitySet.Entities; + var entityCount = entities.Count; + { + var columnData = new String[entityCount]; + for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i].Spec; } + tb.AddStringColumn("string:Spec", columnData); + } + { + var columnData = new String[entityCount]; + for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i].Type; } + tb.AddStringColumn("string:Type", columnData); + } + { + var columnData = new String[entityCount]; + for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i].Label; } + tb.AddStringColumn("string:Label", columnData); + } + return tb; + } + public static EntityTableBuilder ToParameterDescriptorTableBuilder(this EntitySetBuilder entitySet) { var tb = new EntityTableBuilder(TableNames.ParameterDescriptor); var entities = entitySet.Entities; @@ -11204,6 +12054,233 @@ public static EntityTableBuilder ToSunAndShadowSettingsTableBuilder(this EntityS } return tb; } + public static EntityTableBuilder ToExportRevisionTableBuilder(this EntitySetBuilder entitySet) + { + var tb = new EntityTableBuilder(TableNames.ExportRevision); + var entities = entitySet.Entities; + var entityCount = entities.Count; + { + var columnData = new String[entityCount]; + for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i].VimRevision; } + tb.AddStringColumn("string:VimRevision", columnData); + } + { + var columnData = new Int32[entityCount]; + for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i].Kind; } + tb.AddDataColumn("int:Kind", columnData); + } + { + var columnData = new String[entityCount]; + for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i].Generator; } + tb.AddStringColumn("string:Generator", columnData); + } + { + var columnData = new String[entityCount]; + for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i].CreatedUtc; } + tb.AddStringColumn("string:CreatedUtc", columnData); + } + { + var columnData = new Int32[entityCount]; + for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i].DocumentNumSaves; } + tb.AddDataColumn("int:DocumentNumSaves", columnData); + } + { + var columnData = new String[entityCount]; + for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i].DocumentVersionGuid; } + tb.AddStringColumn("string:DocumentVersionGuid", columnData); + } + { + var columnData = new int[entityCount]; + for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i]._BimDocument?.Index ?? EntityRelation.None; } + tb.AddIndexColumn("index:Vim.BimDocument:BimDocument", columnData); + } + return tb; + } + public static EntityTableBuilder ToElementInExportRevisionTableBuilder(this EntitySetBuilder entitySet) + { + var tb = new EntityTableBuilder(TableNames.ElementInExportRevision); + var entities = entitySet.Entities; + var entityCount = entities.Count; + { + var columnData = new int[entityCount]; + for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i]._ExportRevision?.Index ?? EntityRelation.None; } + tb.AddIndexColumn("index:Vim.ExportRevision:ExportRevision", columnData); + } + { + var columnData = new int[entityCount]; + for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i]._Element?.Index ?? EntityRelation.None; } + tb.AddIndexColumn("index:Vim.Element:Element", columnData); + } + return tb; + } + public static EntityTableBuilder ToContributorTableBuilder(this EntitySetBuilder entitySet) + { + var tb = new EntityTableBuilder(TableNames.Contributor); + var entities = entitySet.Entities; + var entityCount = entities.Count; + { + var columnData = new String[entityCount]; + for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i].Name; } + tb.AddStringColumn("string:Name", columnData); + } + { + var columnData = new Int32[entityCount]; + for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i].Source; } + tb.AddDataColumn("int:Source", columnData); + } + return tb; + } + public static EntityTableBuilder ToSyncSessionTableBuilder(this EntitySetBuilder entitySet) + { + var tb = new EntityTableBuilder(TableNames.SyncSession); + var entities = entitySet.Entities; + var entityCount = entities.Count; + { + var columnData = new String[entityCount]; + for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i].SessionGuid; } + tb.AddStringColumn("string:SessionGuid", columnData); + } + { + var columnData = new String[entityCount]; + for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i].MachineName; } + tb.AddStringColumn("string:MachineName", columnData); + } + { + var columnData = new String[entityCount]; + for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i].RevitVersion; } + tb.AddStringColumn("string:RevitVersion", columnData); + } + { + var columnData = new String[entityCount]; + for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i].StartedUtc; } + tb.AddStringColumn("string:StartedUtc", columnData); + } + { + var columnData = new String[entityCount]; + for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i].EndedUtc; } + tb.AddStringColumn("string:EndedUtc", columnData); + } + { + var columnData = new int[entityCount]; + for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i]._Contributor?.Index ?? EntityRelation.None; } + tb.AddIndexColumn("index:Vim.Contributor:Contributor", columnData); + } + { + var columnData = new int[entityCount]; + for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i]._BimDocument?.Index ?? EntityRelation.None; } + tb.AddIndexColumn("index:Vim.BimDocument:BimDocument", columnData); + } + return tb; + } + public static EntityTableBuilder ToElementChangeTableBuilder(this EntitySetBuilder entitySet) + { + var tb = new EntityTableBuilder(TableNames.ElementChange); + var entities = entitySet.Entities; + var entityCount = entities.Count; + { + var columnData = new Int32[entityCount]; + for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i].ChangeKind; } + tb.AddDataColumn("int:ChangeKind", columnData); + } + { + var columnData = new Int32[entityCount]; + for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i].Confidence; } + tb.AddDataColumn("int:Confidence", columnData); + } + { + var columnData = new String[entityCount]; + for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i].ChangedUtc; } + tb.AddStringColumn("string:ChangedUtc", columnData); + } + { + var columnData = new String[entityCount]; + for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i].UniqueId; } + tb.AddStringColumn("string:UniqueId", columnData); + } + { + var columnData = new String[entityCount]; + for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i].CategoryName; } + tb.AddStringColumn("string:CategoryName", columnData); + } + { + var columnData = new String[entityCount]; + for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i].FamilyName; } + tb.AddStringColumn("string:FamilyName", columnData); + } + { + var columnData = new String[entityCount]; + for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i].TypeName; } + tb.AddStringColumn("string:TypeName", columnData); + } + { + var columnData = new String[entityCount]; + for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i].ElementName; } + tb.AddStringColumn("string:ElementName", columnData); + } + { + var columnData = new String[entityCount]; + for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i].LevelName; } + tb.AddStringColumn("string:LevelName", columnData); + } + { + var columnData = new String[entityCount]; + for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i].WorksetName; } + tb.AddStringColumn("string:WorksetName", columnData); + } + { + var columnData = new int[entityCount]; + for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i]._ExportRevision?.Index ?? EntityRelation.None; } + tb.AddIndexColumn("index:Vim.ExportRevision:ExportRevision", columnData); + } + { + var columnData = new int[entityCount]; + for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i]._SyncSession?.Index ?? EntityRelation.None; } + tb.AddIndexColumn("index:Vim.SyncSession:SyncSession", columnData); + } + { + var columnData = new int[entityCount]; + for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i]._Contributor?.Index ?? EntityRelation.None; } + tb.AddIndexColumn("index:Vim.Contributor:Contributor", columnData); + } + return tb; + } + public static EntityTableBuilder ToActivityMarkerTableBuilder(this EntitySetBuilder entitySet) + { + var tb = new EntityTableBuilder(TableNames.ActivityMarker); + var entities = entitySet.Entities; + var entityCount = entities.Count; + { + var columnData = new Int32[entityCount]; + for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i].Kind; } + tb.AddDataColumn("int:Kind", columnData); + } + { + var columnData = new String[entityCount]; + for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i].OccurredUtc; } + tb.AddStringColumn("string:OccurredUtc", columnData); + } + { + var columnData = new String[entityCount]; + for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i].Description; } + tb.AddStringColumn("string:Description", columnData); + } + { + var columnData = new int[entityCount]; + for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i]._ExportRevision?.Index ?? EntityRelation.None; } + tb.AddIndexColumn("index:Vim.ExportRevision:ExportRevision", columnData); + } + { + var columnData = new int[entityCount]; + for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i]._SyncSession?.Index ?? EntityRelation.None; } + tb.AddIndexColumn("index:Vim.SyncSession:SyncSession", columnData); + } + { + var columnData = new int[entityCount]; + for (var i = 0; i < columnData.Length; ++i) { columnData[i] = entities[i]._Contributor?.Index ?? EntityRelation.None; } + tb.AddIndexColumn("index:Vim.Contributor:Contributor", columnData); + } + return tb; + } } // DocumentBuilderExtensions public partial class ObjectModelBuilder @@ -11265,6 +12342,12 @@ public partial class ObjectModelBuilder public readonly EntitySetBuilder LightTypeBuilder = new EntitySetBuilder(TableNames.LightType); public readonly EntitySetBuilder LightSourceBuilder = new EntitySetBuilder(TableNames.LightSource); public readonly EntitySetBuilder SunAndShadowSettingsBuilder = new EntitySetBuilder(TableNames.SunAndShadowSettings); + public readonly EntitySetBuilder ExportRevisionBuilder = new EntitySetBuilder(TableNames.ExportRevision); + public readonly EntitySetBuilder ElementInExportRevisionBuilder = new EntitySetBuilder(TableNames.ElementInExportRevision); + public readonly EntitySetBuilder ContributorBuilder = new EntitySetBuilder(TableNames.Contributor); + public readonly EntitySetBuilder SyncSessionBuilder = new EntitySetBuilder(TableNames.SyncSession); + public readonly EntitySetBuilder ElementChangeBuilder = new EntitySetBuilder(TableNames.ElementChange); + public readonly EntitySetBuilder ActivityMarkerBuilder = new EntitySetBuilder(TableNames.ActivityMarker); public DocumentBuilder AddEntityTableSets(DocumentBuilder db) { @@ -11325,6 +12408,12 @@ public DocumentBuilder AddEntityTableSets(DocumentBuilder db) db.Tables.Add(LightTypeBuilder.EntityTableName, LightTypeBuilder.ToLightTypeTableBuilder()); db.Tables.Add(LightSourceBuilder.EntityTableName, LightSourceBuilder.ToLightSourceTableBuilder()); db.Tables.Add(SunAndShadowSettingsBuilder.EntityTableName, SunAndShadowSettingsBuilder.ToSunAndShadowSettingsTableBuilder()); + db.Tables.Add(ExportRevisionBuilder.EntityTableName, ExportRevisionBuilder.ToExportRevisionTableBuilder()); + db.Tables.Add(ElementInExportRevisionBuilder.EntityTableName, ElementInExportRevisionBuilder.ToElementInExportRevisionTableBuilder()); + db.Tables.Add(ContributorBuilder.EntityTableName, ContributorBuilder.ToContributorTableBuilder()); + db.Tables.Add(SyncSessionBuilder.EntityTableName, SyncSessionBuilder.ToSyncSessionTableBuilder()); + db.Tables.Add(ElementChangeBuilder.EntityTableName, ElementChangeBuilder.ToElementChangeTableBuilder()); + db.Tables.Add(ActivityMarkerBuilder.EntityTableName, ActivityMarkerBuilder.ToActivityMarkerTableBuilder()); return db; } // AddEntityTableSets @@ -11388,6 +12477,12 @@ public void Clear() LightTypeBuilder.Clear(); LightSourceBuilder.Clear(); SunAndShadowSettingsBuilder.Clear(); + ExportRevisionBuilder.Clear(); + ElementInExportRevisionBuilder.Clear(); + ContributorBuilder.Clear(); + SyncSessionBuilder.Clear(); + ElementChangeBuilder.Clear(); + ActivityMarkerBuilder.Clear(); } // Clear } // ObjectModelBuilder } // namespace diff --git a/src/ts/src/objectModel.ts b/src/ts/src/objectModel.ts index 0b8f491..ef5bf77 100644 --- a/src/ts/src/objectModel.ts +++ b/src/ts/src/objectModel.ts @@ -11852,6 +11852,1185 @@ export class SunAndShadowSettingsTable implements ISunAndShadowSettingsTable { } +export interface IExportRevision { + index: number + vimRevision?: string + kind?: number + generator?: string + createdUtc?: string + documentNumSaves?: number + documentVersionGuid?: string + + bimDocumentIndex?: number + bimDocument?: IBimDocument +} + +export interface IExportRevisionTable { + getCount(): Promise + get(exportRevisionIndex: number): Promise + getAll(): Promise + + getVimRevision(exportRevisionIndex: number): Promise + getAllVimRevision(): Promise + getKind(exportRevisionIndex: number): Promise + getAllKind(): Promise + getGenerator(exportRevisionIndex: number): Promise + getAllGenerator(): Promise + getCreatedUtc(exportRevisionIndex: number): Promise + getAllCreatedUtc(): Promise + getDocumentNumSaves(exportRevisionIndex: number): Promise + getAllDocumentNumSaves(): Promise + getDocumentVersionGuid(exportRevisionIndex: number): Promise + getAllDocumentVersionGuid(): Promise + + getBimDocumentIndex(exportRevisionIndex: number): Promise + getAllBimDocumentIndex(): Promise + getBimDocument(exportRevisionIndex: number): Promise +} + +export class ExportRevision implements IExportRevision { + index: number + vimRevision?: string + kind?: number + generator?: string + createdUtc?: string + documentNumSaves?: number + documentVersionGuid?: string + + bimDocumentIndex?: number + bimDocument?: IBimDocument + + static async createFromTable(table: IExportRevisionTable, index: number): Promise { + let result = new ExportRevision() + result.index = index + + await Promise.all([ + table.getVimRevision(index).then(v => result.vimRevision = v), + table.getKind(index).then(v => result.kind = v), + table.getGenerator(index).then(v => result.generator = v), + table.getCreatedUtc(index).then(v => result.createdUtc = v), + table.getDocumentNumSaves(index).then(v => result.documentNumSaves = v), + table.getDocumentVersionGuid(index).then(v => result.documentVersionGuid = v), + table.getBimDocumentIndex(index).then(v => result.bimDocumentIndex = v), + ]) + + return result + } +} + +export class ExportRevisionTable implements IExportRevisionTable { + private document: VimDocument + private entityTable: EntityTable + + static async createFromDocument(document: VimDocument): Promise { + const entity = await document.entities.getBfast("Vim.ExportRevision") + + if (!entity) { + return undefined + } + + let table = new ExportRevisionTable() + table.document = document + table.entityTable = new EntityTable(entity, document.strings) + + return table + } + + getCount(): Promise { + return this.entityTable.getCount() + } + + async get(exportRevisionIndex: number): Promise { + return await ExportRevision.createFromTable(this, exportRevisionIndex) + } + + async getAll(): Promise { + const localTable = await this.entityTable.getLocal() + + let vimRevision: string[] | undefined + let kind: number[] | undefined + let generator: string[] | undefined + let createdUtc: string[] | undefined + let documentNumSaves: number[] | undefined + let documentVersionGuid: string[] | undefined + let bimDocumentIndex: number[] | undefined + + await Promise.all([ + (async () => { vimRevision = (await localTable.getStringArray("string:VimRevision")) })(), + (async () => { kind = (await localTable.getNumberArray("int:Kind")) })(), + (async () => { generator = (await localTable.getStringArray("string:Generator")) })(), + (async () => { createdUtc = (await localTable.getStringArray("string:CreatedUtc")) })(), + (async () => { documentNumSaves = (await localTable.getNumberArray("int:DocumentNumSaves")) })(), + (async () => { documentVersionGuid = (await localTable.getStringArray("string:DocumentVersionGuid")) })(), + (async () => { bimDocumentIndex = (await localTable.getNumberArray("index:Vim.BimDocument:BimDocument")) })(), + ]) + + let exportRevision: IExportRevision[] = [] + + const rowCount = await this.getCount() + for (let i = 0; i < rowCount; i++) { + exportRevision.push({ + index: i, + vimRevision: vimRevision ? vimRevision[i] : undefined, + kind: kind ? kind[i] : undefined, + generator: generator ? generator[i] : undefined, + createdUtc: createdUtc ? createdUtc[i] : undefined, + documentNumSaves: documentNumSaves ? documentNumSaves[i] : undefined, + documentVersionGuid: documentVersionGuid ? documentVersionGuid[i] : undefined, + bimDocumentIndex: bimDocumentIndex ? bimDocumentIndex[i] : undefined + }) + } + + return exportRevision + } + + async getVimRevision(exportRevisionIndex: number): Promise { + return (await this.entityTable.getString(exportRevisionIndex, "string:VimRevision")) + } + + async getAllVimRevision(): Promise { + return (await this.entityTable.getStringArray("string:VimRevision")) + } + + async getKind(exportRevisionIndex: number): Promise { + return (await this.entityTable.getNumber(exportRevisionIndex, "int:Kind")) + } + + async getAllKind(): Promise { + return (await this.entityTable.getNumberArray("int:Kind")) + } + + async getGenerator(exportRevisionIndex: number): Promise { + return (await this.entityTable.getString(exportRevisionIndex, "string:Generator")) + } + + async getAllGenerator(): Promise { + return (await this.entityTable.getStringArray("string:Generator")) + } + + async getCreatedUtc(exportRevisionIndex: number): Promise { + return (await this.entityTable.getString(exportRevisionIndex, "string:CreatedUtc")) + } + + async getAllCreatedUtc(): Promise { + return (await this.entityTable.getStringArray("string:CreatedUtc")) + } + + async getDocumentNumSaves(exportRevisionIndex: number): Promise { + return (await this.entityTable.getNumber(exportRevisionIndex, "int:DocumentNumSaves")) + } + + async getAllDocumentNumSaves(): Promise { + return (await this.entityTable.getNumberArray("int:DocumentNumSaves")) + } + + async getDocumentVersionGuid(exportRevisionIndex: number): Promise { + return (await this.entityTable.getString(exportRevisionIndex, "string:DocumentVersionGuid")) + } + + async getAllDocumentVersionGuid(): Promise { + return (await this.entityTable.getStringArray("string:DocumentVersionGuid")) + } + + async getBimDocumentIndex(exportRevisionIndex: number): Promise { + return await this.entityTable.getNumber(exportRevisionIndex, "index:Vim.BimDocument:BimDocument") + } + + async getAllBimDocumentIndex(): Promise { + return await this.entityTable.getNumberArray("index:Vim.BimDocument:BimDocument") + } + + async getBimDocument(exportRevisionIndex: number): Promise { + const index = await this.getBimDocumentIndex(exportRevisionIndex) + + if (index === undefined) { + return undefined + } + + return await this.document.bimDocument?.get(index) + } + +} + +export interface IElementInExportRevision { + index: number + + exportRevisionIndex?: number + exportRevision?: IExportRevision + elementIndex?: number + element?: IElement +} + +export interface IElementInExportRevisionTable { + getCount(): Promise + get(elementInExportRevisionIndex: number): Promise + getAll(): Promise + + getExportRevisionIndex(elementInExportRevisionIndex: number): Promise + getAllExportRevisionIndex(): Promise + getExportRevision(elementInExportRevisionIndex: number): Promise + getElementIndex(elementInExportRevisionIndex: number): Promise + getAllElementIndex(): Promise + getElement(elementInExportRevisionIndex: number): Promise +} + +export class ElementInExportRevision implements IElementInExportRevision { + index: number + + exportRevisionIndex?: number + exportRevision?: IExportRevision + elementIndex?: number + element?: IElement + + static async createFromTable(table: IElementInExportRevisionTable, index: number): Promise { + let result = new ElementInExportRevision() + result.index = index + + await Promise.all([ + table.getExportRevisionIndex(index).then(v => result.exportRevisionIndex = v), + table.getElementIndex(index).then(v => result.elementIndex = v), + ]) + + return result + } +} + +export class ElementInExportRevisionTable implements IElementInExportRevisionTable { + private document: VimDocument + private entityTable: EntityTable + + static async createFromDocument(document: VimDocument): Promise { + const entity = await document.entities.getBfast("Vim.ElementInExportRevision") + + if (!entity) { + return undefined + } + + let table = new ElementInExportRevisionTable() + table.document = document + table.entityTable = new EntityTable(entity, document.strings) + + return table + } + + getCount(): Promise { + return this.entityTable.getCount() + } + + async get(elementInExportRevisionIndex: number): Promise { + return await ElementInExportRevision.createFromTable(this, elementInExportRevisionIndex) + } + + async getAll(): Promise { + const localTable = await this.entityTable.getLocal() + + let exportRevisionIndex: number[] | undefined + let elementIndex: number[] | undefined + + await Promise.all([ + (async () => { exportRevisionIndex = (await localTable.getNumberArray("index:Vim.ExportRevision:ExportRevision")) })(), + (async () => { elementIndex = (await localTable.getNumberArray("index:Vim.Element:Element")) })(), + ]) + + let elementInExportRevision: IElementInExportRevision[] = [] + + const rowCount = await this.getCount() + for (let i = 0; i < rowCount; i++) { + elementInExportRevision.push({ + index: i, + exportRevisionIndex: exportRevisionIndex ? exportRevisionIndex[i] : undefined, + elementIndex: elementIndex ? elementIndex[i] : undefined + }) + } + + return elementInExportRevision + } + + async getExportRevisionIndex(elementInExportRevisionIndex: number): Promise { + return await this.entityTable.getNumber(elementInExportRevisionIndex, "index:Vim.ExportRevision:ExportRevision") + } + + async getAllExportRevisionIndex(): Promise { + return await this.entityTable.getNumberArray("index:Vim.ExportRevision:ExportRevision") + } + + async getExportRevision(elementInExportRevisionIndex: number): Promise { + const index = await this.getExportRevisionIndex(elementInExportRevisionIndex) + + if (index === undefined) { + return undefined + } + + return await this.document.exportRevision?.get(index) + } + + async getElementIndex(elementInExportRevisionIndex: number): Promise { + return await this.entityTable.getNumber(elementInExportRevisionIndex, "index:Vim.Element:Element") + } + + async getAllElementIndex(): Promise { + return await this.entityTable.getNumberArray("index:Vim.Element:Element") + } + + async getElement(elementInExportRevisionIndex: number): Promise { + const index = await this.getElementIndex(elementInExportRevisionIndex) + + if (index === undefined) { + return undefined + } + + return await this.document.element?.get(index) + } + +} + +export interface IContributor { + index: number + name?: string + source?: number +} + +export interface IContributorTable { + getCount(): Promise + get(contributorIndex: number): Promise + getAll(): Promise + + getName(contributorIndex: number): Promise + getAllName(): Promise + getSource(contributorIndex: number): Promise + getAllSource(): Promise +} + +export class Contributor implements IContributor { + index: number + name?: string + source?: number + + static async createFromTable(table: IContributorTable, index: number): Promise { + let result = new Contributor() + result.index = index + + await Promise.all([ + table.getName(index).then(v => result.name = v), + table.getSource(index).then(v => result.source = v), + ]) + + return result + } +} + +export class ContributorTable implements IContributorTable { + private entityTable: EntityTable + + static async createFromDocument(document: VimDocument): Promise { + const entity = await document.entities.getBfast("Vim.Contributor") + + if (!entity) { + return undefined + } + + let table = new ContributorTable() + table.entityTable = new EntityTable(entity, document.strings) + + return table + } + + getCount(): Promise { + return this.entityTable.getCount() + } + + async get(contributorIndex: number): Promise { + return await Contributor.createFromTable(this, contributorIndex) + } + + async getAll(): Promise { + const localTable = await this.entityTable.getLocal() + + let name: string[] | undefined + let source: number[] | undefined + + await Promise.all([ + (async () => { name = (await localTable.getStringArray("string:Name")) })(), + (async () => { source = (await localTable.getNumberArray("int:Source")) })(), + ]) + + let contributor: IContributor[] = [] + + const rowCount = await this.getCount() + for (let i = 0; i < rowCount; i++) { + contributor.push({ + index: i, + name: name ? name[i] : undefined, + source: source ? source[i] : undefined + }) + } + + return contributor + } + + async getName(contributorIndex: number): Promise { + return (await this.entityTable.getString(contributorIndex, "string:Name")) + } + + async getAllName(): Promise { + return (await this.entityTable.getStringArray("string:Name")) + } + + async getSource(contributorIndex: number): Promise { + return (await this.entityTable.getNumber(contributorIndex, "int:Source")) + } + + async getAllSource(): Promise { + return (await this.entityTable.getNumberArray("int:Source")) + } + +} + +export interface ISyncSession { + index: number + sessionGuid?: string + machineName?: string + revitVersion?: string + startedUtc?: string + endedUtc?: string + + contributorIndex?: number + contributor?: IContributor + bimDocumentIndex?: number + bimDocument?: IBimDocument +} + +export interface ISyncSessionTable { + getCount(): Promise + get(syncSessionIndex: number): Promise + getAll(): Promise + + getSessionGuid(syncSessionIndex: number): Promise + getAllSessionGuid(): Promise + getMachineName(syncSessionIndex: number): Promise + getAllMachineName(): Promise + getRevitVersion(syncSessionIndex: number): Promise + getAllRevitVersion(): Promise + getStartedUtc(syncSessionIndex: number): Promise + getAllStartedUtc(): Promise + getEndedUtc(syncSessionIndex: number): Promise + getAllEndedUtc(): Promise + + getContributorIndex(syncSessionIndex: number): Promise + getAllContributorIndex(): Promise + getContributor(syncSessionIndex: number): Promise + getBimDocumentIndex(syncSessionIndex: number): Promise + getAllBimDocumentIndex(): Promise + getBimDocument(syncSessionIndex: number): Promise +} + +export class SyncSession implements ISyncSession { + index: number + sessionGuid?: string + machineName?: string + revitVersion?: string + startedUtc?: string + endedUtc?: string + + contributorIndex?: number + contributor?: IContributor + bimDocumentIndex?: number + bimDocument?: IBimDocument + + static async createFromTable(table: ISyncSessionTable, index: number): Promise { + let result = new SyncSession() + result.index = index + + await Promise.all([ + table.getSessionGuid(index).then(v => result.sessionGuid = v), + table.getMachineName(index).then(v => result.machineName = v), + table.getRevitVersion(index).then(v => result.revitVersion = v), + table.getStartedUtc(index).then(v => result.startedUtc = v), + table.getEndedUtc(index).then(v => result.endedUtc = v), + table.getContributorIndex(index).then(v => result.contributorIndex = v), + table.getBimDocumentIndex(index).then(v => result.bimDocumentIndex = v), + ]) + + return result + } +} + +export class SyncSessionTable implements ISyncSessionTable { + private document: VimDocument + private entityTable: EntityTable + + static async createFromDocument(document: VimDocument): Promise { + const entity = await document.entities.getBfast("Vim.SyncSession") + + if (!entity) { + return undefined + } + + let table = new SyncSessionTable() + table.document = document + table.entityTable = new EntityTable(entity, document.strings) + + return table + } + + getCount(): Promise { + return this.entityTable.getCount() + } + + async get(syncSessionIndex: number): Promise { + return await SyncSession.createFromTable(this, syncSessionIndex) + } + + async getAll(): Promise { + const localTable = await this.entityTable.getLocal() + + let sessionGuid: string[] | undefined + let machineName: string[] | undefined + let revitVersion: string[] | undefined + let startedUtc: string[] | undefined + let endedUtc: string[] | undefined + let contributorIndex: number[] | undefined + let bimDocumentIndex: number[] | undefined + + await Promise.all([ + (async () => { sessionGuid = (await localTable.getStringArray("string:SessionGuid")) })(), + (async () => { machineName = (await localTable.getStringArray("string:MachineName")) })(), + (async () => { revitVersion = (await localTable.getStringArray("string:RevitVersion")) })(), + (async () => { startedUtc = (await localTable.getStringArray("string:StartedUtc")) })(), + (async () => { endedUtc = (await localTable.getStringArray("string:EndedUtc")) })(), + (async () => { contributorIndex = (await localTable.getNumberArray("index:Vim.Contributor:Contributor")) })(), + (async () => { bimDocumentIndex = (await localTable.getNumberArray("index:Vim.BimDocument:BimDocument")) })(), + ]) + + let syncSession: ISyncSession[] = [] + + const rowCount = await this.getCount() + for (let i = 0; i < rowCount; i++) { + syncSession.push({ + index: i, + sessionGuid: sessionGuid ? sessionGuid[i] : undefined, + machineName: machineName ? machineName[i] : undefined, + revitVersion: revitVersion ? revitVersion[i] : undefined, + startedUtc: startedUtc ? startedUtc[i] : undefined, + endedUtc: endedUtc ? endedUtc[i] : undefined, + contributorIndex: contributorIndex ? contributorIndex[i] : undefined, + bimDocumentIndex: bimDocumentIndex ? bimDocumentIndex[i] : undefined + }) + } + + return syncSession + } + + async getSessionGuid(syncSessionIndex: number): Promise { + return (await this.entityTable.getString(syncSessionIndex, "string:SessionGuid")) + } + + async getAllSessionGuid(): Promise { + return (await this.entityTable.getStringArray("string:SessionGuid")) + } + + async getMachineName(syncSessionIndex: number): Promise { + return (await this.entityTable.getString(syncSessionIndex, "string:MachineName")) + } + + async getAllMachineName(): Promise { + return (await this.entityTable.getStringArray("string:MachineName")) + } + + async getRevitVersion(syncSessionIndex: number): Promise { + return (await this.entityTable.getString(syncSessionIndex, "string:RevitVersion")) + } + + async getAllRevitVersion(): Promise { + return (await this.entityTable.getStringArray("string:RevitVersion")) + } + + async getStartedUtc(syncSessionIndex: number): Promise { + return (await this.entityTable.getString(syncSessionIndex, "string:StartedUtc")) + } + + async getAllStartedUtc(): Promise { + return (await this.entityTable.getStringArray("string:StartedUtc")) + } + + async getEndedUtc(syncSessionIndex: number): Promise { + return (await this.entityTable.getString(syncSessionIndex, "string:EndedUtc")) + } + + async getAllEndedUtc(): Promise { + return (await this.entityTable.getStringArray("string:EndedUtc")) + } + + async getContributorIndex(syncSessionIndex: number): Promise { + return await this.entityTable.getNumber(syncSessionIndex, "index:Vim.Contributor:Contributor") + } + + async getAllContributorIndex(): Promise { + return await this.entityTable.getNumberArray("index:Vim.Contributor:Contributor") + } + + async getContributor(syncSessionIndex: number): Promise { + const index = await this.getContributorIndex(syncSessionIndex) + + if (index === undefined) { + return undefined + } + + return await this.document.contributor?.get(index) + } + + async getBimDocumentIndex(syncSessionIndex: number): Promise { + return await this.entityTable.getNumber(syncSessionIndex, "index:Vim.BimDocument:BimDocument") + } + + async getAllBimDocumentIndex(): Promise { + return await this.entityTable.getNumberArray("index:Vim.BimDocument:BimDocument") + } + + async getBimDocument(syncSessionIndex: number): Promise { + const index = await this.getBimDocumentIndex(syncSessionIndex) + + if (index === undefined) { + return undefined + } + + return await this.document.bimDocument?.get(index) + } + +} + +export interface IElementChange { + index: number + changeKind?: number + confidence?: number + changedUtc?: string + uniqueId?: string + categoryName?: string + familyName?: string + typeName?: string + elementName?: string + levelName?: string + worksetName?: string + + exportRevisionIndex?: number + exportRevision?: IExportRevision + syncSessionIndex?: number + syncSession?: ISyncSession + contributorIndex?: number + contributor?: IContributor +} + +export interface IElementChangeTable { + getCount(): Promise + get(elementChangeIndex: number): Promise + getAll(): Promise + + getChangeKind(elementChangeIndex: number): Promise + getAllChangeKind(): Promise + getConfidence(elementChangeIndex: number): Promise + getAllConfidence(): Promise + getChangedUtc(elementChangeIndex: number): Promise + getAllChangedUtc(): Promise + getUniqueId(elementChangeIndex: number): Promise + getAllUniqueId(): Promise + getCategoryName(elementChangeIndex: number): Promise + getAllCategoryName(): Promise + getFamilyName(elementChangeIndex: number): Promise + getAllFamilyName(): Promise + getTypeName(elementChangeIndex: number): Promise + getAllTypeName(): Promise + getElementName(elementChangeIndex: number): Promise + getAllElementName(): Promise + getLevelName(elementChangeIndex: number): Promise + getAllLevelName(): Promise + getWorksetName(elementChangeIndex: number): Promise + getAllWorksetName(): Promise + + getExportRevisionIndex(elementChangeIndex: number): Promise + getAllExportRevisionIndex(): Promise + getExportRevision(elementChangeIndex: number): Promise + getSyncSessionIndex(elementChangeIndex: number): Promise + getAllSyncSessionIndex(): Promise + getSyncSession(elementChangeIndex: number): Promise + getContributorIndex(elementChangeIndex: number): Promise + getAllContributorIndex(): Promise + getContributor(elementChangeIndex: number): Promise +} + +export class ElementChange implements IElementChange { + index: number + changeKind?: number + confidence?: number + changedUtc?: string + uniqueId?: string + categoryName?: string + familyName?: string + typeName?: string + elementName?: string + levelName?: string + worksetName?: string + + exportRevisionIndex?: number + exportRevision?: IExportRevision + syncSessionIndex?: number + syncSession?: ISyncSession + contributorIndex?: number + contributor?: IContributor + + static async createFromTable(table: IElementChangeTable, index: number): Promise { + let result = new ElementChange() + result.index = index + + await Promise.all([ + table.getChangeKind(index).then(v => result.changeKind = v), + table.getConfidence(index).then(v => result.confidence = v), + table.getChangedUtc(index).then(v => result.changedUtc = v), + table.getUniqueId(index).then(v => result.uniqueId = v), + table.getCategoryName(index).then(v => result.categoryName = v), + table.getFamilyName(index).then(v => result.familyName = v), + table.getTypeName(index).then(v => result.typeName = v), + table.getElementName(index).then(v => result.elementName = v), + table.getLevelName(index).then(v => result.levelName = v), + table.getWorksetName(index).then(v => result.worksetName = v), + table.getExportRevisionIndex(index).then(v => result.exportRevisionIndex = v), + table.getSyncSessionIndex(index).then(v => result.syncSessionIndex = v), + table.getContributorIndex(index).then(v => result.contributorIndex = v), + ]) + + return result + } +} + +export class ElementChangeTable implements IElementChangeTable { + private document: VimDocument + private entityTable: EntityTable + + static async createFromDocument(document: VimDocument): Promise { + const entity = await document.entities.getBfast("Vim.ElementChange") + + if (!entity) { + return undefined + } + + let table = new ElementChangeTable() + table.document = document + table.entityTable = new EntityTable(entity, document.strings) + + return table + } + + getCount(): Promise { + return this.entityTable.getCount() + } + + async get(elementChangeIndex: number): Promise { + return await ElementChange.createFromTable(this, elementChangeIndex) + } + + async getAll(): Promise { + const localTable = await this.entityTable.getLocal() + + let changeKind: number[] | undefined + let confidence: number[] | undefined + let changedUtc: string[] | undefined + let uniqueId: string[] | undefined + let categoryName: string[] | undefined + let familyName: string[] | undefined + let typeName: string[] | undefined + let elementName: string[] | undefined + let levelName: string[] | undefined + let worksetName: string[] | undefined + let exportRevisionIndex: number[] | undefined + let syncSessionIndex: number[] | undefined + let contributorIndex: number[] | undefined + + await Promise.all([ + (async () => { changeKind = (await localTable.getNumberArray("int:ChangeKind")) })(), + (async () => { confidence = (await localTable.getNumberArray("int:Confidence")) })(), + (async () => { changedUtc = (await localTable.getStringArray("string:ChangedUtc")) })(), + (async () => { uniqueId = (await localTable.getStringArray("string:UniqueId")) })(), + (async () => { categoryName = (await localTable.getStringArray("string:CategoryName")) })(), + (async () => { familyName = (await localTable.getStringArray("string:FamilyName")) })(), + (async () => { typeName = (await localTable.getStringArray("string:TypeName")) })(), + (async () => { elementName = (await localTable.getStringArray("string:ElementName")) })(), + (async () => { levelName = (await localTable.getStringArray("string:LevelName")) })(), + (async () => { worksetName = (await localTable.getStringArray("string:WorksetName")) })(), + (async () => { exportRevisionIndex = (await localTable.getNumberArray("index:Vim.ExportRevision:ExportRevision")) })(), + (async () => { syncSessionIndex = (await localTable.getNumberArray("index:Vim.SyncSession:SyncSession")) })(), + (async () => { contributorIndex = (await localTable.getNumberArray("index:Vim.Contributor:Contributor")) })(), + ]) + + let elementChange: IElementChange[] = [] + + const rowCount = await this.getCount() + for (let i = 0; i < rowCount; i++) { + elementChange.push({ + index: i, + changeKind: changeKind ? changeKind[i] : undefined, + confidence: confidence ? confidence[i] : undefined, + changedUtc: changedUtc ? changedUtc[i] : undefined, + uniqueId: uniqueId ? uniqueId[i] : undefined, + categoryName: categoryName ? categoryName[i] : undefined, + familyName: familyName ? familyName[i] : undefined, + typeName: typeName ? typeName[i] : undefined, + elementName: elementName ? elementName[i] : undefined, + levelName: levelName ? levelName[i] : undefined, + worksetName: worksetName ? worksetName[i] : undefined, + exportRevisionIndex: exportRevisionIndex ? exportRevisionIndex[i] : undefined, + syncSessionIndex: syncSessionIndex ? syncSessionIndex[i] : undefined, + contributorIndex: contributorIndex ? contributorIndex[i] : undefined + }) + } + + return elementChange + } + + async getChangeKind(elementChangeIndex: number): Promise { + return (await this.entityTable.getNumber(elementChangeIndex, "int:ChangeKind")) + } + + async getAllChangeKind(): Promise { + return (await this.entityTable.getNumberArray("int:ChangeKind")) + } + + async getConfidence(elementChangeIndex: number): Promise { + return (await this.entityTable.getNumber(elementChangeIndex, "int:Confidence")) + } + + async getAllConfidence(): Promise { + return (await this.entityTable.getNumberArray("int:Confidence")) + } + + async getChangedUtc(elementChangeIndex: number): Promise { + return (await this.entityTable.getString(elementChangeIndex, "string:ChangedUtc")) + } + + async getAllChangedUtc(): Promise { + return (await this.entityTable.getStringArray("string:ChangedUtc")) + } + + async getUniqueId(elementChangeIndex: number): Promise { + return (await this.entityTable.getString(elementChangeIndex, "string:UniqueId")) + } + + async getAllUniqueId(): Promise { + return (await this.entityTable.getStringArray("string:UniqueId")) + } + + async getCategoryName(elementChangeIndex: number): Promise { + return (await this.entityTable.getString(elementChangeIndex, "string:CategoryName")) + } + + async getAllCategoryName(): Promise { + return (await this.entityTable.getStringArray("string:CategoryName")) + } + + async getFamilyName(elementChangeIndex: number): Promise { + return (await this.entityTable.getString(elementChangeIndex, "string:FamilyName")) + } + + async getAllFamilyName(): Promise { + return (await this.entityTable.getStringArray("string:FamilyName")) + } + + async getTypeName(elementChangeIndex: number): Promise { + return (await this.entityTable.getString(elementChangeIndex, "string:TypeName")) + } + + async getAllTypeName(): Promise { + return (await this.entityTable.getStringArray("string:TypeName")) + } + + async getElementName(elementChangeIndex: number): Promise { + return (await this.entityTable.getString(elementChangeIndex, "string:ElementName")) + } + + async getAllElementName(): Promise { + return (await this.entityTable.getStringArray("string:ElementName")) + } + + async getLevelName(elementChangeIndex: number): Promise { + return (await this.entityTable.getString(elementChangeIndex, "string:LevelName")) + } + + async getAllLevelName(): Promise { + return (await this.entityTable.getStringArray("string:LevelName")) + } + + async getWorksetName(elementChangeIndex: number): Promise { + return (await this.entityTable.getString(elementChangeIndex, "string:WorksetName")) + } + + async getAllWorksetName(): Promise { + return (await this.entityTable.getStringArray("string:WorksetName")) + } + + async getExportRevisionIndex(elementChangeIndex: number): Promise { + return await this.entityTable.getNumber(elementChangeIndex, "index:Vim.ExportRevision:ExportRevision") + } + + async getAllExportRevisionIndex(): Promise { + return await this.entityTable.getNumberArray("index:Vim.ExportRevision:ExportRevision") + } + + async getExportRevision(elementChangeIndex: number): Promise { + const index = await this.getExportRevisionIndex(elementChangeIndex) + + if (index === undefined) { + return undefined + } + + return await this.document.exportRevision?.get(index) + } + + async getSyncSessionIndex(elementChangeIndex: number): Promise { + return await this.entityTable.getNumber(elementChangeIndex, "index:Vim.SyncSession:SyncSession") + } + + async getAllSyncSessionIndex(): Promise { + return await this.entityTable.getNumberArray("index:Vim.SyncSession:SyncSession") + } + + async getSyncSession(elementChangeIndex: number): Promise { + const index = await this.getSyncSessionIndex(elementChangeIndex) + + if (index === undefined) { + return undefined + } + + return await this.document.syncSession?.get(index) + } + + async getContributorIndex(elementChangeIndex: number): Promise { + return await this.entityTable.getNumber(elementChangeIndex, "index:Vim.Contributor:Contributor") + } + + async getAllContributorIndex(): Promise { + return await this.entityTable.getNumberArray("index:Vim.Contributor:Contributor") + } + + async getContributor(elementChangeIndex: number): Promise { + const index = await this.getContributorIndex(elementChangeIndex) + + if (index === undefined) { + return undefined + } + + return await this.document.contributor?.get(index) + } + +} + +export interface IActivityMarker { + index: number + kind?: number + occurredUtc?: string + description?: string + + exportRevisionIndex?: number + exportRevision?: IExportRevision + syncSessionIndex?: number + syncSession?: ISyncSession + contributorIndex?: number + contributor?: IContributor +} + +export interface IActivityMarkerTable { + getCount(): Promise + get(activityMarkerIndex: number): Promise + getAll(): Promise + + getKind(activityMarkerIndex: number): Promise + getAllKind(): Promise + getOccurredUtc(activityMarkerIndex: number): Promise + getAllOccurredUtc(): Promise + getDescription(activityMarkerIndex: number): Promise + getAllDescription(): Promise + + getExportRevisionIndex(activityMarkerIndex: number): Promise + getAllExportRevisionIndex(): Promise + getExportRevision(activityMarkerIndex: number): Promise + getSyncSessionIndex(activityMarkerIndex: number): Promise + getAllSyncSessionIndex(): Promise + getSyncSession(activityMarkerIndex: number): Promise + getContributorIndex(activityMarkerIndex: number): Promise + getAllContributorIndex(): Promise + getContributor(activityMarkerIndex: number): Promise +} + +export class ActivityMarker implements IActivityMarker { + index: number + kind?: number + occurredUtc?: string + description?: string + + exportRevisionIndex?: number + exportRevision?: IExportRevision + syncSessionIndex?: number + syncSession?: ISyncSession + contributorIndex?: number + contributor?: IContributor + + static async createFromTable(table: IActivityMarkerTable, index: number): Promise { + let result = new ActivityMarker() + result.index = index + + await Promise.all([ + table.getKind(index).then(v => result.kind = v), + table.getOccurredUtc(index).then(v => result.occurredUtc = v), + table.getDescription(index).then(v => result.description = v), + table.getExportRevisionIndex(index).then(v => result.exportRevisionIndex = v), + table.getSyncSessionIndex(index).then(v => result.syncSessionIndex = v), + table.getContributorIndex(index).then(v => result.contributorIndex = v), + ]) + + return result + } +} + +export class ActivityMarkerTable implements IActivityMarkerTable { + private document: VimDocument + private entityTable: EntityTable + + static async createFromDocument(document: VimDocument): Promise { + const entity = await document.entities.getBfast("Vim.ActivityMarker") + + if (!entity) { + return undefined + } + + let table = new ActivityMarkerTable() + table.document = document + table.entityTable = new EntityTable(entity, document.strings) + + return table + } + + getCount(): Promise { + return this.entityTable.getCount() + } + + async get(activityMarkerIndex: number): Promise { + return await ActivityMarker.createFromTable(this, activityMarkerIndex) + } + + async getAll(): Promise { + const localTable = await this.entityTable.getLocal() + + let kind: number[] | undefined + let occurredUtc: string[] | undefined + let description: string[] | undefined + let exportRevisionIndex: number[] | undefined + let syncSessionIndex: number[] | undefined + let contributorIndex: number[] | undefined + + await Promise.all([ + (async () => { kind = (await localTable.getNumberArray("int:Kind")) })(), + (async () => { occurredUtc = (await localTable.getStringArray("string:OccurredUtc")) })(), + (async () => { description = (await localTable.getStringArray("string:Description")) })(), + (async () => { exportRevisionIndex = (await localTable.getNumberArray("index:Vim.ExportRevision:ExportRevision")) })(), + (async () => { syncSessionIndex = (await localTable.getNumberArray("index:Vim.SyncSession:SyncSession")) })(), + (async () => { contributorIndex = (await localTable.getNumberArray("index:Vim.Contributor:Contributor")) })(), + ]) + + let activityMarker: IActivityMarker[] = [] + + const rowCount = await this.getCount() + for (let i = 0; i < rowCount; i++) { + activityMarker.push({ + index: i, + kind: kind ? kind[i] : undefined, + occurredUtc: occurredUtc ? occurredUtc[i] : undefined, + description: description ? description[i] : undefined, + exportRevisionIndex: exportRevisionIndex ? exportRevisionIndex[i] : undefined, + syncSessionIndex: syncSessionIndex ? syncSessionIndex[i] : undefined, + contributorIndex: contributorIndex ? contributorIndex[i] : undefined + }) + } + + return activityMarker + } + + async getKind(activityMarkerIndex: number): Promise { + return (await this.entityTable.getNumber(activityMarkerIndex, "int:Kind")) + } + + async getAllKind(): Promise { + return (await this.entityTable.getNumberArray("int:Kind")) + } + + async getOccurredUtc(activityMarkerIndex: number): Promise { + return (await this.entityTable.getString(activityMarkerIndex, "string:OccurredUtc")) + } + + async getAllOccurredUtc(): Promise { + return (await this.entityTable.getStringArray("string:OccurredUtc")) + } + + async getDescription(activityMarkerIndex: number): Promise { + return (await this.entityTable.getString(activityMarkerIndex, "string:Description")) + } + + async getAllDescription(): Promise { + return (await this.entityTable.getStringArray("string:Description")) + } + + async getExportRevisionIndex(activityMarkerIndex: number): Promise { + return await this.entityTable.getNumber(activityMarkerIndex, "index:Vim.ExportRevision:ExportRevision") + } + + async getAllExportRevisionIndex(): Promise { + return await this.entityTable.getNumberArray("index:Vim.ExportRevision:ExportRevision") + } + + async getExportRevision(activityMarkerIndex: number): Promise { + const index = await this.getExportRevisionIndex(activityMarkerIndex) + + if (index === undefined) { + return undefined + } + + return await this.document.exportRevision?.get(index) + } + + async getSyncSessionIndex(activityMarkerIndex: number): Promise { + return await this.entityTable.getNumber(activityMarkerIndex, "index:Vim.SyncSession:SyncSession") + } + + async getAllSyncSessionIndex(): Promise { + return await this.entityTable.getNumberArray("index:Vim.SyncSession:SyncSession") + } + + async getSyncSession(activityMarkerIndex: number): Promise { + const index = await this.getSyncSessionIndex(activityMarkerIndex) + + if (index === undefined) { + return undefined + } + + return await this.document.syncSession?.get(index) + } + + async getContributorIndex(activityMarkerIndex: number): Promise { + return await this.entityTable.getNumber(activityMarkerIndex, "index:Vim.Contributor:Contributor") + } + + async getAllContributorIndex(): Promise { + return await this.entityTable.getNumberArray("index:Vim.Contributor:Contributor") + } + + async getContributor(activityMarkerIndex: number): Promise { + const index = await this.getContributorIndex(activityMarkerIndex) + + if (index === undefined) { + return undefined + } + + return await this.document.contributor?.get(index) + } + +} + export class VimDocument { asset: IAssetTable | undefined displayUnit: IDisplayUnitTable | undefined @@ -11910,6 +13089,12 @@ export class VimDocument { lightType: ILightTypeTable | undefined lightSource: ILightSourceTable | undefined sunAndShadowSettings: ISunAndShadowSettingsTable | undefined + exportRevision: IExportRevisionTable | undefined + elementInExportRevision: IElementInExportRevisionTable | undefined + contributor: IContributorTable | undefined + syncSession: ISyncSessionTable | undefined + elementChange: IElementChangeTable | undefined + activityMarker: IActivityMarkerTable | undefined entities: BFast strings: string[] | undefined @@ -11984,6 +13169,12 @@ export class VimDocument { doc.lightType = await LightTypeTable.createFromDocument(doc) doc.lightSource = await LightSourceTable.createFromDocument(doc) doc.sunAndShadowSettings = await SunAndShadowSettingsTable.createFromDocument(doc) + doc.exportRevision = await ExportRevisionTable.createFromDocument(doc) + doc.elementInExportRevision = await ElementInExportRevisionTable.createFromDocument(doc) + doc.contributor = await ContributorTable.createFromDocument(doc) + doc.syncSession = await SyncSessionTable.createFromDocument(doc) + doc.elementChange = await ElementChangeTable.createFromDocument(doc) + doc.activityMarker = await ActivityMarkerTable.createFromDocument(doc) return doc }