From b5447b263e680b22fb2d24ceb0fbc926a34ca728 Mon Sep 17 00:00:00 2001 From: Ralph Wessel Date: Thu, 10 Oct 2024 08:52:18 +0100 Subject: [PATCH] NB: Interim commit - new code is untested at this point Information sent to Speckle is now presented in hiearchical collections: - Root level contains material proxies - Second level is project storeys - Third level is element type Added database and record definitions for attributes including: - Finishes (surface rendering material) - Storeys Element getters for storey and type name Aligned some speckle_type names to Revit (where possible) --- .../Connector.xcodeproj/project.pbxproj | 18 +- .../Connector/ConnectorResource.h | 1 + .../Interface/Browser/Bridge/Send/Send.cpp | 29 +- .../Record/Collection/FinishProxy.cpp | 66 ++++ .../Connector/Record/Collection/FinishProxy.h | 50 ++++ .../Record/Collection/ProjectCollection.cpp | 142 +++++++++ .../Record/Collection/ProjectCollection.h | 88 ++++++ .../Record/Collection/RecordCollection.cpp | 85 ++++-- .../Record/Collection/RecordCollection.h | 87 ++++-- .../Record/Collection/RootCollection.cpp | 34 --- .../Record/Collection/RootCollection.h | 52 ---- SpeckleConnector/RINT/Connector.grc | 1 + SpeckleLib/RINT/Speckle.grc | 1 + .../Speckle/Database/BIMAttributeDatabase.cpp | 143 +++++++++ .../Speckle/Database/BIMAttributeDatabase.h | 95 ++++++ .../Speckle/Database/BIMElementDatabase.h | 6 +- .../Speckle/Database/Content/BIMRecord.h | 27 +- SpeckleLib/Speckle/Database/Content/Record.h | 16 +- .../Speckle/Database/Identity/BIMIndex.h | 25 +- .../Speckle/Database/Identity/BIMLink.cpp | 3 +- .../Speckle/Database/Identity/BIMLink.h | 3 +- .../ArchicadAttributeDBaseEngine.cpp | 282 ++++++++++++++++++ .../Attribute/ArchicadAttributeDBaseEngine.h | 142 +++++++++ .../Element/ArchicadElementDBaseEngine.cpp | 46 +-- .../Element/ArchicadElementDBaseEngine.h | 8 + SpeckleLib/Speckle/Environment/Project.cpp | 2 + SpeckleLib/Speckle/Environment/Project.h | 9 + .../Event/Subscriber/SelectionSubscriber.cpp | 7 +- .../Speckle/Record/Attribute/Attribute.cpp | 103 +++++++ .../Speckle/Record/Attribute/Attribute.h | 120 ++++++++ .../Speckle/Record/Attribute/Finish.cpp | 164 ++++++++++ SpeckleLib/Speckle/Record/Attribute/Finish.h | 128 ++++++++ .../Speckle/Record/Attribute/Storey.cpp | 192 ++++++++++++ SpeckleLib/Speckle/Record/Attribute/Storey.h | 129 ++++++++ SpeckleLib/Speckle/Record/Element/Element.cpp | 47 ++- SpeckleLib/Speckle/Record/Element/Element.h | 16 +- SpeckleLib/Speckle/SpeckleResource.h | 1 + .../SpeckleLib.xcodeproj/project.pbxproj | 56 ++++ 38 files changed, 2214 insertions(+), 210 deletions(-) create mode 100644 SpeckleConnector/Connector/Record/Collection/FinishProxy.cpp create mode 100644 SpeckleConnector/Connector/Record/Collection/FinishProxy.h create mode 100644 SpeckleConnector/Connector/Record/Collection/ProjectCollection.cpp create mode 100644 SpeckleConnector/Connector/Record/Collection/ProjectCollection.h delete mode 100644 SpeckleConnector/Connector/Record/Collection/RootCollection.cpp delete mode 100644 SpeckleConnector/Connector/Record/Collection/RootCollection.h create mode 100644 SpeckleLib/Speckle/Database/BIMAttributeDatabase.cpp create mode 100644 SpeckleLib/Speckle/Database/BIMAttributeDatabase.h create mode 100644 SpeckleLib/Speckle/Database/Storage/ArchicadDBase/Attribute/ArchicadAttributeDBaseEngine.cpp create mode 100644 SpeckleLib/Speckle/Database/Storage/ArchicadDBase/Attribute/ArchicadAttributeDBaseEngine.h create mode 100644 SpeckleLib/Speckle/Record/Attribute/Attribute.cpp create mode 100644 SpeckleLib/Speckle/Record/Attribute/Attribute.h create mode 100644 SpeckleLib/Speckle/Record/Attribute/Finish.cpp create mode 100644 SpeckleLib/Speckle/Record/Attribute/Finish.h create mode 100644 SpeckleLib/Speckle/Record/Attribute/Storey.cpp create mode 100644 SpeckleLib/Speckle/Record/Attribute/Storey.h diff --git a/SpeckleConnector/Connector.xcodeproj/project.pbxproj b/SpeckleConnector/Connector.xcodeproj/project.pbxproj index 74a280d..fc6c93a 100644 --- a/SpeckleConnector/Connector.xcodeproj/project.pbxproj +++ b/SpeckleConnector/Connector.xcodeproj/project.pbxproj @@ -29,8 +29,9 @@ 215F082E2C94C5C000CD343B /* FilterMover.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 215F082C2C94C5C000CD343B /* FilterMover.cpp */; }; 215F08372C95808B00CD343B /* ReceiverModelCard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 215F08362C95808B00CD343B /* ReceiverModelCard.cpp */; }; 215F08462C9633A800CD343B /* EverythingSendFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 215F08452C9633A800CD343B /* EverythingSendFilter.cpp */; }; - 2192460D2CA3469D00CF5703 /* RootCollection.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2192460B2CA3469D00CF5703 /* RootCollection.cpp */; }; + 2192460D2CA3469D00CF5703 /* ProjectCollection.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2192460B2CA3469D00CF5703 /* ProjectCollection.cpp */; }; 219F30422C769283009834E9 /* ConfigTests.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 219F30402C769282009834E9 /* ConfigTests.cpp */; }; + 21A0FB982CB723240023F24E /* FinishProxy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 21A0FB942CB723240023F24E /* FinishProxy.cpp */; }; 21AEF9EB2CAB56E5000B8681 /* SendError.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 21AEF9E32CAB56E5000B8681 /* SendError.cpp */; }; 21AEF9EC2CAB56E5000B8681 /* SendViaBrowserArgs.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 21AEF9E52CAB56E5000B8681 /* SendViaBrowserArgs.cpp */; }; 21AEF9EF2CAB5720000B8681 /* SendObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 21AEF9EE2CAB5720000B8681 /* SendObject.cpp */; }; @@ -305,12 +306,14 @@ 215F08452C9633A800CD343B /* EverythingSendFilter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EverythingSendFilter.cpp; sourceTree = ""; }; 215F084A2C9782F100CD343B /* ArchicadEverythingFilter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ArchicadEverythingFilter.h; sourceTree = ""; }; 2161FD902BF2600C006D9527 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; - 2192460B2CA3469D00CF5703 /* RootCollection.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RootCollection.cpp; sourceTree = ""; }; - 2192460C2CA3469D00CF5703 /* RootCollection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RootCollection.h; sourceTree = ""; }; + 2192460B2CA3469D00CF5703 /* ProjectCollection.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ProjectCollection.cpp; sourceTree = ""; }; + 2192460C2CA3469D00CF5703 /* ProjectCollection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ProjectCollection.h; sourceTree = ""; }; 219388682C4E5DE2002A0180 /* CMakeLists.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CMakeLists.txt; sourceTree = ""; }; 219F30352C768F0A009834E9 /* Connector-AC27-Test.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Connector-AC27-Test.bundle"; sourceTree = BUILT_PRODUCTS_DIR; }; 219F30402C769282009834E9 /* ConfigTests.cpp */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; path = ConfigTests.cpp; sourceTree = ""; }; 219F30432C7693B6009834E9 /* Connector-AC27-Debug.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = "Connector-AC27-Debug.xctestplan"; sourceTree = SOURCE_ROOT; }; + 21A0FB942CB723240023F24E /* FinishProxy.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FinishProxy.cpp; sourceTree = ""; }; + 21A0FB972CB723240023F24E /* FinishProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FinishProxy.h; sourceTree = ""; }; 21AEF9E32CAB56E5000B8681 /* SendError.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SendError.cpp; sourceTree = ""; }; 21AEF9E42CAB56E5000B8681 /* SendError.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SendError.h; sourceTree = ""; }; 21AEF9E52CAB56E5000B8681 /* SendViaBrowserArgs.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SendViaBrowserArgs.cpp; sourceTree = ""; }; @@ -1231,10 +1234,12 @@ 21FF70482CA1A7F400AAD99A /* Collection */ = { isa = PBXGroup; children = ( + 21A0FB942CB723240023F24E /* FinishProxy.cpp */, + 21A0FB972CB723240023F24E /* FinishProxy.h */, 21FF70462CA1A7F400AAD99A /* RecordCollection.cpp */, 21FF70472CA1A7F400AAD99A /* RecordCollection.h */, - 2192460B2CA3469D00CF5703 /* RootCollection.cpp */, - 2192460C2CA3469D00CF5703 /* RootCollection.h */, + 2192460B2CA3469D00CF5703 /* ProjectCollection.cpp */, + 2192460C2CA3469D00CF5703 /* ProjectCollection.h */, ); path = Collection; sourceTree = ""; @@ -1456,11 +1461,12 @@ 21B67CF72C78D4DE00FD64FC /* GetComplexType.cpp in Sources */, 21D0BDDC2C93897B0077E104 /* SenderModelCard.cpp in Sources */, 21B67CAE2C77329800FD64FC /* GetSourceApplicationVersion.cpp in Sources */, + 21A0FB982CB723240023F24E /* FinishProxy.cpp in Sources */, 21B67CC32C77649F00FD64FC /* GetDocumentState.cpp in Sources */, 21D0BD602C89BFEA0077E104 /* SendBridge.cpp in Sources */, 21D0BD972C8F13F30077E104 /* GetSendFilters.cpp in Sources */, 21B67CAC2C77329800FD64FC /* BaseBridge.cpp in Sources */, - 2192460D2CA3469D00CF5703 /* RootCollection.cpp in Sources */, + 2192460D2CA3469D00CF5703 /* ProjectCollection.cpp in Sources */, 21D0BD6A2C8A0DB40077E104 /* GetIsDevMode.cpp in Sources */, 210CC8832C80E6A300610F58 /* TriggerEvent.cpp in Sources */, 21B67CEB2C78D27200FD64FC /* DocumentInfo.cpp in Sources */, diff --git a/SpeckleConnector/Connector/ConnectorResource.h b/SpeckleConnector/Connector/ConnectorResource.h index daec179..dbf6b2c 100755 --- a/SpeckleConnector/Connector/ConnectorResource.h +++ b/SpeckleConnector/Connector/ConnectorResource.h @@ -28,6 +28,7 @@ enum StringResource { enum TitleString { addonNameID = 1, addonDescriptionID, + noStoreyID, }; diff --git a/SpeckleConnector/Connector/Interface/Browser/Bridge/Send/Send.cpp b/SpeckleConnector/Connector/Interface/Browser/Bridge/Send/Send.cpp index 8cd4e53..279fd23 100644 --- a/SpeckleConnector/Connector/Interface/Browser/Bridge/Send/Send.cpp +++ b/SpeckleConnector/Connector/Interface/Browser/Bridge/Send/Send.cpp @@ -7,6 +7,7 @@ #include "Connector/Database/ModelCardDatabase.h" #include "Connector/Interface/Browser/Bridge/Send/Arg/SendError.h" #include "Connector/Interface/Browser/Bridge/Send/Arg/SendViaBrowserArgs.h" +#include "Connector/Record/Collection/ProjectCollection.h" #include "Speckle/Database/AccountDatabase.h" #include "Speckle/Database/Content/BIMRecord.h" #include "Speckle/Interface/Browser/Bridge/BrowserBridge.h" @@ -23,6 +24,7 @@ using namespace speckle::record::element; using namespace active::serialise; using namespace connector::interfac::browser::bridge; +using namespace connector::record; using namespace speckle::database; using namespace speckle::serialise; using namespace speckle::utility; @@ -68,27 +70,20 @@ void Send::run(const String& modelCardID) const { std::make_unique(connector()->getLocalString(errorString, noProjectOpenID), modelCardID)); return; } - //Collect targeted elements here - - - //NB: This is a testing placeholder - - Element::Unique element; - if (auto project = connector::connector()->getActiveProject().lock(); project) { - auto elementDatabase = project->getElementDatabase(); - auto selected = elementDatabase->getSelection(); - for (const auto& link : selected) - if (element = elementDatabase->getElement(link); element) - break; - } - if (!element) { + //Build a collection from the selected elements + auto collection = std::make_unique(project); + auto elementDatabase = project->getElementDatabase(); + auto selected = elementDatabase->getSelection(); + if (selected.empty()) { getBridge()->sendEvent("setModelError", std::make_unique(connector()->getLocalString(errorString, noSelectedModelItemsID), modelCardID)); return; } - - + for (const auto& link : selected) { + if (auto element = elementDatabase->getElement(link); element) + collection->addElement(*element); + } //Send the collected information - auto result = std::make_unique(*modelCard, *account, SendObject{std::move(element)}); //NB: Using a placeholder object for now + auto result = std::make_unique(*modelCard, *account, SendObject{std::move(collection)}); getBridge()->sendEvent("sendByBrowser", std::move(result)); } //Send::run diff --git a/SpeckleConnector/Connector/Record/Collection/FinishProxy.cpp b/SpeckleConnector/Connector/Record/Collection/FinishProxy.cpp new file mode 100644 index 0000000..a4643fc --- /dev/null +++ b/SpeckleConnector/Connector/Record/Collection/FinishProxy.cpp @@ -0,0 +1,66 @@ +#include "Connector/Record/Collection/FinishProxy.h" + +#include "Active/Serialise/Package/Wrapper/ContainerWrap.h" +#include "Active/Serialise/Package/Wrapper/PackageWrap.h" + +#include + +using namespace active::serialise; +using namespace connector::record; +using namespace speckle::utility; + +namespace { + + ///Serialisation fields + enum FieldIndex { + materialID, + linkedMeshID, + }; + + ///Serialisation field IDs + static std::array fieldID = { + Identity{"value"}, + Identity{"objects"}, + }; + +} + +/*-------------------------------------------------------------------- + Fill an inventory with the package items + + inventory: The inventory to receive the package items + + return: True if the package has added items to the inventory + --------------------------------------------------------------------*/ +bool FinishProxy::fillInventory(active::serialise::Inventory& inventory) const { + using enum Entry::Type; + inventory.merge(Inventory{ + { + { fieldID[materialID], materialID, element }, + { fieldID[linkedMeshID], linkedMeshID, m_meshID.size(), std::nullopt }, + }, + }.withType(&typeid(FinishProxy))); + return true; +} //FinishProxy::fillInventory + + +/*-------------------------------------------------------------------- + Get the specified cargo + + item: The inventory item to retrieve + + return: The requested cargo (nullptr on failure) + --------------------------------------------------------------------*/ +Cargo::Unique FinishProxy::getCargo(const active::serialise::Inventory::Item& item) const { + if (item.ownerType != &typeid(FinishProxy)) + return nullptr; + using namespace active::serialise; + switch (item.index) { + case materialID: + return std::make_unique(m_finish); + case linkedMeshID: + return std::make_unique>>(m_meshID); + default: + return nullptr; //Requested an unknown index + } +} //FinishProxy::getCargo diff --git a/SpeckleConnector/Connector/Record/Collection/FinishProxy.h b/SpeckleConnector/Connector/Record/Collection/FinishProxy.h new file mode 100644 index 0000000..19fba4a --- /dev/null +++ b/SpeckleConnector/Connector/Record/Collection/FinishProxy.h @@ -0,0 +1,50 @@ +#ifndef CONNECTOR_RECORD_COLLECTION_MATERIAL_PROXY +#define CONNECTOR_RECORD_COLLECTION_MATERIAL_PROXY + +#include "Active/Serialise/Package/Package.h" +#include "Speckle/Record/Attribute/Finish.h" +#include "Speckle/Utility/String.h" + +namespace connector::record { + + /*! + A proxy record binding a surface finishes to meshes + */ + class FinishProxy : public active::serialise::Package { + public: + + // MARK: - Constructors + + /*! + Constructor + @param finish The proxy surface finish + @param meshID The list of mesh IDs the finish is applied to + */ + FinishProxy(const speckle::record::attribute::Finish& finish, const std::unordered_set& meshID) : + m_finish{finish} { std::copy(meshID.begin(), meshID.end(), std::back_inserter(m_meshID)); } + + // MARK: - Serialisation + + /*! + Fill an inventory with the package items + @param inventory The inventory to receive the package items + @return True if the package has added items to the inventory + */ + bool fillInventory(active::serialise::Inventory& inventory) const override; + /*! + Get the specified cargo + @param item The inventory item to retrieve + @return The requested cargo (nullptr on failure) + */ + Cargo::Unique getCargo(const active::serialise::Inventory::Item& item) const override; + + private: + ///The proxy surface finish + speckle::record::attribute::Finish m_finish; + ///The list of mesh IDs the finish is applied to + std::vector m_meshID; + }; + +} + +#endif //CONNECTOR_RECORD_COLLECTION_MATERIAL_PROXY diff --git a/SpeckleConnector/Connector/Record/Collection/ProjectCollection.cpp b/SpeckleConnector/Connector/Record/Collection/ProjectCollection.cpp new file mode 100644 index 0000000..368c3a9 --- /dev/null +++ b/SpeckleConnector/Connector/Record/Collection/ProjectCollection.cpp @@ -0,0 +1,142 @@ +#include "Connector/Record/Collection/ProjectCollection.h" + +#include "Active/Serialise/CargoHold.h" +#include "Active/Serialise/Item/Wrapper/ValueWrap.h" +#include "Active/Serialise/Package/Wrapper/PackageWrap.h" +#include "Connector/Connector.h" +#include "Connector/ConnectorResource.h" +#include "Connector/Record/Collection/FinishProxy.h" +#include "Speckle/Database/BIMAttributeDatabase.h" +#include "Speckle/Database/BIMElementDatabase.h" +#include "Speckle/Record/Element/Element.h" + +using namespace active::serialise; +using namespace connector::record; +using namespace speckle::database; +using namespace speckle::record::attribute; +using namespace speckle::utility; + +namespace { + + ///Serialisation fields + enum FieldIndex { + finishProxyID, + }; + + ///Serialisation field IDs + static std::array fieldID = { + Identity{"renderMaterialProxies"}, + }; + + using WrappedProxy = CargoHold; + +} + +/*-------------------------------------------------------------------- + Add an element to the collection hierarchy + + index The index of the element to add + + return: True if the element was added (false typically means the element already exists) + --------------------------------------------------------------------*/ +bool ProjectCollection::addElement(const speckle::database::BIMIndex& index) { + //Lookup the element in the element database of the active project + auto elementDbase = m_project->getElementDatabase(); + if (elementDbase == nullptr) + return false; + if (auto element = elementDbase->getElement(index); element) { + addElement(*element); //Add the element to the collection hierarchy + return true; + } + return false; +} //ProjectCollection::addElement + + +/*-------------------------------------------------------------------- + Add an element to the collection hierarchy + + element: The element to add + + return: True if the element was added (false typically means the element already exists) + --------------------------------------------------------------------*/ +bool ProjectCollection::addElement(const speckle::record::element::Element& element) { + std::vector collectionNames; + //The first collection hierarchy level is the element storey/level + auto storey = element.getStorey(); + collectionNames.emplace_back(storey ? storey->getName() : connector()->getLocalString(titleString, noStoreyID)); + //The next level is the name of the element type + collectionNames.emplace_back(element.getTypeName()); + //Add any future levels here as required + RecordCollection* collection = this; + for (const auto& childName : collectionNames) + collection = collection->getChild(childName); + return collection->addIndex(BIMIndex{element.getBIMID(), element.getTableID()}); +} //ProjectCollection::addElement + + +/*-------------------------------------------------------------------- + Add a material proxy record to the collection + + materialIndex: The index of the material to add + objectID: The object the material is applied to + + return: True if the material proxy was added (false typically means the record already exists) + --------------------------------------------------------------------*/ +bool ProjectCollection::addMaterialProxy(const speckle::database::BIMIndex& materialIndex, const speckle::database::BIMRecordID& objectID) { + auto iter = m_finishProxies.find(materialIndex); + if (iter == m_finishProxies.end()) + iter = m_finishProxies.insert({materialIndex, {}}).first; + return iter->second.insert(objectID).second; +} //ProjectCollection::addMaterialProxy + + +/*-------------------------------------------------------------------- + Fill an inventory with the package items + + inventory: The inventory to receive the package items + + return: True if the package has added items to the inventory + --------------------------------------------------------------------*/ +bool ProjectCollection::fillInventory(active::serialise::Inventory& inventory) const { + using enum Entry::Type; + base::fillInventory(inventory); + inventory.merge(Inventory{ + { + { Identity{fieldID[finishProxyID]}, finishProxyID, m_finishProxies.size(), std::nullopt }, + }, + }.withType(&typeid(ProjectCollection))); + return true; +} //ProjectCollection::fillInventory + + +/*-------------------------------------------------------------------- + Get the specified cargo + + item: The inventory item to retrieve + + return: The requested cargo (nullptr on failure) + --------------------------------------------------------------------*/ +Cargo::Unique ProjectCollection::getCargo(const Inventory::Item& item) const { + if (item.ownerType != &typeid(ProjectCollection)) + return base::getCargo(item); + using namespace active::serialise; + //TODO: This is only currently coded to write collection content - reading can be added as required in future + switch (item.index) { + case finishProxyID: { + if (item.available < m_finishProxies.size()) { + auto iter = m_finishProxies.begin(); + std::advance(iter, item.available); + if (auto attribute = m_project->getAttributeDatabase()->getAttribute(iter->first, iter->first.tableID); attribute) { + if (auto finish = dynamic_cast(attribute.get()); finish != nullptr) { + auto proxy = std::make_unique(*finish, iter->second); + return std::make_unique(std::move(proxy)); + } + } + } + break; + } + default: + break; + } + return nullptr; //Requested an unknown index +} //ProjectCollection::getCargo diff --git a/SpeckleConnector/Connector/Record/Collection/ProjectCollection.h b/SpeckleConnector/Connector/Record/Collection/ProjectCollection.h new file mode 100644 index 0000000..33b8931 --- /dev/null +++ b/SpeckleConnector/Connector/Record/Collection/ProjectCollection.h @@ -0,0 +1,88 @@ +#ifndef CONNECTOR_RECORD_ROOT_COLLECTiON +#define CONNECTOR_RECORD_ROOT_COLLECTiON + +#include "Connector/Record/Collection/RecordCollection.h" + +#include + +namespace speckle::record::element { + class Element; +} + +namespace connector::record { + + /*! + Root collection for sending a project model to a Speckle server + + Additional information is anticipated at the root level that will not apply at any other level in the container hierarchy, e.g.: + - Classification hierarchy + - Layers + - Other attributes, e.g. materials + Add all this supplementary data to the root container as required + */ + class ProjectCollection : public RecordCollection { + public: + + // MARK: - Types + + using base = RecordCollection; + + // MARK: - Constructors + + /*! + Constructor + @param project The source project + */ + ProjectCollection(speckle::environment::Project::Shared project) : base{project->getInfo().name, project} {} + + using base::base; + + // MARK: - Functions (const) + + // MARK: - Functions (mutating) + + /*! + Add an element to the collection hierarchy + @param index The index of the element to add + @return True if the element was added (false typically means the element already exists) + */ + bool addElement(const speckle::database::BIMIndex& index); + /*! + Add an element to the collection hierarchy + @param element The element to add + @return True if the element was added (false typically means the element already exists) + */ + bool addElement(const speckle::record::element::Element& element); + /*! + Add a material proxy record to the collection + @param materialIndex The index of the material to add + @param objectID The object the material is applied to + @return True if the material proxy was added (false typically means the record already exists) + */ + bool addMaterialProxy(const speckle::database::BIMIndex& materialIndex, const speckle::database::BIMRecordID& objectID); + + // MARK: - Serialisation + + /*! + Fill an inventory with the package items + @param inventory The inventory to receive the package items + @return True if the package has added items to the inventory + */ + bool fillInventory(active::serialise::Inventory& inventory) const override; + /*! + Get the specified cargo + @param item The inventory item to retrieve + @return The requested cargo (nullptr on failure) + */ + active::serialise::Cargo::Unique getCargo(const active::serialise::Inventory::Item& item) const override; + + private: + using FinishProxies = std::unordered_map>; + + ///Finish proxies accumulated from meshes generated from the collection elements + FinishProxies m_finishProxies; + }; + +} + +#endif //CONNECTOR_RECORD_ROOT_COLLECTiON diff --git a/SpeckleConnector/Connector/Record/Collection/RecordCollection.cpp b/SpeckleConnector/Connector/Record/Collection/RecordCollection.cpp index a6f0d5e..dcbbf51 100644 --- a/SpeckleConnector/Connector/Record/Collection/RecordCollection.cpp +++ b/SpeckleConnector/Connector/Record/Collection/RecordCollection.cpp @@ -1,11 +1,15 @@ #include "Connector/Record/Collection/RecordCollection.h" +#include "Active/Serialise/CargoHold.h" #include "Active/Serialise/Item/Wrapper/ValueWrap.h" #include "Active/Serialise/Package/Wrapper/PackageWrap.h" +#include "Speckle/Database/BIMElementDatabase.h" using namespace active::serialise; using namespace connector::record; using namespace speckle::database; +using namespace speckle::environment; +using namespace speckle::record::element; using namespace speckle::utility; #include @@ -14,20 +18,55 @@ namespace { ///Serialisation fields enum FieldIndex { - nameID, elementID, - childrenID, }; ///Serialisation field IDs static std::array fieldID = { - Identity{"name"}, - Identity{"element"}, - Identity{"child"}, + Identity{"elements"}, }; + using WrappedElement = CargoHold; + } +/*-------------------------------------------------------------------- + Constructor + + name: The collection name + project: The source project + --------------------------------------------------------------------*/ +RecordCollection::RecordCollection(const speckle::utility::String& name, Project::Shared project) : m_name{name}, m_project{project} { +} //RecordCollection::RecordCollection + + +/*-------------------------------------------------------------------- + Get a child collection by name (adding if missing) + + name: The child name + + return: A pointer to the requested child (nullptr on failure, caller does not take ownership) + --------------------------------------------------------------------*/ +RecordCollection* RecordCollection::getChild(const speckle::utility::String& name) { + //Return an existing child if possible + if (auto iter = m_children.find(name); iter != m_children.end()) + return &iter->second; + //Otherwise insert and return a new collection with the requested name + return &m_children.insert({name, RecordCollection{name, m_project}}).first->second; +} //RecordCollection::getChild + + +/*-------------------------------------------------------------------- + Add an index to the collection + + index: The index to add + + return: True if the index was added (false typically means the index already exists) + --------------------------------------------------------------------*/ +bool RecordCollection::addIndex(const speckle::database::BIMIndex& index) { + return m_indices.insert(index).second; +} //RecordCollection::addIndex + /*-------------------------------------------------------------------- Fill an inventory with the package items @@ -36,13 +75,12 @@ namespace { return: True if the package has added items to the inventory --------------------------------------------------------------------*/ -bool RecordCollection::fillInventory(Inventory& inventory) const { +bool RecordCollection::fillInventory(active::serialise::Inventory& inventory) const { using enum Entry::Type; + base::fillInventory(inventory); inventory.merge(Inventory{ { - { fieldID[nameID], nameID, element }, - { fieldID[elementID], elementID, base::size(), std::nullopt, !base::empty() }, - { fieldID[childrenID], childrenID, m_children.size(), std::nullopt, !m_children.empty() }, + { Identity{fieldID[elementID]}, elementID, m_children.size() + m_indices.size(), std::nullopt }, }, }.withType(&typeid(RecordCollection))); return true; @@ -58,18 +96,27 @@ bool RecordCollection::fillInventory(Inventory& inventory) const { --------------------------------------------------------------------*/ Cargo::Unique RecordCollection::getCargo(const Inventory::Item& item) const { if (item.ownerType != &typeid(RecordCollection)) - return nullptr; + return base::getCargo(item); using namespace active::serialise; + //TODO: This is only currently coded to write collection content - reading can be added as required in future switch (item.index) { - case nameID: - return std::make_unique(m_name); - case elementID: - return nullptr; //TODO: Implement - need to interrogate BIM database for element and return as cargo - case childrenID: - if (item.available < m_children.size()) - return std::make_unique(m_children[item.available]); - return nullptr; + case elementID: { + if (item.available < m_children.size()) { + auto iter = m_children.begin(); + std::advance(iter, item.available); + return std::make_unique(iter->second); + } + auto index = item.available - m_children.size(); + if (index < m_indices.size()) { + auto iter = m_indices.begin(); + std::advance(iter, index); + if (auto element = m_project->getElementDatabase()->getElement(*iter, iter->tableID); element) + return std::make_unique(std::move(element)); + } + break; + } default: - return nullptr; //Requested an unknown index + break; } + return nullptr; //Requested an unknown index } //RecordCollection::getCargo diff --git a/SpeckleConnector/Connector/Record/Collection/RecordCollection.h b/SpeckleConnector/Connector/Record/Collection/RecordCollection.h index 0d19b33..b7be102 100644 --- a/SpeckleConnector/Connector/Record/Collection/RecordCollection.h +++ b/SpeckleConnector/Connector/Record/Collection/RecordCollection.h @@ -1,13 +1,18 @@ #ifndef CONNECTOR_RECORD_RECORD_COLLECTiON #define CONNECTOR_RECORD_RECORD_COLLECTiON -#include "Active/Container/Vector.h" -#include "Active/Serialise/Package/Package.h" -#include "Speckle/Database/Identity/RecordID.h" +#include "Speckle/Database/Content/Record.h" +#include "Speckle/Database/Identity/BIMIndex.h" +#include "Speckle/Environment/Project.h" #include "Speckle/Utility/String.h" +#include +#include + namespace connector::record { + class ProjectCollection; + /*! Container for a collection of elements (and potentially tables of associated attributes) for Speckle commits @@ -26,43 +31,34 @@ namespace connector::record { Note that the serialisation is currently implemented for sending only. Receive can be added as required */ - class RecordCollection : public std::vector, public active::serialise::Package { + class RecordCollection : public speckle::database::Record { public: - // MARK: - Types - - using base = std::vector; - using Children = std::vector; - // MARK: - Constructors - using base::base; + /*! + Destructor + */ + ~RecordCollection() {} // MARK: - Functions (const) + /*! + Get the speckle type identifier + @return The speckle type (relevant objects should override as required, but "Base" is still considered a type on its own) + */ + speckle::utility::String getSpeckleType() const override { return "Speckle.Core.Models.Collections.Collection"; } /*! Get the container name @return The container name */ const speckle::utility::String& getName() const { return m_name; } /*! - Get the child collections - @return The child collections nested under this collection + Find a child by name + @param name The required child name + @return A pointer to the requested child (nullptr if not found) */ - const Children& getChildren() const; - - // MARK: - Functions (mutating) - - /*! - Set the container name - @param name The container name - */ - void setName(const speckle::utility::String& name) { m_name = name; } - /*! - Add a child collection - @param child The child collection to add - */ - void addChild(RecordCollection&& child); + RecordCollection* findChild(const speckle::utility::String& name) const; // MARK: - Serialisation @@ -70,20 +66,51 @@ namespace connector::record { Fill an inventory with the package items @param inventory The inventory to receive the package items @return True if the package has added items to the inventory - */ + */ bool fillInventory(active::serialise::Inventory& inventory) const override; /*! Get the specified cargo @param item The inventory item to retrieve @return The requested cargo (nullptr on failure) - */ + */ active::serialise::Cargo::Unique getCargo(const active::serialise::Inventory::Item& item) const override; private: - ///Child nodes of this collection - Children m_children; + friend ProjectCollection; + + // MARK: - Types + + using Indices = std::unordered_set; + using Children = std::unordered_map; + + /*! + Constructor + @param name The collection name + @param project The source project + */ + RecordCollection(const speckle::utility::String& name, speckle::environment::Project::Shared project); + + /*! + Get a child collection by name (adding if missing) + @param name The child name + @return A pointer to the requested child (nullptr on failure, caller does not take ownership) + */ + RecordCollection* getChild(const speckle::utility::String& name); + /*! + Add an index to the collection + @param index The index to add + @return True if the index was added (false typically means the index already exists) + */ + bool addIndex(const speckle::database::BIMIndex& index); + + ///The source project for the collection + speckle::environment::Project::Shared m_project; ///The collection name speckle::utility::String m_name; + ///Child nodes of this collection + Children m_children; + ///Indices of records in this collection + Indices m_indices; }; } diff --git a/SpeckleConnector/Connector/Record/Collection/RootCollection.cpp b/SpeckleConnector/Connector/Record/Collection/RootCollection.cpp deleted file mode 100644 index 90be329..0000000 --- a/SpeckleConnector/Connector/Record/Collection/RootCollection.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include "Connector/Record/Collection/RootCollection.h" - -#include "Active/Serialise/Item/Wrapper/ValueWrap.h" -#include "Active/Serialise/Package/Wrapper/PackageWrap.h" - -using namespace active::serialise; -using namespace connector::record; -using namespace speckle::database; -using namespace speckle::utility; - -/*-------------------------------------------------------------------- - Fill an inventory with the package items - - inventory: The inventory to receive the package items - - return: True if the package has added items to the inventory - --------------------------------------------------------------------*/ -bool RootCollection::fillInventory(Inventory& inventory) const { - //Extend with supplementary data as required - return base::fillInventory(inventory); -} //RootCollection::fillInventory - - -/*-------------------------------------------------------------------- - Get the specified cargo - - item: The inventory item to retrieve - - return: The requested cargo (nullptr on failure) - --------------------------------------------------------------------*/ -Cargo::Unique RootCollection::getCargo(const Inventory::Item& item) const { - //Extend with supplementary data as required - return base::getCargo(item); -} //RootCollection::getCargo diff --git a/SpeckleConnector/Connector/Record/Collection/RootCollection.h b/SpeckleConnector/Connector/Record/Collection/RootCollection.h deleted file mode 100644 index f12a5b0..0000000 --- a/SpeckleConnector/Connector/Record/Collection/RootCollection.h +++ /dev/null @@ -1,52 +0,0 @@ -#ifndef CONNECTOR_RECORD_ROOT_COLLECTiON -#define CONNECTOR_RECORD_ROOT_COLLECTiON - -#include "Connector/Record/Collection/RecordCollection.h" - -namespace connector::record { - - /*! - Root container for sending model data to a Speckle server - - Additional information is anticipated at the root level that will not apply at any other level in the container hierarchy, e.g.: - - Classification hierarchy - - Layers - - Other attributes, e.g. materials - Add all this supplementary data to the root container as required - */ - class RootCollection : public RecordCollection { - public: - - // MARK: - Types - - using base = RecordCollection; - - // MARK: - Constructors - - using base::base; - - // MARK: - Functions (const) - - - // MARK: - Functions (mutating) - - - // MARK: - Serialisation - - /*! - Fill an inventory with the package items - @param inventory The inventory to receive the package items - @return True if the package has added items to the inventory - */ - bool fillInventory(active::serialise::Inventory& inventory) const override; - /*! - Get the specified cargo - @param item The inventory item to retrieve - @return The requested cargo (nullptr on failure) - */ - active::serialise::Cargo::Unique getCargo(const active::serialise::Inventory::Item& item) const override; - }; - -} - -#endif //CONNECTOR_RECORD_ROOT_COLLECTiON diff --git a/SpeckleConnector/RINT/Connector.grc b/SpeckleConnector/RINT/Connector.grc index c1e3741..cfc3237 100644 --- a/SpeckleConnector/RINT/Connector.grc +++ b/SpeckleConnector/RINT/Connector.grc @@ -1,6 +1,7 @@ 'STR#' 32600 "Title strings" { /* [ 1] */ "Speckle Connector" /* [ 2] */ "Connector to share model content with Speckle" +/* [ 3] */ "No level" } 'STR#' 32604 "Error strings" { diff --git a/SpeckleLib/RINT/Speckle.grc b/SpeckleLib/RINT/Speckle.grc index 552987b..43c993f 100644 --- a/SpeckleLib/RINT/Speckle.grc +++ b/SpeckleLib/RINT/Speckle.grc @@ -1,3 +1,4 @@ 'STR#' 32700 "Speckle Title strings" { /* [ 1] */ "Untitled" +/* [ 2] */ "Unknown" } diff --git a/SpeckleLib/Speckle/Database/BIMAttributeDatabase.cpp b/SpeckleLib/Speckle/Database/BIMAttributeDatabase.cpp new file mode 100644 index 0000000..f7de91d --- /dev/null +++ b/SpeckleLib/Speckle/Database/BIMAttributeDatabase.cpp @@ -0,0 +1,143 @@ +#include "Speckle/Database/BIMAttributeDatabase.h" + +#include "Active/Database/Storage/Storage.h" +#include "Active/Serialise/UnboxedTransport.h" +#include "Speckle/Database/Identity/RecordID.h" +#include "Speckle/Database/Storage/ArchicadDBase/Attribute/ArchicadAttributeDBaseEngine.h" +#include "Speckle/Record/Attribute/Attribute.h" + +#include + +using namespace active::container; +using namespace active::database; +using namespace active::event; +using namespace active::serialise; +using namespace speckle::database; +using namespace speckle::record; +using namespace speckle::record::attribute; +using namespace speckle::database; +using namespace speckle::utility; + +namespace speckle::database { + + ///Define other platform engines here as required +#ifdef ARCHICAD + using AttributeDatabaseEngine = ArchicadAttributeDBaseEngine; +#endif + + ///Attribute database engine declaration + class BIMAttributeDatabase::Engine : public AttributeDatabaseEngine { + using base = ArchicadAttributeDBaseEngine; + using base::base; + }; + + ///Attribute database storage declaration + class BIMAttributeDatabase::Store : public Storage { + using base = Storage; + using base::base; + }; + +} + +namespace { + + ///The database storage identifier for attributes + const char* attributeDBaseName = "speckle::database::BIMAttributeDatabase"; + ///The primary model table, e.g. floor plan in Archicad + const char* modelTableName = "Model"; + +} + +/*-------------------------------------------------------------------- + Constructor + --------------------------------------------------------------------*/ +BIMAttributeDatabase::BIMAttributeDatabase() { + m_engine = std::make_shared(attributeDBaseName, + //Schema + DBaseSchema{active::utility::String{attributeDBaseName}, + //Tables + { + //Model attribute table + { + modelTableName, 0, 0, {} //The primary model. Additonal tables could be linked to other drawings/layouts in future + } + } + } + ); + m_store = std::make_shared(m_engine); +} //BIMAttributeDatabase::BIMAttributeDatabase + + +/*-------------------------------------------------------------------- + Destructor + --------------------------------------------------------------------*/ +BIMAttributeDatabase::~BIMAttributeDatabase() {} + + +/*-------------------------------------------------------------------- + Get a specified attribute + + attributeID: The ID of the target attribute + + return: The requested attribute (nullptr on failure) + --------------------------------------------------------------------*/ +Attribute::Unique BIMAttributeDatabase::getAttribute(const BIMRecordID& attributeID, std::optional tableID, + std::optional documentID) const { + return m_engine->getObject(attributeID, tableID, documentID); +} //BIMAttributeDatabase::getAttribute + + +/*-------------------------------------------------------------------- + Get all attributes + + return: All the attributes + --------------------------------------------------------------------*/ +Vector BIMAttributeDatabase::getAttributes() const { + return m_store->getObjects(); +} //BIMAttributeDatabase::getAttributes + + +/*-------------------------------------------------------------------- + Write an attribute to storage + + attribute: The attribute to write + --------------------------------------------------------------------*/ +void BIMAttributeDatabase::write(const Attribute& attribute) const { + m_store->write(attribute); +} //BIMAttributeDatabase::write + + +/*-------------------------------------------------------------------- + Erase an attribute + + attributeID: The ID of the attribute to erase + --------------------------------------------------------------------*/ +void BIMAttributeDatabase::erase(const Guid& attributeID) const { + m_store->erase(attributeID); +} //BIMAttributeDatabase::erase + + +#ifdef ARCHICAD +/*-------------------------------------------------------------------- + Get attribute data direct from the AC API. For internal use - avoid direct use + + link: A link to the required attribute + + return: The AC API attribute data + --------------------------------------------------------------------*/ +std::optional BIMAttributeDatabase::getAPIData(const BIMLink& link) const { + return m_engine->getAPIData(link); +} //BIMAttributeDatabase::getAPIData + + +/*-------------------------------------------------------------------- + Get storey data direct from the AC API. For internal use - avoid direct use + + link: A link to the required storey + + return: The AC API storey data + --------------------------------------------------------------------*/ +std::optional BIMAttributeDatabase::getAPIStorey(const BIMLink& link) const { + return m_engine->getAPIStorey(link); +} //BIMAttributeDatabase::getAPIData +#endif diff --git a/SpeckleLib/Speckle/Database/BIMAttributeDatabase.h b/SpeckleLib/Speckle/Database/BIMAttributeDatabase.h new file mode 100644 index 0000000..4ff21c8 --- /dev/null +++ b/SpeckleLib/Speckle/Database/BIMAttributeDatabase.h @@ -0,0 +1,95 @@ +#ifndef CONNECTOR_DATABASE_BIM_ATTRIBUTE_DATABASE +#define CONNECTOR_DATABASE_BIM_ATTRIBUTE_DATABASE + +#include "Speckle/Database/Identity/BIMLink.h" +#include "Speckle/Record/Attribute/Attribute.h" +#include "Speckle/Utility/Guid.h" + +namespace active::event { + class Subscriber; +} + +namespace speckle::database { + + /*! + Database of model attributes relating to a specific project + */ + class BIMAttributeDatabase { + public: + + // MARK: - Constructors + + /*! + Constructor + */ + BIMAttributeDatabase(); + BIMAttributeDatabase(const BIMAttributeDatabase&) = delete; + /*! + Destructor + */ + ~BIMAttributeDatabase(); + + // MARK: - Functions (const) + + /*! + Get the current user attribute selection + @return A list of selected attribute IDs + */ + BIMLinkList getSelection() const; + /*! + Get a specified attribute + @param attributeID The ID of the target attribute + @param tableID Optional table ID (defaults to the floor plan) + @param documentID Optional document ID (when the object is bound to a specific document) + @return The requested attribute (nullptr on failure) + */ + record::attribute::Attribute::Unique getAttribute(const BIMRecordID& attributeID, std::optional tableID = std::nullopt, + std::optional documentID = std::nullopt) const; + /*! + Get a specified attribute + @param link A link to the target attribute + @return The requested attribute (nullptr on failure) + */ + record::attribute::Attribute::Unique getAttribute(const BIMLink& link) const { return getAttribute(link, link.tableID, link.docID); } + /*! + Get all model attributes + @return All the attributes + */ + active::container::Vector getAttributes() const; + /*! + Write an attribute to storage + @param attribute The attribute to write + */ + void write(const record::attribute::Attribute& attribute) const; + /*! + Erase an attribute + @param attributeID The ID of the attribute to erase + */ + void erase(const speckle::utility::Guid& attributeID) const; + +#ifdef ARCHICAD + /*! + Get attribute data direct from the AC API. For internal use - avoid direct use + @param link A link to the required attribute + @return The AC API attribute data + */ + std::optional getAPIData(const BIMLink& link) const; + /*! + Get storey data direct from the AC API. For internal use - avoid direct use + @param link A link to the required storey + @return The AC API storey data + */ + std::optional getAPIStorey(const BIMLink& link) const; +#endif + + private: + class Engine; + class Store; + ///Model attribute database storage + std::shared_ptr m_engine; + std::shared_ptr m_store; + }; + +} + +#endif //CONNECTOR_DATABASE_BIM_ATTRIBUTE_DATABASE diff --git a/SpeckleLib/Speckle/Database/BIMElementDatabase.h b/SpeckleLib/Speckle/Database/BIMElementDatabase.h index bcb990b..27aa3db 100644 --- a/SpeckleLib/Speckle/Database/BIMElementDatabase.h +++ b/SpeckleLib/Speckle/Database/BIMElementDatabase.h @@ -1,5 +1,5 @@ -#ifndef CONNECTOR_DATABASE_BIM_DATABASE -#define CONNECTOR_DATABASE_BIM_DATABASE +#ifndef CONNECTOR_DATABASE_BIM_ELEMENT_DATABASE +#define CONNECTOR_DATABASE_BIM_ELEMENT_DATABASE #include "Speckle/Database/Identity/BIMLink.h" #include "Speckle/Record/Element/Element.h" @@ -77,4 +77,4 @@ namespace speckle::database { } -#endif //CONNECTOR_DATABASE_BIM_DATABASE +#endif //CONNECTOR_DATABASE_BIM_ELEMENT_DATABASE diff --git a/SpeckleLib/Speckle/Database/Content/BIMRecord.h b/SpeckleLib/Speckle/Database/Content/BIMRecord.h index dcbc4c4..8bf5cfc 100644 --- a/SpeckleLib/Speckle/Database/Content/BIMRecord.h +++ b/SpeckleLib/Speckle/Database/Content/BIMRecord.h @@ -3,7 +3,7 @@ #include "Active/Setting/Values/Measurement/Units/LengthUnit.h" #include "Speckle/Database/Content/Record.h" -#include "Speckle/Database/Identity/Link.h" +#include "Speckle/Database/Identity/BIMLink.h" #include "Speckle/Database/Identity/BIMRecordID.h" namespace speckle::database { @@ -34,10 +34,12 @@ namespace speckle::database { /*! Constructor @param ID The record ID - @param unit The recordc unit type + @param tableID The parent table ID + @param unit The record unit type */ - BIMRecord(speckle::utility::Guid ID, active::measure::LengthType unit = active::measure::LengthType::metre) : - base{}, m_applicationID{ID}, m_unit{unit} {} + BIMRecord(const speckle::utility::Guid& ID, const speckle::utility::Guid& tableID, + active::measure::LengthType unit = active::measure::LengthType::metre) : + base{}, m_applicationID{ID}, m_applicationTableID{tableID}, m_unit{unit} {} /*! Destructor */ @@ -50,6 +52,16 @@ namespace speckle::database { @return The BIM application ID */ BIMRecordID getBIMID() const { return m_applicationID; } + /*! + Get the BIM application parent table ID + @return The BIM table ID + */ + BIMRecordID getTableID() const { return m_applicationTableID; } + /*! + Get a link to the BIM record + @return The BIM record link + */ + BIMLink getBIMLink() const { return BIMLink{ {m_applicationID, m_applicationTableID} }; } // MARK: - Functions (mutating) @@ -58,6 +70,11 @@ namespace speckle::database { @param ID The BIM application ID */ void setBIMID(const BIMRecordID& ID) { m_applicationID = ID; } + /*! + Set the BIM application parent table ID + @param tableID The BIM table ID + */ + void setTableID(const BIMRecordID& tableID) { m_applicationTableID = tableID; } // MARK: - Serialisation @@ -81,6 +98,8 @@ namespace speckle::database { private: ///The BIM application record ID BIMRecordID m_applicationID; + ///The BIM application parent table ID + BIMRecordID m_applicationTableID; ///The BIM record unit of length measurement std::optional m_unit = active::measure::LengthType::metre; }; diff --git a/SpeckleLib/Speckle/Database/Content/Record.h b/SpeckleLib/Speckle/Database/Content/Record.h index ab22bd7..6a1adfd 100644 --- a/SpeckleLib/Speckle/Database/Content/Record.h +++ b/SpeckleLib/Speckle/Database/Content/Record.h @@ -51,16 +51,16 @@ namespace speckle::database { // MARK: - Serialisation /*! - Fill an inventory with the package items - @param inventory The inventory to receive the package items - @return True if the package has added items to the inventory - */ + Fill an inventory with the package items + @param inventory The inventory to receive the package items + @return True if the package has added items to the inventory + */ bool fillInventory(active::serialise::Inventory& inventory) const override; /*! - Get the specified cargo - @param item The inventory item to retrieve - @return The requested cargo (nullptr on failure) - */ + Get the specified cargo + @param item The inventory item to retrieve + @return The requested cargo (nullptr on failure) + */ active::serialise::Cargo::Unique getCargo(const active::serialise::Inventory::Item& item) const override; /*! Set to the default package content diff --git a/SpeckleLib/Speckle/Database/Identity/BIMIndex.h b/SpeckleLib/Speckle/Database/Identity/BIMIndex.h index ff0a09f..89abdbc 100644 --- a/SpeckleLib/Speckle/Database/Identity/BIMIndex.h +++ b/SpeckleLib/Speckle/Database/Identity/BIMIndex.h @@ -2,7 +2,7 @@ #define SPECKLE_DATABASE_BIM_INDEX #include "Active/Database/Identity/Index.h" -#include "Speckle/Database/Identity/RecordID.h" +#include "Speckle/Database/Identity/BIMRecordID.h" namespace speckle::database { @@ -13,25 +13,30 @@ namespace speckle::database { this is typically a guid, for Revit a string and for Vectorworks a handle. Note that this index is not necessarily persistent between sessions. */ - class BIMIndex : public active::database::Index { + class BIMIndex : public active::database::Index { public: // MARK: - Types - using base = active::database::Index; + using base = active::database::Index; // MARK: - Constructors using base::base; - - // MARK: - Public variables - - //The table identifier - BIMTableID table; - //The document identifier - BIMDocID document; }; } + + ///Hashing for BIMIndex class, e.g. to use as a key in unordered_map +template<> +struct std::hash { + std::size_t operator() (const speckle::database::BIMIndex& index) const { + std::size_t h1 = std::hash{}(index); + std::size_t h2 = std::hash{}(index.tableID); + return h1 ^ (h2 << 1); + } +}; + + #endif //SPECKLE_DATABASE_BIM_INDEX diff --git a/SpeckleLib/Speckle/Database/Identity/BIMLink.cpp b/SpeckleLib/Speckle/Database/Identity/BIMLink.cpp index 4c1ab7f..af20fb4 100644 --- a/SpeckleLib/Speckle/Database/Identity/BIMLink.cpp +++ b/SpeckleLib/Speckle/Database/Identity/BIMLink.cpp @@ -8,8 +8,9 @@ using namespace speckle::utility; Constructor selected: Information about a selected Archicad element + tableID: The ID of the parent table --------------------------------------------------------------------*/ -BIMLink::BIMLink(const API_Neig& selected) : base{Guid{selected.guid}} { +BIMLink::BIMLink(const API_Neig& selected, const BIMRecordID& tableID) : base{Guid{selected.guid}, tableID} { //More info should be extracted from API_Neig in future (as required) - extract into link settings, e.g. selection target etc } //Link::Link #endif diff --git a/SpeckleLib/Speckle/Database/Identity/BIMLink.h b/SpeckleLib/Speckle/Database/Identity/BIMLink.h index a78f56e..08ec63e 100644 --- a/SpeckleLib/Speckle/Database/Identity/BIMLink.h +++ b/SpeckleLib/Speckle/Database/Identity/BIMLink.h @@ -34,8 +34,9 @@ namespace speckle::database { /*! Constructor @param selected Information about a selected Archicad element + @param tableID The ID of the parent table */ - BIMLink(const API_Neig& selected); + BIMLink(const API_Neig& selected, const BIMRecordID& tableID); #endif }; diff --git a/SpeckleLib/Speckle/Database/Storage/ArchicadDBase/Attribute/ArchicadAttributeDBaseEngine.cpp b/SpeckleLib/Speckle/Database/Storage/ArchicadDBase/Attribute/ArchicadAttributeDBaseEngine.cpp new file mode 100644 index 0000000..2da5214 --- /dev/null +++ b/SpeckleLib/Speckle/Database/Storage/ArchicadDBase/Attribute/ArchicadAttributeDBaseEngine.cpp @@ -0,0 +1,282 @@ +#include "Speckle/Database/Storage/ArchicadDBase/Attribute/ArchicadAttributeDBaseEngine.h" + +#ifdef ARCHICAD + +#include "Active/Utility/Defer.h" +#include "Active/Utility/Memory.h" +#include "Active/Utility/String.h" +#include "Speckle/Record/Attribute/Finish.h" +#include "Speckle/Record/Attribute/Storey.h" +#include "Speckle/Database/Identity/BIMLink.h" +#include "Speckle/Environment/Addon.h" +#include "Speckle/Environment/Project.h" +#include "Speckle/Event/Type/DocStoreMergeEvent.h" +#include "Speckle/Event/Type/ProjectEvent.h" +#include "Speckle/Utility/Guid.h" +#include "Speckle/Utility/String.h" + +#include +#include +#include + +using namespace active::event; +using namespace active::setting; +using namespace speckle::database; +using namespace speckle::environment; +using namespace speckle::event; +using namespace speckle::record::attribute; +using namespace speckle::utility; + +using enum ArchicadDBaseCore::Status; + +namespace speckle::database { + +#ifdef ARCHICAD + /*! + A class to collect and manage a list of active Archicad storeys + NB: This list has to be rebuilt every time a storey has changed because Archicad reindexes storeys each time + This class also has to release memory used by the API to hold storey data + */ + class ArchicadAttributeDBaseEngine::StoreyCache : public std::vector { + public: + /*! + Constructor (NB: This automatically gathers information about the current storeys) + */ + StoreyCache() { rebuild(); } + + /*! + Rebuild the current storey list + */ + void rebuild() { + clear(); + API_StoryInfo storeyInfo; + active::utility::Memory::erase(storeyInfo); + ACAPI_ProjectSetting_GetStorySettings(&storeyInfo); + auto storeyCount = storeyInfo.lastStory - storeyInfo.firstStory + 1; + for (auto i = 0; i < storeyCount; ++i) + push_back((*storeyInfo.data)[i]); + BMKillHandle(reinterpret_cast(&storeyInfo.data)); + } + /*! + Find a storey by unique ID + @param id The storey unique ID + @return An iterator pointing to the found storey (end() on failure) + */ + const_iterator find(const Guid& id) const { + auto floorID = static_cast(Guid::toInt(id)); + return std::find_if(begin(), end(), [&](auto storey){ return storey.floorId == floorID; }); + } + /*! + Find a storey by index + @param index The storey index + @return An iterator pointing to the found storey (end() on failure) + */ + const_iterator find(short index) const { + return std::find_if(begin(), end(), [&](auto storey){ return storey.index == index; }); + } + }; +#endif + +} + +namespace { + + ///Attribute factory + std::unordered_map> attributeFactory; + +#ifdef ARCHICAD + /*! + Make a new attribute object + @param attributeData The API attribute representation + @return A new attribute object (nullptr on failure) + */ + Attribute::Unique makeAttribute(const API_Attribute& attributeData) { + auto tableID = active::utility::Guid::fromInt(static_cast(attributeData.header.typeID)); + if (attributeFactory.empty()) { + attributeFactory[active::utility::Guid::fromInt(static_cast(attributeData.header.typeID))] = + [](API_Attribute attrData, const active::utility::Guid& tableID){ return std::make_unique(attrData, tableID); }; + } + if (auto iter = attributeFactory.find(tableID); iter != attributeFactory.end()) + return iter->second(attributeData, tableID); + return nullptr; + } + + + /*! + Get the AC API data for a specified attribute + @param ID The attribute ID + @param tableID The parent table ID + @return A new attribute object (nullptr on failure) + */ + std::optional getAPIData(const BIMRecordID& ID, std::optional tableID) { + API_Attribute attribute; + active::utility::Memory::erase(attribute); + attribute.header.index = ACAPI_CreateAttributeIndex(static_cast(Guid::toInt(ID))); + attribute.header.typeID = static_cast(Guid::toInt(*tableID)); + if (ACAPI_Attribute_Get(&attribute) != NoError) + return std::nullopt; + return attribute; + } +#endif + +} + +/*-------------------------------------------------------------------- + Constructor + + id: The document storage identifier + --------------------------------------------------------------------*/ +ArchicadAttributeDBaseEngine::ArchicadAttributeDBaseEngine(const active::utility::NameID& id, ArchicadDBaseSchema&& schema) : + ArchicadDBaseCore{id, std::move(schema)} { +} //ArchicadAttributeDBaseEngine::ArchicadAttributeDBaseEngine + + +/*-------------------------------------------------------------------- + Destructor + --------------------------------------------------------------------*/ +ArchicadAttributeDBaseEngine::~ArchicadAttributeDBaseEngine() { +} //ArchicadAttributeDBaseEngine::~ArchicadAttributeDBaseEngine + + +/*-------------------------------------------------------------------- + Get an object by ID + + objID: The object index + tableID: Optional table ID (defaults to the floor plan) + documentID: Optional document ID (when the object is bound to a specific document) + + return: The requested object (nullptr on failure) + --------------------------------------------------------------------*/ +std::unique_ptr ArchicadAttributeDBaseEngine::getObject(const BIMRecordID& objID, std::optional tableID, + std::optional documentID) const { + if (!tableID) + return nullptr; + if (auto attrData = ::getAPIData(objID, *tableID); attrData) + return makeAttribute(*attrData); + return nullptr; +} //ArchicadAttributeDBaseEngine::getObject + + +/*-------------------------------------------------------------------- + Get an object in a transportable form, e.g. packaged for serialisation + + index: The object index + tableID: Optional table ID (defaults to the floor plan) + documentID: Optional document ID (when the object is bound to a specific document) + + return: The requested wrapped cargo (nullptr on failure) + --------------------------------------------------------------------*/ +active::serialise::Cargo::Unique ArchicadAttributeDBaseEngine::getObjectCargo(const BIMRecordID& ID, std::optional tableID, + std::optional documentID) const { + return nullptr; //TODO: Implement +} //ArchicadAttributeDBaseEngine::getObject + + +/*-------------------------------------------------------------------- + Get all objects + + tableID: Optional table ID (defaults to the floor plan) + documentID: Optional document ID (filter for this document only - nullopt = all objects) + + return: The requested objects (nullptr on failure) + --------------------------------------------------------------------*/ +active::container::Vector ArchicadAttributeDBaseEngine::getObjects(std::optional tableID, + std::optional documentID) const { + return {}; //TODO: Implement +} //ArchicadAttributeDBaseEngine::getObjects + + +/*-------------------------------------------------------------------- + Get all objects + + filter: The object filter + tableID: Optional table ID (defaults to the floor plan) + documentID: Optional document ID (filter for this document only - nullopt = all objects) + + return: The requested objects (nullptr on failure) + --------------------------------------------------------------------*/ +active::container::Vector ArchicadAttributeDBaseEngine::getObjects(const Filter& filter, std::optional tableID, + std::optional documentID) const { + return {}; //TODO: Implement +} //ArchicadAttributeDBaseEngine::getObjects + + +/*-------------------------------------------------------------------- + Write an object to the database + + object: The object to write + objID: The object ID + objDocID: The object document-specific ID (unique within a specific document - nullopt if not document-bound) + tableID: Optional table ID (defaults to the floor plan) + documentID: Optional document ID (when the object is bound to a specific document) + --------------------------------------------------------------------*/ +void ArchicadAttributeDBaseEngine::write(const Attribute& object, const BIMRecordID& objID, std::optional objDocID, + std::optional tableID, std::optional documentID) const { + //TODO: Implement +} //ArchicadAttributeDBaseEngine::write + + +/*-------------------------------------------------------------------- + Erase an object by index + + objID: The object ID + tableID: Optional table ID (defaults to the floor plan) + documentID: Optional document ID (when the object is bound to a specific document) + + return: True if the object was successfully erased + --------------------------------------------------------------------*/ +void ArchicadAttributeDBaseEngine::erase(const BIMRecordID& ID, std::optional tableID, + std::optional documentID) const { + //TODO: Implement +} //ArchicadAttributeDBaseEngine::erase + + +/*-------------------------------------------------------------------- + Erase all objects + + tableID: Optional table ID (defaults to the floor plan) + documentID: Optional document ID (filter for this document only - nullopt = all objects) + --------------------------------------------------------------------*/ +void ArchicadAttributeDBaseEngine::erase(std::optional tableID, std::optional documentID) const { + //TODO: Implement +} //ArchicadAttributeDBaseEngine::erase + + +/*-------------------------------------------------------------------- + Get the database outline + + return: The database outline + --------------------------------------------------------------------*/ +ArchicadAttributeDBaseEngine::Outline ArchicadAttributeDBaseEngine::getOutline() const { + return {}; //TODO: Implement +} //ArchicadAttributeDBaseEngine::getOutline + + +/*-------------------------------------------------------------------- + Get attribute data direct from the AC API. For internal use - avoid direct use + + link: A link to the required attribute + + return: The AC API attribute data + --------------------------------------------------------------------*/ +std::optional ArchicadAttributeDBaseEngine::getAPIData(const BIMLink& link) const { + return ::getAPIData(link, link.tableID); +} //ArchicadAttributeDBaseEngine::getAPIData + + +/*-------------------------------------------------------------------- + Get storey data direct from the AC API. For internal use - avoid direct use + + link: A link to the required storey + + return: The AC API storey data + --------------------------------------------------------------------*/ +std::optional ArchicadAttributeDBaseEngine::getAPIStorey(const BIMLink& link) const { + if (!m_storeyCache) + m_storeyCache = std::make_unique(); + if (auto iter = m_storeyCache->find(link); iter != m_storeyCache->end()) + return *iter; + return std::nullopt; +} //ArchicadAttributeDBaseEngine::getAPIStorey + +#endif diff --git a/SpeckleLib/Speckle/Database/Storage/ArchicadDBase/Attribute/ArchicadAttributeDBaseEngine.h b/SpeckleLib/Speckle/Database/Storage/ArchicadDBase/Attribute/ArchicadAttributeDBaseEngine.h new file mode 100644 index 0000000..f8a5787 --- /dev/null +++ b/SpeckleLib/Speckle/Database/Storage/ArchicadDBase/Attribute/ArchicadAttributeDBaseEngine.h @@ -0,0 +1,142 @@ +#ifndef SPECKLE_DATABASE_ARCHICAD_ATTRIBUTE_DBASE_ENGINE +#define SPECKLE_DATABASE_ARCHICAD_ATTRIBUTE_DBASE_ENGINE + +#include "Active/Database/Storage/DBaseEngine.h" +#include "Active/Serialise/UnboxedTransport.h" +#include "Speckle/Database/Storage/ArchicadDBase/ArchicadDBaseCore.h" +#include "Speckle/Database/Identity/BIMLink.h" +#include "Speckle/Record/Attribute/Attribute.h" +#include "Speckle/Utility/Guid.h" +#include "Speckle/Utility/String.h" + +#include +#include + +namespace speckle::database { + + /*! + A database engine to read/write elements in an Archicad project database (local file or cloud-based) + + For attribute indices: + - Each attribute type is considered to be stored in a dedicated table + - For Archicad: + - The table ID is typically the attribute type ID + - The record ID is the attribute index + - Storeys are also treated as an attribute (the API treats them separately, although application to elements is virtually the identical) + */ + class ArchicadAttributeDBaseEngine : public ArchicadDBaseCore, + public active::database::DBaseEngine { + public: + + // MARK: - Types + + using base = active::database::DBaseEngine; + using Attribute = record::attribute::Attribute; + using Filter = base::Filter; + using Outline = base::Outline; + + // MARK: - Constructors + + /*! + Constructor + @param id The document storage identifier + */ + ArchicadAttributeDBaseEngine(const active::utility::NameID& id, ArchicadDBaseSchema&& schema); + ArchicadAttributeDBaseEngine(const ArchicadAttributeDBaseEngine&) = delete; + /*! + Destructor + */ + ~ArchicadAttributeDBaseEngine(); + + // MARK: - Functions (const) + + /*! + Get an object by ID + @param objID The object ID + @param tableID Optional table ID (defaults to the floor plan) + @param documentID Optional document ID (when the object is bound to a specific document) + @return The requested object (nullptr on failure) + */ + std::unique_ptr getObject(const BIMRecordID& objID, std::optional tableID = std::nullopt, std::optional documentID = std::nullopt) const override; + /*! + Get an object in a transportable form, e.g. packaged for serialisation + @param objID The object ID + @param tableID Optional table ID (defaults to the floor plan) + @param documentID Optional document ID (when the object is bound to a specific document) + @return: The requested wrapped cargo (nullptr on failure) + */ + active::serialise::Cargo::Unique getObjectCargo(const BIMRecordID& objID, std::optional tableID = std::nullopt, std::optional documentID = std::nullopt) const override; + /*! + Get all objects + @param tableID Optional table ID (defaults to the floor plan) + @param documentID Optional document ID (filter for this document only - nullopt = all objects) + @return The requested objects (nullptr on failure) + */ + active::container::Vector getObjects(std::optional tableID = std::nullopt, std::optional documentID = std::nullopt) const override; + /*! + Get a filtered list of objects + @param filter The object filter + @param tableID Optional table ID (defaults to the floor plan) + @param documentID Optional document ID (filter for this document only - nullopt = all objects) + @return The filtered objects (nullptr on failure) + */ + active::container::Vector getObjects(const Filter& filter, std::optional tableID = std::nullopt, + std::optional documentID = std::nullopt) const override; + /*! + Write an object to the database + @param object The object to write + @param objID The object ID + @param objDocID The object document-specific ID (unique within a specific document - nullopt if not document-bound) + @param tableID Optional table ID (defaults to the floor plan) + @param documentID Optional document ID (when the object is bound to a specific document) + */ + void write(const Attribute& object, const BIMRecordID& objID, std::optional objDocID = std::nullopt, + std::optional tableID = std::nullopt, std::optional documentID = std::nullopt) const override; + /*! + Erase an object by index + @param ID The object ID + @param tableID Optional table ID (defaults to the floor plan) + @param documentID Optional document ID (when the object is bound to a specific document) + @throw Exception thrown on SQL error + */ + void erase(const BIMRecordID& ID, std::optional tableID = std::nullopt, + std::optional documentID = std::nullopt) const override; + /*! + Erase all objects + @param tableID Optional table ID (defaults to the floor plan) + @param documentID Optional document ID (when the object is bound to a specific document) + @throw Exception thrown on SQL error + */ + void erase(std::optional tableID = std::nullopt, std::optional documentID = std::nullopt) const override; + /*! + Get the database outline + @return The database outline + */ + Outline getOutline() const override; + +#ifdef ARCHICAD + /*! + Get attribute data direct from the AC API. For internal use - avoid direct use + @param link A link to the required attribute + @return The AC API attribute data + */ + std::optional getAPIData(const BIMLink& link) const; + /*! + Get storey data direct from the AC API. For internal use - avoid direct use + @param link A link to the required storey + @return The AC API storey data + */ + std::optional getAPIStorey(const BIMLink& link) const; +#endif + + private: + void setTable(std::optional tableID = std::nullopt, std::optional documentID = std::nullopt); + + class StoreyCache; + ///Cache of storeys in the database (saves repeated lookups) + mutable std::unique_ptr m_storeyCache; + }; + +} + +#endif //SPECKLE_DATABASE_ARCHICAD_ATTRIBUTE_DBASE_ENGINE diff --git a/SpeckleLib/Speckle/Database/Storage/ArchicadDBase/Element/ArchicadElementDBaseEngine.cpp b/SpeckleLib/Speckle/Database/Storage/ArchicadDBase/Element/ArchicadElementDBaseEngine.cpp index b7cb9cd..7438e6d 100644 --- a/SpeckleLib/Speckle/Database/Storage/ArchicadDBase/Element/ArchicadElementDBaseEngine.cpp +++ b/SpeckleLib/Speckle/Database/Storage/ArchicadDBase/Element/ArchicadElementDBaseEngine.cpp @@ -43,19 +43,6 @@ namespace { } //getTableInfo - /*! - Get the ID of the active Archicad table - @return The active table ID (nullopt on failure) - */ - std::optional getActiveTable() { - API_WindowInfo dbaseInfo; - active::utility::Memory::erase(dbaseInfo); - if (auto err = ACAPI_Database_GetCurrentDatabase(&dbaseInfo); err == NoError) - return dbaseInfo.databaseUnId.elemSetId; - return std::nullopt; - } //getActiveTable - - /*! Set the active Archicad table @param tableID The target table ID @@ -64,7 +51,7 @@ namespace { bool setActiveTable(const BIMRecordID& tableID) { if (!tableID) return false; //Null guid doens't point to anything - if (auto activeTable = getActiveTable(); activeTable && *activeTable == tableID) + if (auto activeTable = ArchicadElementDBaseEngine::getActiveTable(); activeTable && *activeTable == tableID) return true; auto dbaseInfo = getTableInfo(tableID); if (!dbaseInfo) @@ -76,29 +63,47 @@ namespace { /*! Make a new element object @param elementData The API element representation + @param tableID The ID of the parent table (defaults to the active drawing) @return A new element object (nullptr on failure) */ - Element::Unique makeElement(const API_Element& elementData) { + Element::Unique makeElement(const API_Element& elementData, const BIMRecordID& tableID) { //Implement an object factory in future as classes for specific element types are implemented, e.g. Wall, Roof etc. using hash map //The fallback for undefined element types will always be the base Element class - return std::make_unique(elementData); + return std::make_unique(elementData, tableID); } } +/*-------------------------------------------------------------------- + Get the ID of the active Archicad table + + return; The active table ID (nullopt on failure) + --------------------------------------------------------------------*/ +std::optional ArchicadElementDBaseEngine::getActiveTable() { + API_WindowInfo dbaseInfo; + active::utility::Memory::erase(dbaseInfo); + if (auto err = ACAPI_Database_GetCurrentDatabase(&dbaseInfo); err == NoError) + return dbaseInfo.databaseUnId.elemSetId; + return std::nullopt; +} //ArchicadElementDBaseEngine::getActiveTable + + /*-------------------------------------------------------------------- Get the current user element selection return: A list of selected element IDs --------------------------------------------------------------------*/ BIMLinkList ArchicadElementDBaseEngine::getSelection() const { + auto tableID = getActiveTable(); + if (!tableID) + return {}; BIMLinkList result; API_SelectionInfo selectionInfo; active::utility::Memory::erase(selectionInfo); GS::Array selection; if (auto err = ACAPI_Selection_Get(&selectionInfo, &selection, true); err == NoError) { for (const auto& item : selection) - result.push_back(BIMLink{item}); + result.push_back(BIMLink{item, *tableID}); } return result; } //ArchicadElementDBaseEngine::getSelection @@ -115,12 +120,17 @@ BIMLinkList ArchicadElementDBaseEngine::getSelection() const { --------------------------------------------------------------------*/ std::unique_ptr ArchicadElementDBaseEngine::getObject(const BIMRecordID& ID, std::optional tableID, std::optional documentID) const { + if (!tableID) { + tableID = getActiveTable(); + if (!tableID) + return nullptr; + } API_Element element; active::utility::Memory::erase(element); API_Guid guid{ID.operator API_Guid()}; if (ACAPI_Element_GetElementFromAnywhere(&guid, &element) != NoError) return nullptr; - return makeElement(element); + return makeElement(element, *tableID); } //ArchicadElementDBaseEngine::getObject diff --git a/SpeckleLib/Speckle/Database/Storage/ArchicadDBase/Element/ArchicadElementDBaseEngine.h b/SpeckleLib/Speckle/Database/Storage/ArchicadDBase/Element/ArchicadElementDBaseEngine.h index 6e23859..103a11a 100644 --- a/SpeckleLib/Speckle/Database/Storage/ArchicadDBase/Element/ArchicadElementDBaseEngine.h +++ b/SpeckleLib/Speckle/Database/Storage/ArchicadDBase/Element/ArchicadElementDBaseEngine.h @@ -28,6 +28,14 @@ namespace speckle::database { using Filter = base::Filter; using Outline = base::Outline; + // MARK: - Static functions + + /*! + Get the ID of the active Archicad table + @return The active table ID (nullopt on failure) + */ + static std::optional getActiveTable(); + // MARK: - Constructors /*! diff --git a/SpeckleLib/Speckle/Environment/Project.cpp b/SpeckleLib/Speckle/Environment/Project.cpp index 27c401d..a991660 100644 --- a/SpeckleLib/Speckle/Environment/Project.cpp +++ b/SpeckleLib/Speckle/Environment/Project.cpp @@ -1,5 +1,6 @@ #include "Speckle/Environment/Project.h" +#include "Speckle/Database/BIMAttributeDatabase.h" #include "Speckle/Database/BIMElementDatabase.h" #include "Speckle/Environment/Addon.h" #include "Speckle/SpeckleResource.h" @@ -23,6 +24,7 @@ namespace { --------------------------------------------------------------------*/ Project::Project() { m_element = std::make_unique(); + m_attribute = std::make_unique(); } //Project::Project diff --git a/SpeckleLib/Speckle/Environment/Project.h b/SpeckleLib/Speckle/Environment/Project.h index 284be8c..752f040 100644 --- a/SpeckleLib/Speckle/Environment/Project.h +++ b/SpeckleLib/Speckle/Environment/Project.h @@ -5,6 +5,7 @@ #include "Speckle/Utility/String.h" namespace speckle::database { + class BIMAttributeDatabase; class BIMElementDatabase; } @@ -59,6 +60,11 @@ namespace speckle::environment { @return The account database */ const database::BIMElementDatabase* getElementDatabase() const { return m_element.get(); } + /*! + Get the account database + @return The account database + */ + const database::BIMAttributeDatabase* getAttributeDatabase() const { return m_attribute.get(); } // MARK: - Functions (mutating) @@ -73,7 +79,10 @@ namespace speckle::environment { Project(); private: + ///The BIM element database std::unique_ptr m_element; + ///The BIM attribute database + std::unique_ptr m_attribute; }; } diff --git a/SpeckleLib/Speckle/Event/Subscriber/SelectionSubscriber.cpp b/SpeckleLib/Speckle/Event/Subscriber/SelectionSubscriber.cpp index 2d10bd6..48b5f04 100644 --- a/SpeckleLib/Speckle/Event/Subscriber/SelectionSubscriber.cpp +++ b/SpeckleLib/Speckle/Event/Subscriber/SelectionSubscriber.cpp @@ -2,6 +2,7 @@ #include "Speckle/Environment/Addon.h" #include "Speckle/Database/Identity/BIMLink.h" +#include "Speckle/Database/Storage/ArchicadDBase/Element/ArchicadElementDBaseEngine.h" #include "Speckle/Event/Type/SelectionEvent.h" #ifdef ARCHICAD @@ -23,8 +24,10 @@ namespace { */ GSErrCode __ACENV_CALL selectionCallback(const API_Neig* params) { if (addon() != nullptr) { - auto selection = (params == nullptr) ? BIMLink{} : BIMLink{*params}; - addon()->publishExternal(SelectionEvent{selection}); + if (auto tableID = ArchicadElementDBaseEngine::getActiveTable(); tableID) { + auto selection = (params == nullptr) ? BIMLink{} : BIMLink{*params, *tableID}; + addon()->publishExternal(SelectionEvent{selection}); + } } return NoError; } diff --git a/SpeckleLib/Speckle/Record/Attribute/Attribute.cpp b/SpeckleLib/Speckle/Record/Attribute/Attribute.cpp new file mode 100644 index 0000000..dba0b12 --- /dev/null +++ b/SpeckleLib/Speckle/Record/Attribute/Attribute.cpp @@ -0,0 +1,103 @@ +#include "Speckle/Record/Attribute/Attribute.h" + +#include "Speckle/Database/BIMAttributeDatabase.h" +#include "Speckle/Environment/Addon.h" +#include "Speckle/Environment/Project.h" + +using namespace active::serialise; +using namespace speckle::database; +using namespace speckle::environment; +using namespace speckle::record::attribute; +using namespace speckle::utility; + +#include +#include + +namespace { + + ///Serialisation fields + enum FieldIndex { + nameID, + }; + + ///Serialisation field IDs + static std::array fieldID = { + Identity{"name"}, + }; + +} + +/*-------------------------------------------------------------------- + Default constructor + --------------------------------------------------------------------*/ +Attribute::Attribute() { +} //Attribute::Attribute + + +/*-------------------------------------------------------------------- + Get the attribute name + + return: The attribute name + --------------------------------------------------------------------*/ +speckle::utility::String Attribute::getName() const { +#ifdef ARCHICAD + return getHead().name; +#endif +} //Attribute::getName + + +/*-------------------------------------------------------------------- + Fill an inventory with the package items + + inventory: The inventory to receive the package items + + return: True if the package has added items to the inventory + --------------------------------------------------------------------*/ +bool Attribute::fillInventory(Inventory& inventory) const { + using enum Entry::Type; + inventory.merge(Inventory{ + { + { fieldID[nameID], nameID, element }, + }, + }.withType(&typeid(Attribute))); + return base::fillInventory(inventory); +} //Attribute::fillInventory + + +/*-------------------------------------------------------------------- + Get the specified cargo + + item: The inventory item to retrieve + + return: The requested cargo (nullptr on failure) + --------------------------------------------------------------------*/ +Cargo::Unique Attribute::getCargo(const Inventory::Item& item) const { + if (item.ownerType != &typeid(Attribute)) + return base::getCargo(item); + using namespace active::serialise; + switch (item.index) { + case nameID: + //return std::make_unique(*body); + return std::make_unique(getHead().name); + default: + return nullptr; //Requested an unknown index + } +} //Attribute::getCargo + + +#ifdef ARCHICAD +/*-------------------------------------------------------------------- + Get the attribute data from the host BIM application + + return: The attribute data (for internal use to populate derived classes) + --------------------------------------------------------------------*/ +API_Attribute Attribute::getData() const { + if (auto project = addon()->getActiveProject().lock(); project) { + if (auto attr = project->getAttributeDatabase()->getAPIData(getBIMLink()); attr) + return *attr; + } + API_Attribute attr; + active::utility::Memory::erase(attr); + return attr; +} //Attribute::getData +#endif diff --git a/SpeckleLib/Speckle/Record/Attribute/Attribute.h b/SpeckleLib/Speckle/Record/Attribute/Attribute.h new file mode 100644 index 0000000..e1a13ed --- /dev/null +++ b/SpeckleLib/Speckle/Record/Attribute/Attribute.h @@ -0,0 +1,120 @@ +#ifndef SPECKLE_RECORD_ATTRIBUTE +#define SPECKLE_RECORD_ATTRIBUTE + +#include "Speckle/Database/Content/BIMRecord.h" +#include "Speckle/Utility/String.h" + +namespace speckle::record::attribute { + + /*! + Base BIM attribute class + */ + class Attribute : public speckle::database::BIMRecord { + public: + + // MARK: - Types + + using base = speckle::database::BIMRecord; + ///Unique pointer + using Unique = std::unique_ptr; + ///Shared pointer + using Shared = std::shared_ptr; + ///Optional + using Option = std::optional; + + // MARK: - Constants + +#ifdef ARCHICAD + ///Archicad type identifier for a storey attribute table + static constexpr int32_t storeyTableID = 0x200; + /*! + Get an attribute type ID from a table ID + @param tableID The table ID + @return The attribute type ID (NB: not strictly API_AttrTypeID - can be storeyTypeID) + */ + static API_AttrTypeID getTypeID(const active::utility::Guid& tableID) { + return static_cast(active::utility::Guid::toInt(tableID)); + } + /*! + Get an Archicad attribute index from a record ID + @param recordID The record ID + @return An attribute index + */ + static API_AttributeIndex getIndex(const active::utility::Guid& recordID) { + return ACAPI_CreateAttributeIndex(static_cast(active::utility::Guid::toInt(recordID))); + } +#endif + + // MARK: - Constructors + + using base::base; + + /*! + Default constructor + */ + Attribute(); + /*! + Constructor + @param ID The attribute ID + @param tableID The attribute table ID (attribute type) + */ + Attribute(const database::BIMRecordID& ID, const speckle::utility::Guid& tableID) : base{ID, tableID} {} + + // MARK: - Functions (const) + + /*! + Get the speckle type identifier + @return The speckle type (relevant objects should override as required) + */ + speckle::utility::String getSpeckleType() const override { return "speckle::record::attribute::Attribute"; } + /*! + Get the attribute name + @return The attribute name + */ + speckle::utility::String getName() const; +#ifdef ARCHICAD + /*! + Get the (immutable) API attribute header data + @return The attribute header data (only use this data for low-level operations - for normal code, call getters/setters) + */ + virtual const API_Attr_Head& getHead() const = 0; +#endif + + // MARK: - Functions (mutating) + +#ifdef ARCHICAD + /*! + Get the (mutable) API attribute header data + @return The attribute header data (only use this data for low-level operations - for normal code, call getters/setters) + */ + virtual API_Attr_Head& getHead() = 0; +#endif + + // MARK: - Serialisation + + /*! + Fill an inventory with the package items + @param inventory The inventory to receive the package items + @return True if the package has added items to the inventory + */ + bool fillInventory(active::serialise::Inventory& inventory) const override; + /*! + Get the specified cargo + @param item The inventory item to retrieve + @return The requested cargo (nullptr on failure) + */ + Cargo::Unique getCargo(const active::serialise::Inventory::Item& item) const override; + + protected: +#ifdef ARCHICAD + /*! + Get the attribute data from the host BIM application + @return The attribute data (for internal use to populate derived classes) + */ + API_Attribute getData() const; +#endif + }; + +} + +#endif //SPECKLE_RECORD_ATTRIBUTE diff --git a/SpeckleLib/Speckle/Record/Attribute/Finish.cpp b/SpeckleLib/Speckle/Record/Attribute/Finish.cpp new file mode 100644 index 0000000..33aad98 --- /dev/null +++ b/SpeckleLib/Speckle/Record/Attribute/Finish.cpp @@ -0,0 +1,164 @@ +#include "Speckle/Record/Attribute/Finish.h" + +#include "Active/Serialise/Item/Wrapper/ValueWrap.h" +#include "Speckle/Utility/Guid.h" + +using namespace active::serialise; +using namespace speckle::database; +using namespace speckle::record::attribute; +using namespace speckle::utility; + +#include +#include + +namespace speckle::record::attribute { + + class Finish::Data { + public: +#ifdef ARCHICAD + Data(const API_Attribute& attr) : root{attr.material} {} + Data(const Data& source) : root{source.root} {} + + API_MaterialType root; +#endif + }; + +} + +namespace { + + ///Serialisation fields + enum FieldIndex { + surfaceColourID, + }; + + ///Serialisation field IDs + static std::array fieldID = { + Identity{"surfaceColour"}, + }; + +} + +/*-------------------------------------------------------------------- + Default constructor + --------------------------------------------------------------------*/ +Finish::Finish() { +} //Finish::Finish + + +/*-------------------------------------------------------------------- + Constructor + + ID: The attribute ID + --------------------------------------------------------------------*/ +Finish::Finish(const database::BIMRecordID& ID) : base{ID, Finish::table} { +} //Finish::Finish + + +#ifdef ARCHICAD +/*-------------------------------------------------------------------- + Constructor + + attrData: Archicad attribute data + tableID: The ID of the parent table + --------------------------------------------------------------------*/ +Finish::Finish(const API_Attribute& attrData, const BIMRecordID& tableID) : base{attrData.header.guid, Finish::table} { + m_data = std::make_unique(attrData); +} +#endif + + +/*-------------------------------------------------------------------- + Copy constructor + + source: The object to copy + --------------------------------------------------------------------*/ +Finish::Finish(const Finish& source) : base{source} { + m_data = source.m_data ? std::make_unique(*m_data) : nullptr; +} //Finish::Finish + + +/*-------------------------------------------------------------------- + Destructor + --------------------------------------------------------------------*/ +Finish::~Finish() {} + + +#ifdef ARCHICAD +/*-------------------------------------------------------------------- + Get the (immutable) API attribute header data + + return: The attribute header data (only use this data for low-level operations - for normal code, call getters/setters) + --------------------------------------------------------------------*/ +const API_Attr_Head& Finish::getHead() const { + confirmData(); + return m_data->root.head; +} //Finish::getHead + +/*-------------------------------------------------------------------- + Get the (mutable) API attribute header data + + return: The attribute header data (only use this data for low-level operations - for normal code, call getters/setters) + --------------------------------------------------------------------*/ +API_Attr_Head& Finish::getHead() { + confirmData(); + return m_data->root.head; +} //Finish::getHead +#endif + + +/*-------------------------------------------------------------------- + Fill an inventory with the package items + + inventory: The inventory to receive the package items + + return: True if the package has added items to the inventory + --------------------------------------------------------------------*/ +bool Finish::fillInventory(Inventory& inventory) const { + using enum Entry::Type; + inventory.merge(Inventory{ + { + { fieldID[surfaceColourID], surfaceColourID, element }, //TODO: implement other fields + }, + }.withType(&typeid(Finish))); + return base::fillInventory(inventory); +} //Finish::fillInventory + + +/*-------------------------------------------------------------------- + Get the specified cargo + + item: The inventory item to retrieve + + return: The requested cargo (nullptr on failure) + --------------------------------------------------------------------*/ +Cargo::Unique Finish::getCargo(const Inventory::Item& item) const { + if (item.ownerType != &typeid(Finish)) + return base::getCargo(item); + confirmData(); + using namespace active::serialise; + switch (item.index) { + case surfaceColourID: + return nullptr; //TODO: lookup surface colour + default: + return nullptr; //Requested an unknown index + } +} //Finish::getCargo + + +/*-------------------------------------------------------------------- + Set to the default package content + --------------------------------------------------------------------*/ +void Finish::setDefault() { + +} //Finish::setDefault + + +/*-------------------------------------------------------------------- + Confirm the internal data, either loading from the BIM application or setting a default + --------------------------------------------------------------------*/ +void Finish::confirmData() const { + if (m_data) + return; + m_data = std::make_unique(getData()); +} //Finish::confirmData diff --git a/SpeckleLib/Speckle/Record/Attribute/Finish.h b/SpeckleLib/Speckle/Record/Attribute/Finish.h new file mode 100644 index 0000000..36c274b --- /dev/null +++ b/SpeckleLib/Speckle/Record/Attribute/Finish.h @@ -0,0 +1,128 @@ +#ifndef SPECKLE_RECORD_ATTRIBUTE_FINISH +#define SPECKLE_RECORD_ATTRIBUTE_FINISH + +#include "Speckle/Record/Attribute/Attribute.h" + +namespace speckle::record::attribute { + + /*! + Class to represent the rendered finish on a 3D body, i.e. the surface colour/texture etc. + + In Archicad this attribute is represented by `API_MaterialType` + */ + class Finish : public Attribute { + public: + + // MARK: - Types + + using base = Attribute; + ///Unique pointer + using Unique = std::unique_ptr; + ///Shared pointer + using Shared = std::shared_ptr; + ///Optional + using Option = std::optional; + + // MARK: - Constants + +#ifdef ARCHICAD + ///The finishes table identifier + static constexpr active::utility::Guid table{active::utility::Guid::fromInt(API_MaterialID)}; +#endif + + // MARK: - Constructors + + using base::base; + + /*! + Default constructor + */ + Finish(); + /*! + Constructor + @param ID The attribute ID + */ + Finish(const database::BIMRecordID& ID); +#ifdef ARCHICAD + /*! + Constructor + @param attrData Archicad attribute data + @param tableID The ID of the parent table + */ + Finish(const API_Attribute& attrData, const database::BIMRecordID& tableID); +#endif + /*! + Copy constructor + @param source The object to copy + */ + Finish(const Finish& source); + /*! + Destructor + */ + ~Finish(); + + /*! + Object cloning + @return A clone of this object + */ + Finish* clonePtr() const override { return new Finish{*this}; } + + + // MARK: - Functions (const) + + /*! + Get the speckle type identifier + @return The speckle type (relevant objects should override as required) + */ + speckle::utility::String getSpeckleType() const override { return "Objects.Other.RenderMaterial"; } +#ifdef ARCHICAD + /*! + Get the (immutable) API attribute header data + @return The attribute header data (only use this data for low-level operations - for normal code, call getters/setters) + */ + const API_Attr_Head& getHead() const override; +#endif + + // MARK: - Functions (mutating) + +#ifdef ARCHICAD + /*! + Get the (mutable) API attribute header data + @return The attribute header data (only use this data for low-level operations - for normal code, call getters/setters) + */ + API_Attr_Head& getHead() override; +#endif + + // MARK: - Serialisation + + /*! + Fill an inventory with the package items + @param inventory The inventory to receive the package items + @return True if the package has added items to the inventory + */ + bool fillInventory(active::serialise::Inventory& inventory) const override; + /*! + Get the specified cargo + @param item The inventory item to retrieve + @return The requested cargo (nullptr on failure) + */ + Cargo::Unique getCargo(const active::serialise::Inventory::Item& item) const override; + /*! + Set to the default package content + */ + void setDefault() override; + + private: + /*! + Confirm the internal data, either loading from the BIM application or setting a default + */ + void confirmData() const; + + class Data; + ///The attribute data - mutable to support lazy loading + mutable std::unique_ptr m_data; + }; + +} + +#endif //SPECKLE_RECORD_ATTRIBUTE_FINISH diff --git a/SpeckleLib/Speckle/Record/Attribute/Storey.cpp b/SpeckleLib/Speckle/Record/Attribute/Storey.cpp new file mode 100644 index 0000000..6bee954 --- /dev/null +++ b/SpeckleLib/Speckle/Record/Attribute/Storey.cpp @@ -0,0 +1,192 @@ +#include "Speckle/Record/Attribute/Storey.h" + +#include "Active/Serialise/Item/Wrapper/ValueWrap.h" +#include "Active/Utility/BufferOut.h" +#include "Speckle/Database/BIMAttributeDatabase.h" +#include "Speckle/Environment/Addon.h" +#include "Speckle/Environment/Project.h" +#include "Speckle/Utility/Guid.h" + +using namespace active::serialise; +using namespace speckle::database; +using namespace speckle::environment; +using namespace speckle::record::attribute; +using namespace speckle::utility; + +#include +#include + +namespace speckle::record::attribute { + + class Storey::Data { + public: +#ifdef ARCHICAD + Data(const API_StoryType& storey) : root{storey} {} + Data(const Data& source) : root{source.root} {} + + API_StoryType root; + API_Attr_Head header; +#endif + }; + +} + +namespace { + + ///Serialisation fields + enum FieldIndex { + levelID, + }; + + ///Serialisation field IDs + static std::array fieldID = { + Identity{"level"}, + }; + + +#ifdef ARCHICAD + /*! + Fill in an Archicad API attribute header based on a storey + @param header The attribute header to be populated + @param storey The storey to be copied into the header + */ + void fillHeader(API_Attr_Head& header, const API_StoryType& storey) { + active::utility::Memory::erase(header); + //NB: This is not intended to be used for API attribute calls - it only transports core properties within this framework, e.g. name + header.typeID = static_cast(Attribute::storeyTableID); + header.index = ACAPI_CreateAttributeIndex(storey.index); + header.guid = Guid{Guid::fromInt(storey.floorId)}; + String{storey.uName}.writeUTF8(active::utility::BufferOut{header.name}, true); + } +#endif + +} + +/*-------------------------------------------------------------------- + Default constructor + --------------------------------------------------------------------*/ +Storey::Storey() { +} //Storey::Storey + + +/*-------------------------------------------------------------------- + Constructor + + ID: The attribute ID + --------------------------------------------------------------------*/ +Storey::Storey(const database::BIMRecordID& ID) : base{ID, storeyTableID} { +} //Storey::Storey + + +/*-------------------------------------------------------------------- + Copy constructor + + source: The object to copy + --------------------------------------------------------------------*/ +Storey::Storey(const Storey& source) : base{source} { + m_data = source.m_data ? std::make_unique(*m_data) : nullptr; +} //Storey::Storey + + +/*-------------------------------------------------------------------- + Destructor + --------------------------------------------------------------------*/ +Storey::~Storey() {} + + +#ifdef ARCHICAD +/*-------------------------------------------------------------------- + Get the (immutable) API attribute header data + + return: The attribute header data (only use this data for low-level operations - for normal code, call getters/setters) + --------------------------------------------------------------------*/ +const API_Attr_Head& Storey::getHead() const { + confirmData(); + fillHeader(m_data->header, m_data->root); + return m_data->header; +} //Storey::getHead + +/*-------------------------------------------------------------------- + Get the (mutable) API attribute header data + + return: The attribute header data (only use this data for low-level operations - for normal code, call getters/setters) + --------------------------------------------------------------------*/ +API_Attr_Head& Storey::getHead() { + confirmData(); + fillHeader(m_data->header, m_data->root); + return m_data->header; +} //Storey::getHead +#endif + + +/*-------------------------------------------------------------------- + Fill an inventory with the package items + + inventory: The inventory to receive the package items + + return: True if the package has added items to the inventory + --------------------------------------------------------------------*/ +bool Storey::fillInventory(Inventory& inventory) const { + using enum Entry::Type; + inventory.merge(Inventory{ + { + { fieldID[levelID], levelID, element }, //TODO: implement other fields + }, + }.withType(&typeid(Storey))); + return base::fillInventory(inventory); +} //Storey::fillInventory + + +/*-------------------------------------------------------------------- + Get the specified cargo + + item: The inventory item to retrieve + + return: The requested cargo (nullptr on failure) + --------------------------------------------------------------------*/ +Cargo::Unique Storey::getCargo(const Inventory::Item& item) const { + if (item.ownerType != &typeid(Storey)) + return base::getCargo(item); + confirmData(); + using namespace active::serialise; + switch (item.index) { + case levelID: + return std::make_unique(m_data->root.level); + default: + return nullptr; //Requested an unknown index + } +} //Storey::getCargo + + +/*-------------------------------------------------------------------- + Set to the default package content + --------------------------------------------------------------------*/ +void Storey::setDefault() { + +} //Storey::setDefault + + +/*-------------------------------------------------------------------- + Confirm the internal data, either loading from the BIM application or setting a default + --------------------------------------------------------------------*/ +void Storey::confirmData() const { + if (m_data) + return; + m_data = std::make_unique(getStoreyData()); +} //Storey::confirmData + + +/*-------------------------------------------------------------------- + Get the storey data from the host BIM application + + return: The storey data (for internal use to populate derived classes) + --------------------------------------------------------------------*/ +API_StoryType Storey::getStoreyData() const { + if (auto project = addon()->getActiveProject().lock(); project) { + if (auto storey = project->getAttributeDatabase()->getAPIStorey(getBIMLink()); storey) + return *storey; + } + API_StoryType storey; + active::utility::Memory::erase(storey); + return storey; +} //Storey::getStoreyData diff --git a/SpeckleLib/Speckle/Record/Attribute/Storey.h b/SpeckleLib/Speckle/Record/Attribute/Storey.h new file mode 100644 index 0000000..67aeb35 --- /dev/null +++ b/SpeckleLib/Speckle/Record/Attribute/Storey.h @@ -0,0 +1,129 @@ +#ifndef SPECKLE_RECORD_ATTRIBUTE_STOREY +#define SPECKLE_RECORD_ATTRIBUTE_STOREY + +#include "Speckle/Record/Attribute/Attribute.h" + +namespace speckle::record::attribute { + + /*! + A storey or level in a building + + Represented in Archicad by `API_StoryType` + */ + class Storey : public Attribute { + public: + + // MARK: - Types + + using base = Attribute; + ///Unique pointer + using Unique = std::unique_ptr; + ///Shared pointer + using Shared = std::shared_ptr; + ///Optional + using Option = std::optional; + + // MARK: - Constructors + + using base::base; + + /*! + Default constructor + */ + Storey(); + /*! + Constructor + @param ID The attribute ID + */ + Storey(const database::BIMRecordID& ID); +#ifdef ARCHICAD + /*! + Constructor + @param attrData Archicad attribute data + */ + Storey(const API_StoryType& attrData); +#endif + /*! + Copy constructor + @param source The object to copy + */ + Storey(const Storey& source); + /*! + Destructor + */ + ~Storey(); + + /*! + Object cloning + @return A clone of this object + */ + Storey* clonePtr() const override { return new Storey{*this}; } + + + // MARK: - Functions (const) + + /*! + Get the speckle type identifier + @return The speckle type (relevant objects should override as required) + */ + speckle::utility::String getSpeckleType() const override { return "speckle::record::attribute::Storey"; } +#ifdef ARCHICAD + /*! + Get the (immutable) API attribute header data + @return The attribute header data (only use this data for low-level operations - for normal code, call getters/setters) + */ + const API_Attr_Head& getHead() const override; +#endif + + // MARK: - Functions (mutating) + +#ifdef ARCHICAD + /*! + Get the (mutable) API attribute header data + @return The attribute header data (only use this data for low-level operations - for normal code, call getters/setters) + */ + API_Attr_Head& getHead() override; +#endif + + // MARK: - Serialisation + + /*! + Fill an inventory with the package items + @param inventory The inventory to receive the package items + @return True if the package has added items to the inventory + */ + bool fillInventory(active::serialise::Inventory& inventory) const override; + /*! + Get the specified cargo + @param item The inventory item to retrieve + @return The requested cargo (nullptr on failure) + */ + Cargo::Unique getCargo(const active::serialise::Inventory::Item& item) const override; + /*! + Set to the default package content + */ + void setDefault() override; + + protected: + + private: + /*! + Confirm the internal data, either loading from the BIM application or setting a default + */ + void confirmData() const; +#ifdef ARCHICAD + /*! + Get the storey data from the host BIM application + @return The storey data (for internal use to populate derived classes) + */ + API_StoryType getStoreyData() const; +#endif + + class Data; + ///The attribute data - mutable to support lazy loading + mutable std::unique_ptr m_data; + }; + +} + +#endif //SPECKLE_RECORD_ATTRIBUTE_STOREY diff --git a/SpeckleLib/Speckle/Record/Element/Element.cpp b/SpeckleLib/Speckle/Record/Element/Element.cpp index 7e5f6d8..4bfded8 100644 --- a/SpeckleLib/Speckle/Record/Element/Element.cpp +++ b/SpeckleLib/Speckle/Record/Element/Element.cpp @@ -3,10 +3,14 @@ #include "Active/Serialise/Item/Wrapper/ValueWrap.h" #include "Active/Serialise/Package/Wrapper/PackageWrap.h" #include "Active/Serialise/Package/Wrapper/ContainerWrap.h" +#include "Speckle/Environment/Addon.h" #include "Speckle/Primitive/Mesh/Mesh.h" +#include "Speckle/SpeckleResource.h" #include "Speckle/Utility/Guid.h" using namespace active::serialise; +using namespace speckle::environment; +using namespace speckle::record::attribute; using namespace speckle::record::element; using namespace speckle::utility; @@ -19,8 +23,10 @@ namespace speckle::record::element { public: friend class Element; +#ifdef ARCHICAD Data(const API_Element& elem) : root{std::make_unique(elem)} {} Data(const Data& source) : root{std::make_unique(*source.root)} {} +#endif private: std::unique_ptr root; @@ -41,6 +47,8 @@ namespace { Identity{"displayValue"}, }; + +#ifdef ARCHICAD void GetComponent(API_Component3D& component, API_3DTypeID typeId, Int32 index) { component.header.typeID = typeId; component.header.index = index; @@ -48,7 +56,8 @@ namespace { // TODO: throw } } - +#endif + } /*-------------------------------------------------------------------- @@ -62,8 +71,9 @@ Element::Element() { Constructor elemData: Archicad element data + tableID: The attribute table ID (attribute type) --------------------------------------------------------------------*/ -Element::Element(const API_Element& elemData) : base{elemData.header.guid} { +Element::Element(const API_Element& elemData, const speckle::utility::Guid& tableID) : base{elemData.header.guid, tableID} { m_data = std::make_unique(elemData); } //Element::Element @@ -83,8 +93,36 @@ Element::Element(const Element& source) : base{source} { --------------------------------------------------------------------*/ Element::~Element() {} -Element::Body* Element::getBody() const { +/*-------------------------------------------------------------------- + Get the element storey + + return: The element storey (nullopt if the element isn't linked to a storey) + --------------------------------------------------------------------*/ +Storey::Option Element::getStorey() const { +#ifdef ARCHICAD + return Storey{getHead().floorInd}; +#endif +} //Element::getStorey + + +/*-------------------------------------------------------------------- + Get the elmeent type name, e.g. "Wall", "Roof" etc + + return: The type name + --------------------------------------------------------------------*/ +String Element::getTypeName() const { +#ifdef ARCHICAD + GS::UniString typeName; + if (auto err = ACAPI_Element_GetElemTypeName(getHead().type, typeName); err != NoError) + return addon()->getLocalString(titleStringLib, unknownElementTypeID); + return typeName; +#endif +} //Element::getTypeName + + +Element::Body* Element::getBody() const { +#ifdef ARCHICAD if (m_data->m_cache) { return m_data->m_cache.get(); } @@ -156,9 +194,11 @@ Element::Body* Element::getBody() const { } m_data->m_cache.reset(elementBody); return m_data->m_cache.get(); +#endif } +#ifdef ARCHICAD /*-------------------------------------------------------------------- Get the (immutable) API element header data @@ -176,6 +216,7 @@ const API_Elem_Head& Element::getHead() const { API_Elem_Head& Element::getHead() { return m_data->root->header; } //Element::getHead +#endif /*-------------------------------------------------------------------- diff --git a/SpeckleLib/Speckle/Record/Element/Element.h b/SpeckleLib/Speckle/Record/Element/Element.h index 26b26f2..081119a 100644 --- a/SpeckleLib/Speckle/Record/Element/Element.h +++ b/SpeckleLib/Speckle/Record/Element/Element.h @@ -2,6 +2,7 @@ #define SPECKLE_RECORD_ELEMENT #include "Speckle/Database/Content/BIMRecord.h" +#include "Speckle/Record/Attribute/Storey.h" #include "Speckle/Utility/String.h" namespace speckle::primitive { @@ -41,8 +42,9 @@ namespace speckle::record::element { /*! Constructor @param elemData Archicad element data + @param tableID The element table ID (AC database, e.g. floor plan, 3D) */ - Element(const API_Element& elemData); + Element(const API_Element& elemData, const speckle::utility::Guid& tableID); #endif /*! Copy constructor @@ -67,7 +69,17 @@ namespace speckle::record::element { Get the speckle type identifier @return The speckle type (relevant objects should override as required) */ - speckle::utility::String getSpeckleType() const override { return "speckle::record::element::Element"; } + virtual speckle::utility::String getSpeckleType() const override { return "Objects.BuiltElements.Element:Objects.BuiltElements.Element"; } + /*! + Get the elmeent type name, e.g. "Wall", "Roof" etc + @return The type name + */ + virtual speckle::utility::String getTypeName() const; + /*! + Get the element storey + @return The element storey (nullopt if the element isn't linked to a storey) + */ + virtual attribute::Storey::Option getStorey() const; /*! Get the element body @return An array of meshes from the element body (nullptr if no body data is available) diff --git a/SpeckleLib/Speckle/SpeckleResource.h b/SpeckleLib/Speckle/SpeckleResource.h index 70843ea..bc56020 100755 --- a/SpeckleLib/Speckle/SpeckleResource.h +++ b/SpeckleLib/Speckle/SpeckleResource.h @@ -14,6 +14,7 @@ enum SpeckleStringResource { //Title strings (UI title/label for dialogs, controls, menu items etc) enum SpeckleTitleString { untitledProjectID = 1, + unknownElementTypeID, }; diff --git a/SpeckleLib/SpeckleLib.xcodeproj/project.pbxproj b/SpeckleLib/SpeckleLib.xcodeproj/project.pbxproj index d67b1b9..fad870d 100644 --- a/SpeckleLib/SpeckleLib.xcodeproj/project.pbxproj +++ b/SpeckleLib/SpeckleLib.xcodeproj/project.pbxproj @@ -37,6 +37,16 @@ 219351B12C62CC1A00E5A69C /* Guid.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 219351AC2C62CC1A00E5A69C /* Guid.cpp */; }; 219351B32C62CC1A00E5A69C /* String.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 219351AE2C62CC1A00E5A69C /* String.cpp */; }; 2196F2E32CB05BAF00450DFC /* LengthUnit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2196F2E22CB05BAF00450DFC /* LengthUnit.cpp */; }; + 2196F2EB2CB4816B00450DFC /* ArchicadAttributeDBaseEngine.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2196F2E82CB4816B00450DFC /* ArchicadAttributeDBaseEngine.cpp */; }; + 2196F2EC2CB4816B00450DFC /* ArchicadAttributeDBaseEngine.h in Headers */ = {isa = PBXBuildFile; fileRef = 2196F2E92CB4816B00450DFC /* ArchicadAttributeDBaseEngine.h */; }; + 2196F2F02CB4823C00450DFC /* Attribute.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2196F2ED2CB4823C00450DFC /* Attribute.cpp */; }; + 2196F2F12CB4823C00450DFC /* Attribute.h in Headers */ = {isa = PBXBuildFile; fileRef = 2196F2EE2CB4823C00450DFC /* Attribute.h */; }; + 2196F2F42CB483D600450DFC /* Finish.h in Headers */ = {isa = PBXBuildFile; fileRef = 2196F2F22CB483D600450DFC /* Finish.h */; }; + 2196F2F52CB483D600450DFC /* Finish.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2196F2F32CB483D600450DFC /* Finish.cpp */; }; + 2196F2F82CB51ED400450DFC /* BIMAttributeDatabase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2196F2F62CB51ED400450DFC /* BIMAttributeDatabase.cpp */; }; + 2196F2F92CB51ED400450DFC /* BIMAttributeDatabase.h in Headers */ = {isa = PBXBuildFile; fileRef = 2196F2F72CB51ED400450DFC /* BIMAttributeDatabase.h */; }; + 2196F3042CB57E8000450DFC /* Storey.h in Headers */ = {isa = PBXBuildFile; fileRef = 2196F3022CB57E7F00450DFC /* Storey.h */; }; + 2196F3052CB57E8000450DFC /* Storey.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2196F3032CB57E7F00450DFC /* Storey.cpp */; }; 2199881E2BD833830035E5EA /* libArchicad27.a in CopyFiles */ = {isa = PBXBuildFile; fileRef = 21379E082AE47A6400A1584C /* libArchicad27.a */; }; 21AEF9BA2CA606B5000B8681 /* DetachedReference.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 21AEF9B92CA606B4000B8681 /* DetachedReference.cpp */; }; 21AEF9BC2CA6DF84000B8681 /* DetachmentManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 21AEF9BB2CA6DF84000B8681 /* DetachmentManager.cpp */; }; @@ -165,6 +175,16 @@ 219351AF2C62CC1A00E5A69C /* String.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = String.h; sourceTree = ""; }; 2196F2DE2CB0566500450DFC /* LengthUnit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LengthUnit.h; sourceTree = ""; }; 2196F2E22CB05BAF00450DFC /* LengthUnit.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = LengthUnit.cpp; sourceTree = ""; }; + 2196F2E82CB4816B00450DFC /* ArchicadAttributeDBaseEngine.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ArchicadAttributeDBaseEngine.cpp; sourceTree = ""; }; + 2196F2E92CB4816B00450DFC /* ArchicadAttributeDBaseEngine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ArchicadAttributeDBaseEngine.h; sourceTree = ""; }; + 2196F2ED2CB4823C00450DFC /* Attribute.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Attribute.cpp; sourceTree = ""; }; + 2196F2EE2CB4823C00450DFC /* Attribute.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Attribute.h; sourceTree = ""; }; + 2196F2F22CB483D600450DFC /* Finish.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Finish.h; sourceTree = ""; }; + 2196F2F32CB483D600450DFC /* Finish.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Finish.cpp; sourceTree = ""; }; + 2196F2F62CB51ED400450DFC /* BIMAttributeDatabase.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BIMAttributeDatabase.cpp; sourceTree = ""; }; + 2196F2F72CB51ED400450DFC /* BIMAttributeDatabase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BIMAttributeDatabase.h; sourceTree = ""; }; + 2196F3022CB57E7F00450DFC /* Storey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Storey.h; sourceTree = ""; }; + 2196F3032CB57E7F00450DFC /* Storey.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Storey.cpp; sourceTree = ""; }; 219712682BE7E2D500D9EF7E /* Serialisation.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Serialisation.md; sourceTree = ""; }; 21AEF9B32CA5F7CF000B8681 /* DetachedWrap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DetachedWrap.h; sourceTree = ""; }; 21AEF9B52CA5FA02000B8681 /* DetachedReference.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DetachedReference.h; sourceTree = ""; }; @@ -323,6 +343,7 @@ children = ( 215F08872CA195EC00CD343B /* ArchicadDBaseCore.cpp */, 215F08882CA195EC00CD343B /* ArchicadDBaseCore.h */, + 2196F2EA2CB4816B00450DFC /* Attribute */, 219246062CA2D22D00CF5703 /* Element */, ); path = ArchicadDBase; @@ -420,6 +441,28 @@ path = Units; sourceTree = ""; }; + 2196F2EA2CB4816B00450DFC /* Attribute */ = { + isa = PBXGroup; + children = ( + 2196F2E82CB4816B00450DFC /* ArchicadAttributeDBaseEngine.cpp */, + 2196F2E92CB4816B00450DFC /* ArchicadAttributeDBaseEngine.h */, + ); + path = Attribute; + sourceTree = ""; + }; + 2196F2EF2CB4823C00450DFC /* Attribute */ = { + isa = PBXGroup; + children = ( + 2196F2ED2CB4823C00450DFC /* Attribute.cpp */, + 2196F2EE2CB4823C00450DFC /* Attribute.h */, + 2196F2F32CB483D600450DFC /* Finish.cpp */, + 2196F2F22CB483D600450DFC /* Finish.h */, + 2196F3032CB57E7F00450DFC /* Storey.cpp */, + 2196F3022CB57E7F00450DFC /* Storey.h */, + ); + path = Attribute; + sourceTree = ""; + }; 219987FA2BD708BC0035E5EA /* SpeckleLibDoctest */ = { isa = PBXGroup; children = ( @@ -457,6 +500,8 @@ children = ( 21D0BD1D2C86F0280077E104 /* AccountDatabase.cpp */, 21D0BD1E2C86F0280077E104 /* AccountDatabase.h */, + 2196F2F62CB51ED400450DFC /* BIMAttributeDatabase.cpp */, + 2196F2F72CB51ED400450DFC /* BIMAttributeDatabase.h */, 215F08932CA19AF800CD343B /* BIMElementDatabase.cpp */, 215F08942CA19AF800CD343B /* BIMElementDatabase.h */, 21D0BD272C86FC350077E104 /* Content */, @@ -593,6 +638,7 @@ 21F69F952C71087A008B6A06 /* Record */ = { isa = PBXGroup; children = ( + 2196F2EF2CB4823C00450DFC /* Attribute */, 215F087A2CA18E1400CD343B /* Element */, 21F69F942C71087A008B6A06 /* Credentials */, ); @@ -641,22 +687,27 @@ 219246032CA2CE2700CF5703 /* BIMLink.h in Headers */, 21B67D0D2C7E0E8D00FD64FC /* ErrorReport.h in Headers */, 215F08962CA19AF800CD343B /* BIMElementDatabase.h in Headers */, + 2196F2F12CB4823C00450DFC /* Attribute.h in Headers */, 21D0BD332C86FE090077E104 /* Link.h in Headers */, 21D0BD5A2C8910400077E104 /* UserInfo.h in Headers */, 21D0BD562C890B1C0077E104 /* ServerMigration.h in Headers */, 210CC88F2C81A98500610F58 /* Guid64.h in Headers */, 21AEF9DD2CAAA4EA000B8681 /* DetachedObjectStore.h in Headers */, 215F088D2CA195EC00CD343B /* ArchicadElementDBaseEngine.h in Headers */, + 2196F2F42CB483D600450DFC /* Finish.h in Headers */, 21B67D002C7CE15100FD64FC /* Exception.h in Headers */, 21D0BD2C2C86FC350077E104 /* Record.h in Headers */, 21D0BDB42C8F8AB60077E104 /* DocumentStoreCore.h in Headers */, 215F087E2CA18E1400CD343B /* Element.h in Headers */, + 2196F2F92CB51ED400450DFC /* BIMAttributeDatabase.h in Headers */, 215F08562C99DA8D00CD343B /* Project.h in Headers */, 219245FF2CA2CC4300CF5703 /* BIMRecord.h in Headers */, 210CC8802C80CD2A00610F58 /* BridgeChild.h in Headers */, 21D0BD4D2C8901A00077E104 /* ServerInfo.h in Headers */, + 2196F3042CB57E8000450DFC /* Storey.h in Headers */, 21D0BDB52C8F8AB60077E104 /* DocumentStoreEngine.h in Headers */, 21D0BDC52C9241940077E104 /* ProjectSubscriber.h in Headers */, + 2196F2EC2CB4816B00450DFC /* ArchicadAttributeDBaseEngine.h in Headers */, 21D0BD312C86FE090077E104 /* Index.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -788,6 +839,7 @@ 21F69F812C6FF3B0008B6A06 /* BridgeArgumentWrap.cpp in Sources */, 215F088B2CA195EC00CD343B /* ArchicadDBaseCore.cpp in Sources */, 2193517B2C624FC100E5A69C /* MenuSubscriber.cpp in Sources */, + 2196F2F02CB4823C00450DFC /* Attribute.cpp in Sources */, 21F69F612C6D0286008B6A06 /* GetBindingsMethodNames.cpp in Sources */, 215F08662C9B006800CD343B /* ProjectEvent.cpp in Sources */, 21D0BDBD2C90F2830077E104 /* DocStoreSubscriber.cpp in Sources */, @@ -796,11 +848,14 @@ 2196F2E32CB05BAF00450DFC /* LengthUnit.cpp in Sources */, 21F93AEC2B2F406E009A2C5B /* Addon.cpp in Sources */, 215F087D2CA18E1400CD343B /* Element.cpp in Sources */, + 2196F2F82CB51ED400450DFC /* BIMAttributeDatabase.cpp in Sources */, 21D0BD4E2C8901A00077E104 /* ServerInfo.cpp in Sources */, 21B67D0E2C7E0E8D00FD64FC /* ErrorReport.cpp in Sources */, 21F69F7E2C6FD9FC008B6A06 /* GetCallResult.cpp in Sources */, + 2196F3052CB57E8000450DFC /* Storey.cpp in Sources */, 219245FE2CA2CC4300CF5703 /* BIMRecord.cpp in Sources */, 21AEF9BE2CA6FDA4000B8681 /* DetachedWrap.cpp in Sources */, + 2196F2F52CB483D600450DFC /* Finish.cpp in Sources */, 2193519B2C6278D900E5A69C /* SelectionSubscriber.cpp in Sources */, 21D0BD2B2C86FC350077E104 /* Record.cpp in Sources */, 219246042CA2CE2700CF5703 /* BIMLink.cpp in Sources */, @@ -815,6 +870,7 @@ 21AEF9BC2CA6DF84000B8681 /* DetachmentManager.cpp in Sources */, 215F08552C99DA8D00CD343B /* Project.cpp in Sources */, 21F69F3B2C6B880C008B6A06 /* JSBaseTransport.cpp in Sources */, + 2196F2EB2CB4816B00450DFC /* ArchicadAttributeDBaseEngine.cpp in Sources */, 210CC89F2C81E34400610F58 /* Platform.cpp in Sources */, 21D0BD202C86F0280077E104 /* AccountDatabase.cpp in Sources */, 21F69F962C71087A008B6A06 /* Account.cpp in Sources */,