/////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2002-2025, Open Design Alliance (the "Alliance"). // All rights reserved. // // This software and its documentation and related materials are owned by // the Alliance. The software may only be incorporated into application // programs owned by members of the Alliance, subject to a signed // Membership Agreement and Supplemental Software License Agreement with the // Alliance. The structure and organization of this software are the valuable // trade secrets of the Alliance and its suppliers. The software is also // protected by copyright law and international treaty provisions. Application // programs incorporating this software must include the following statement // with their copyright notices: // // This application incorporates Open Design Alliance software pursuant to a license // agreement with Open Design Alliance. // Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance. // All rights reserved. // // By use of this software, its documentation or related materials, you // acknowledge and accept the above terms. /////////////////////////////////////////////////////////////////////////////// #include "OdaCommon.h" #include "RxDynamicModule.h" #include "RxVariantValue.h" // Visualize SDK #include "TvFactory.h" #include "PointCloud2Visualize.h" #include "TvFilerTimer.h" #include "TvDatabaseUtils.h" #include "TvGeomCollector.h" #include "TvError.h" #include "DynamicLinker.h" using namespace PointCloud2Visualize; OdTvVisualizePointCloudFilerProperties::OdTvVisualizePointCloudFilerProperties() : m_defaultColor(ODRGB(191, 191, 191)) , m_iPointSize(1) , m_bImportAsRcsPointCloud(false) , m_bUseSpatialTreeForSelection(true) , m_separator(OD_T(" ")) , m_intensityIndex(OdUInt32(-1)) , m_skipLines(0) , m_defaultUnits(OdTv::kUserDefined) , m_colorRange(OdPointCloudDataSource::kColorRange255) , m_bNeedCDATree(false) , m_bNeedCollectPropertiesInCDA(false) { } OdTvVisualizePointCloudFilerProperties::~OdTvVisualizePointCloudFilerProperties() { } OdRxDictionaryPtr OdTvVisualizePointCloudFilerProperties::createObject() { return OdRxObjectImpl::createObject(); } namespace PointCloud2Visualize { ODRX_DECLARE_PROPERTY(DefaultColor) ODRX_DECLARE_PROPERTY(PointSize) ODRX_DECLARE_PROPERTY(AppendTransform) ODRX_DECLARE_PROPERTY(ImportAsRcsPointCloud) ODRX_DECLARE_PROPERTY(UseSpatialTreeForSelection) ODRX_DECLARE_PROPERTY(Separator) ODRX_DECLARE_PROPERTY(XYZIndexes) ODRX_DECLARE_PROPERTY(RGBIndexes) ODRX_DECLARE_PROPERTY(IntensityIndex) ODRX_DECLARE_PROPERTY(SkipLinesCount) ODRX_DECLARE_PROPERTY(DefaultUnits) ODRX_DECLARE_PROPERTY(ColorRange) ODRX_DECLARE_PROPERTY(NeedCDATree) ODRX_DECLARE_PROPERTY(NeedCollectPropertiesInCDA) ODRX_BEGIN_DYNAMIC_PROPERTY_MAP(OdTvVisualizePointCloudFilerProperties); ODRX_GENERATE_PROPERTY(DefaultColor) ODRX_GENERATE_PROPERTY(PointSize) ODRX_GENERATE_PROPERTY(AppendTransform) ODRX_GENERATE_PROPERTY(ImportAsRcsPointCloud) ODRX_GENERATE_PROPERTY(UseSpatialTreeForSelection) ODRX_GENERATE_PROPERTY(Separator) ODRX_GENERATE_PROPERTY(XYZIndexes) ODRX_GENERATE_PROPERTY(RGBIndexes) ODRX_GENERATE_PROPERTY(IntensityIndex) ODRX_GENERATE_PROPERTY(SkipLinesCount) ODRX_GENERATE_PROPERTY(DefaultUnits) ODRX_GENERATE_PROPERTY(ColorRange) ODRX_GENERATE_PROPERTY(NeedCDATree) ODRX_GENERATE_PROPERTY(NeedCollectPropertiesInCDA) ODRX_END_DYNAMIC_PROPERTY_MAP(OdTvVisualizePointCloudFilerProperties); ODRX_DEFINE_PROPERTY_METHODS(DefaultColor, OdTvVisualizePointCloudFilerProperties, getDefaultColor, setDefaultColor, getUInt32); ODRX_DEFINE_PROPERTY_METHODS(PointSize, OdTvVisualizePointCloudFilerProperties, getPointSize, setPointSize, getUInt8); ODRX_DEFINE_PROPERTY_METHODS(AppendTransform, OdTvVisualizePointCloudFilerProperties, getAppendTransformAsIntPtr, setAppendTransformAsIntPtr, getIntPtr); ODRX_DEFINE_PROPERTY_METHODS(ImportAsRcsPointCloud, OdTvVisualizePointCloudFilerProperties, getImportAsRcsPointCloud, setImportAsRcsPointCloud, getBool); ODRX_DEFINE_PROPERTY_METHODS(UseSpatialTreeForSelection, OdTvVisualizePointCloudFilerProperties, getUseSpatialTreeForSelection, setUseSpatialTreeForSelection, getBool); ODRX_DEFINE_PROPERTY_METHODS(Separator, OdTvVisualizePointCloudFilerProperties, getSeparator, setSeparator, getString); ODRX_DEFINE_PROPERTY_METHODS(XYZIndexes, OdTvVisualizePointCloudFilerProperties, getXYZIndexesAsIntPtr, setXYZIndexesAsIntPtr, getIntPtr); ODRX_DEFINE_PROPERTY_METHODS(RGBIndexes, OdTvVisualizePointCloudFilerProperties, getRGBIndexesAsIntPtr, setRGBIndexesAsIntPtr, getIntPtr); ODRX_DEFINE_PROPERTY_METHODS(IntensityIndex, OdTvVisualizePointCloudFilerProperties, getIntensityIndex, setIntensityIndex, getUInt32); ODRX_DEFINE_PROPERTY_METHODS(SkipLinesCount, OdTvVisualizePointCloudFilerProperties, getSkipLinesCount, setSkipLinesCount, getUInt32); ODRX_DEFINE_PROPERTY_METHODS(DefaultUnits, OdTvVisualizePointCloudFilerProperties, getDefaultUnitsAsUInt8, setDefaultUnitsAsUInt8, getUInt8); ODRX_DEFINE_PROPERTY_METHODS(ColorRange, OdTvVisualizePointCloudFilerProperties, getColorRangeAsInt8, setColorRangeAsInt8, getInt8); ODRX_DEFINE_PROPERTY_METHODS(NeedCDATree, OdTvVisualizePointCloudFilerProperties, getNeedCDATree, setNeedCDATree, getBool); ODRX_DEFINE_PROPERTY_METHODS(NeedCollectPropertiesInCDA, OdTvVisualizePointCloudFilerProperties, getNeedCollectPropertiesInCDA, setNeedCollectPropertiesInCDA, getBool); } void OdTvVisualizePointCloudFilerProperties::setAppendTransformAsIntPtr(OdIntPtr pTransform) { const OdTvMatrix* pAppendTransform = reinterpret_cast(pTransform); m_appendTransform = pAppendTransform ? *pAppendTransform : OdTvMatrix::kIdentity; } OdIntPtr OdTvVisualizePointCloudFilerProperties::getAppendTransformAsIntPtr() const { return reinterpret_cast(&m_appendTransform); } void OdTvVisualizePointCloudFilerProperties::setSeparator(const OdString& separator) { m_separator = !separator.isEmpty() ? separator : OdString(" "); } const OdString& OdTvVisualizePointCloudFilerProperties::getSeparator() const { return m_separator; } void OdTvVisualizePointCloudFilerProperties::setXYZIndexesAsIntPtr(OdIntPtr pIndexes) { const OdUInt32Array* pArr = reinterpret_cast(pIndexes); if (pArr) m_xyzIndexes = *pArr; else m_xyzIndexes.clear(); } OdIntPtr OdTvVisualizePointCloudFilerProperties::getXYZIndexesAsIntPtr() const { return reinterpret_cast(&m_xyzIndexes); } void OdTvVisualizePointCloudFilerProperties::setRGBIndexesAsIntPtr(OdIntPtr pIndexes) { const OdUInt32Array* pArr = reinterpret_cast(pIndexes); if (pArr) m_rgbIndexes = *pArr; else m_rgbIndexes.clear(); } OdIntPtr OdTvVisualizePointCloudFilerProperties::getRGBIndexesAsIntPtr() const { return reinterpret_cast(&m_rgbIndexes); } OdTvVisualizePointCloudFiler::OdTvVisualizePointCloudFiler() : m_properties(OdTvVisualizePointCloudFilerProperties::createObject()) {} OdPointCloudDataSourcePtr OdTvVisualizePointCloudFiler::createPointCloudDataSource(const OdString& filePath, OdTvResult* rc) const { OdString fileExt = filePath.right(3); fileExt.makeUpper(); bool isPts = fileExt == OD_T("PTS"); bool isXyz = fileExt == OD_T("XYZ"); bool isPcd = fileExt == OD_T("PCD"); bool isLas = fileExt == OD_T("LAS") || fileExt == OD_T("LAZ"); if (!isPts && !isXyz && !isPcd && !isLas) { if (rc) *rc = tvNotSupported; return {}; } OdRxRcsFileServicesPtr pRcsFileServices = odrxDynamicLinker()->loadApp(RX_RCS_FILE_SERVICES); if (pRcsFileServices.isNull()) { if (rc) *rc = tvRcsModuleIsMissed; return {}; } ODCOLORREF pDefColor = m_properties->getDefaultColor(); const OdString& separator = isXyz ? m_properties->getSeparator() : " "; OdUInt32 intensityIndex = isPts || isXyz ? m_properties->getIntensityIndex() : OdUInt32(-1); OdUInt32 skipLines = isXyz ? m_properties->getSkipLinesCount() : 0; const OdUInt32Array& xyzArr = isPts || isXyz ? m_properties->getXYZIndexes() : OdUInt32Array(); const OdUInt32Array& rgbArr = isPts || isXyz ? m_properties->getRGBIndexes() : OdUInt32Array(); OdPointCloudDataSource::ColorRange colorRange = isPts || isXyz ? static_cast(m_properties->getColorRange()) : OdPointCloudDataSource::kNoColorRange; OdPointCloudDataSourcePtr dataSource = pRcsFileServices->getPointCloudDataSource(filePath, OdPointCloudDataSource::kMeter, pDefColor, colorRange, xyzArr, rgbArr, intensityIndex, skipLines, separator); if (!dataSource && rc) *rc = tvCannotOpenFile; return dataSource; } bool OdTvVisualizePointCloudFiler::appendPointCloud(OdTvModelPtr pTvModel, const OdString& filePath, OdPointCloudDataSourcePtr pDataSource, OdTvFilerTimer& timing, OdTvResult* rc, OdGeExtents3d* pointCloudExtents) const { OdTvEntityId entityId = pTvModel->appendEntity(); OdTvEntityPtr entity = entityId.openObject(OdTv::kForWrite); ODCOLORREF pDefColor = m_properties->getDefaultColor(); OdUInt8 r = ODGETRED(pDefColor); OdUInt8 g = ODGETGREEN(pDefColor); OdUInt8 b = ODGETBLUE(pDefColor); entity->setColor(OdTvColorDef(r, g, b)); OdTvResult tvRes = tvOk; if (m_properties->getImportAsRcsPointCloud()) { entity->setName(OD_T("RcsPointCloud")); OdString newFilePath = filePath.left(filePath.getLength() - 3) + OD_T("rcs"); timing.startMisc(); OdTvGeometryDataId pointCloudId = entity->appendRcsPointCloud(pDataSource, newFilePath, &tvRes); timing.endMisc(); if (tvRes != tvOk) { if (rc) *rc = tvRes; return false; } OdTvGeometryDataPtr pGeom = pointCloudId.openObject(); if (!pGeom.isNull()) { pGeom->setTargetDisplayMode(OdTvGeometryData::kEveryWhere); OdTvRcsPointCloudDataPtr pPointCloud = pointCloudId.openAsRcsPointCloud(); pPointCloud->setPointSize(m_properties->getPointSize()); if (pointCloudExtents) *pointCloudExtents = pPointCloud->getTransformedExtents(); } } else { entity->setName(OD_T("PointCloud")); timing.startMisc(); OdTvGeometryDataId tvPntCloudId = entity->appendPointCloud(pDataSource, &tvRes); if (!tvPntCloudId.isNull()) tvPntCloudId.openAsPointCloud()->setPointSize(m_properties->getPointSize()); timing.endMisc(); if (tvRes != tvOk) { if (rc) *rc = tvRes; return false; } if (m_properties->getUseSpatialTreeForSelection()) { OdTvGeometryDataIteratorPtr pGeometriesIter = entity->getGeometryDataIterator(); while (!pGeometriesIter->done()) { OdTvGeometryDataId geometryDataId = pGeometriesIter->getGeometryData(); if (geometryDataId.getType() == OdTv::kPointCloud) { OdTvPointCloudDataPtr pPointCloud = geometryDataId.openAsPointCloud(); pPointCloud->setUseSpatialTreeForSelection(m_properties->getUseSpatialTreeForSelection()); } pGeometriesIter->step(); } } } return true; } OdTvDatabaseId OdTvVisualizePointCloudFiler::loadFrom(const OdString& filePath, OdTvFilerTimeProfiling* pProfileRes, OdTvResult* rc) const { OdTvDatabaseId tvDbId; //check that time profiling is need bool bUseTimeProfiling = false; if (pProfileRes) bUseTimeProfiling = true; //prepare timing object OdTvFilerTimer timing(bUseTimeProfiling); timing.startTotal(); //generate model name OdString filename = OdTvDatabaseUtils::getFileNameFromPath(filePath); // create tv database OdTvFactoryId tvFactoryId = odTvGetFactory(); tvDbId = tvFactoryId.createDatabase(); OdTvDatabasePtr pDb = tvDbId.openObject(OdTv::kForWrite); // save filename to database user data OdTvDatabaseUtils::writeFileNameToTvDatabase(tvDbId, filename); //create tv model OdTvModelId tvModelId = pDb->createModel(filename); //prepare tv model for modifications OdTvModelPtr pTvModel = tvModelId.openObject(OdTv::kForWrite); try { OdPointCloudDataSourcePtr pDataSource = createPointCloudDataSource(filePath, rc); if (!pDataSource) return tvDbId; timing.startVectorizing(); OdGeExtents3d pointCloudExtents; if (!appendPointCloud(pTvModel, filePath, pDataSource, timing, rc, &pointCloudExtents)) return tvDbId; OdTvDatabaseUtils::createDeviceAndView(tvDbId, tvModelId, OdTvGsView::kWireframe); if (m_properties->getImportAsRcsPointCloud()) { OdTvDevicesIteratorPtr pDevicesIterator = pDb->getDevicesIterator(); OdTvGsDeviceId deviceId; while (!pDevicesIterator->done()) { deviceId = pDevicesIterator->getDevice(); if (deviceId.openObject()->getActive()) break; } OdTvGsViewId viewId = deviceId.openObject()->getActiveView(); if (!viewId.isNull()) viewId.openObject(OdTv::kForWrite)->zoomExtents(pointCloudExtents.minPoint(), pointCloudExtents.maxPoint(), true); } //set units if (!pTvModel.isNull()) { //get units OdTv::Units tvUnits = m_properties->getDefaultUnits(); pTvModel->setUnits(tvUnits, 1.); } if (m_properties->getNeedCDATree() && tvDbId.isValid()) { timing.startMisc(); if (!tvDbId.isNull()) createCommonDataAccessTree(tvDbId, filename); timing.endMisc(); if (pProfileRes) pProfileRes->setCDATreeCreationTime(OdInt64((timing.getMiscTime()) * 1000.)); } timing.endVectorizing(); } catch (...) { if (rc) *rc = tvCannotOpenFile; return tvDbId; } //////////////////////////////////////////////////////////////////// timing.endTotal(); ::odrxDynamicLinker()->unloadUnreferenced(); if (pProfileRes) { pProfileRes->setImportTime(OdInt64((timing.getTotalTime() - timing.getVectorizingTime()) * 1000.)); pProfileRes->setVectorizingTime(OdInt64((timing.getVectorizingTime()) * 1000.)); #if !defined(__APPLE__) pProfileRes->setTvTime(OdInt64(timing.getMiscTime() * 1000.)); #endif } if (rc) *rc = tvOk; return tvDbId; } OdRxDictionaryPtr OdTvVisualizePointCloudFiler::properties() { return m_properties; } OdTvDatabaseId OdTvVisualizePointCloudFiler::loadFrom(OdStreamBuf* pBuffer, OdTvFilerTimeProfiling* pProfileRes, OdTvResult* rc) const { return OdTvDatabaseId(); } OdTvDatabaseId OdTvVisualizePointCloudFiler::loadFrom(OdDbBaseDatabase* pDatabase, OdTvFilerTimeProfiling* pProfileRes, OdTvResult* rc) const { return OdTvDatabaseId(); } OdTvDatabaseId OdTvVisualizePointCloudFiler::generate(OdTvFilerTimeProfiling* pProfileRes) const { return OdTvDatabaseId(); } OdTvModelId OdTvVisualizePointCloudFiler::appendFrom(const OdTvDatabaseId& databaseId, const OdString& filePath, OdTvFilerTimeProfiling* pProfileRes, OdTvResult* rc) const { OdTvModelId tvModelId; if (rc) *rc = tvOk; OdTvDatabasePtr pDb = databaseId.openObject(OdTv::kForWrite); //check that we are in the real append mode OdTvGsDeviceId activeTvGsDeviceId; { OdTvDevicesIteratorPtr devicesIteratorPtr = pDb->getDevicesIterator(); while (!devicesIteratorPtr->done()) { activeTvGsDeviceId = devicesIteratorPtr->getDevice(); if (activeTvGsDeviceId.openObject()->getActive()) break; devicesIteratorPtr->step(); } } //check that time profiling is need bool bUseTimeProfiling = false; if (pProfileRes) bUseTimeProfiling = true; //prepare timing object OdTvFilerTimer timing(bUseTimeProfiling); timing.startTotal(); //generate model name OdString fileName = OdTvDatabaseUtils::getFileNameFromPath(filePath); // save filename to database user data OdTvDatabaseUtils::writeFileNameToTvDatabase(databaseId, fileName); // Generate model name OdString modelName = OdTvDatabaseUtils::generateModelName(databaseId, fileName); //create tv model tvModelId = pDb->createModel(modelName); //prepare tv model for modifications OdTvModelPtr pTvModel = tvModelId.openObject(OdTv::kForWrite); OdString fileExt = filePath.right(3); fileExt.makeUpper(); try { OdPointCloudDataSourcePtr pDataSource = createPointCloudDataSource(filePath, rc); if (!pDataSource) { if (rc) *rc = tvCannotOpenFile; return tvModelId; } timing.startVectorizing(); if (!appendPointCloud(pTvModel, filePath, pDataSource, timing, rc)) return tvModelId; //add model to view if (activeTvGsDeviceId.isNull()) OdTvDatabaseUtils::createDeviceAndView(databaseId, tvModelId, OdTvGsView::kWireframe); else { OdTvGsViewId viewId = activeTvGsDeviceId.openObject()->getActiveView(); if (!viewId.isNull()) viewId.openObject(OdTv::kForWrite)->addModel(tvModelId); } //apply transform if need const OdTvMatrix& pTransform = m_properties->getAppendTransform(); if (pTransform != OdTvMatrix::kIdentity) { OdTvDatabaseUtils::applyTransformToTheModel(databaseId, modelName, pTransform); } //set units if (!pTvModel.isNull()) { //get units OdTv::Units tvUnits = m_properties->getDefaultUnits(); pTvModel->setUnits(tvUnits, 1.); } if (m_properties->getNeedCDATree() && !databaseId.isNull()) { timing.startMisc(); createCommonDataAccessTree(databaseId, modelName); timing.endMisc(); if (pProfileRes) pProfileRes->setCDATreeCreationTime(OdInt64((timing.getMiscTime()) * 1000.)); } timing.endVectorizing(); } catch (...) { if (rc) *rc = tvCannotOpenFile; return tvModelId; } //////////////////////////////////////////////////////////////////// timing.endTotal(); ::odrxDynamicLinker()->unloadUnreferenced(); if (pProfileRes) { pProfileRes->setImportTime(OdInt64((timing.getTotalTime() - timing.getVectorizingTime()) * 1000.)); pProfileRes->setVectorizingTime(OdInt64((timing.getVectorizingTime()) * 1000.)); #if !defined(__APPLE__) pProfileRes->setTvTime(OdInt64(timing.getMiscTime() * 1000.)); #endif } return tvModelId; } OdTvModelId OdTvVisualizePointCloudFiler::appendFrom(const OdTvDatabaseId& databaseId, OdStreamBuf* pBuffer, OdTvFilerTimeProfiling* pProfileRes, OdTvResult* rc) const { return OdTvModelId(); } OdTvModelId OdTvVisualizePointCloudFiler::appendFrom(const OdTvDatabaseId& databaseId, OdDbBaseDatabase* pDatabase, OdTvFilerTimeProfiling* pProfileRes, OdTvResult* rc) const { return OdTvModelId(); } void OdTvVisualizePointCloudFiler::createCommonDataAccessTree(const OdTvDatabaseId& tvDbId, const OdString& strTreeName) const { OdTvDatabasePtr pDb = tvDbId.openObject(OdTv::kForWrite); OdTvCDATreePtr pTree = OdTvCDATree::createObject(); pTree->createTvDatabaseHierarchyTree(tvDbId, m_properties->getNeedCollectPropertiesInCDA()); //Add tree to the Tv database OdTvResult rc; OdTvCDATreeStorageId cdaTreeId = pDb->addCDATreeStorage(strTreeName, pTree, &rc); if (rc == tvAlreadyExistSameName) { OdUInt32 i = 1; while (rc != tvOk && i < MAX_CDATREENAME_GENERATION_ATTEMPTS) { OdString str; str.format(L"%s_%d", strTreeName.c_str(), i++); //not to fast but it is not a "bottle neck" cdaTreeId = pDb->addCDATreeStorage(str, pTree, &rc); } } //Add new CDA tree to the appropriate models OdTvModelsIteratorPtr modelsIterPtr = pDb->getModelsIterator(); if (!modelsIterPtr->done()) { OdTvModelPtr pModel = modelsIterPtr->getModel().openObject(); if (!pModel.isNull()) { pModel->setCDATreeStorage(cdaTreeId); } } } //***************************************************************************// // 'OdTvVisualizePointCloudFilerModule' methods implementation //***************************************************************************// ODRX_DEFINE_DYNAMIC_MODULE(OdTvVisualizePointCloudFilerModule); OdTvVisualizeFilerPtr OdTvVisualizePointCloudFilerModule::getVisualizeFiler() const { OdTvVisualizeFilerPtr pFiler = new OdTvVisualizePointCloudFiler(); return pFiler; } void OdTvVisualizePointCloudFilerModule::initApp() { // initialize the Visualize SDK odTvInitialize(); } void OdTvVisualizePointCloudFilerModule::uninitApp() { // Uninitialize the Visualize SDK odTvUninitialize(); }