/////////////////////////////////////////////////////////////////////////////// // 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 #include #include #include #include #include #include "OdVisualizeQtAppWindow.h" #include "OdVisualizeQtAppDialogs.h" #include #include "TvFactory.h" void clientExecution( OdTvStreamingClientPtr pClient ) { if( !pClient->startClient( "127.0.0.1", "27015" ) ) { return; } } OdTvMainWindow::OdTvMainWindow( QWidget* parent ) : QMainWindow( parent ) { //Create OdTvQtRenderingWidget and QPushButton to open .VSFX QWidget* wdg = new QWidget(this); QVBoxLayout* vlay = new QVBoxLayout( wdg ); //Create OdTvQtRenderingWidget m_pWidget = new OdTvQtRenderingWidget(); vlay->addWidget( m_pWidget ); //Create button QPushButton* btn1 = new QPushButton( "Streaming setup" ); vlay->addWidget( btn1 ); connect( btn1, &QPushButton::clicked, this, &OdTvMainWindow::on_pushButton_clicked ); QPushButton* pBtn = new QPushButton( "Regen" ); connect( pBtn, &QPushButton::clicked, this, &OdTvMainWindow::onRegen ); vlay->addWidget( pBtn ); pBtn = new QPushButton( "Update" ); connect( pBtn, &QPushButton::clicked, this, &OdTvMainWindow::onUpdate ); vlay->addWidget( pBtn ); wdg->setLayout( vlay ); setCentralWidget( wdg ); //Create and run client m_pClient = OdTvStreamingClient::createClient(); m_clientThread = std::thread( clientExecution, m_pClient ); m_pReceivingReactor = new Reactor( this ); } void OdTvMainWindow::onRegen() { OdTvGsDeviceId devId = m_pWidget->renderingDevice(); if( !devId.isNull() ) { OdTvGsDevicePtr pDev = devId.openObject( OdTv::kForWrite ); pDev->regen( OdTvGsDevice::kRegenAll ); m_pWidget->update(); } } OdTvMainWindow::OdTvStreamingLimitManager::OdTvStreamingLimitManager() : m_nMemoryLimit( 0 ){} OdTvMainWindow::OdTvStreamingLimitManager::~OdTvStreamingLimitManager() {} class OdTvAllocatorLimitManager : public OdTvMainWindow::OdTvStreamingLimitManager { public: OdTvAllocatorLimitManager() : OdTvStreamingLimitManager() {} virtual ~OdTvAllocatorLimitManager() {} virtual OdTvLimitManager::MemoryLimitControlResult checkMemoryUsage( OdUInt8 nReason, OdUInt64 nApproximateMemoryUsage = 0 ) const { if( m_nMemoryLimit == 0 ) return OdTvLimitManager::kPassed; OdUInt64 memUsage = GetMemoryUsageInternalInfo( 0 ); if( memUsage + nApproximateMemoryUsage < m_nMemoryLimit ) { return OdTvLimitManager::kPassed; } return OdTvLimitManager::kNotPassed; } }; class OdTvWindowsLimitManager : public OdTvMainWindow::OdTvStreamingLimitManager { HANDLE m_processId; public: OdTvWindowsLimitManager() : OdTvStreamingLimitManager() { m_processId = GetCurrentProcess(); } virtual ~OdTvWindowsLimitManager() {} virtual OdTvLimitManager::MemoryLimitControlResult checkMemoryUsage( OdUInt8 nReason, OdUInt64 nApproximateMemoryUsage = 0 ) const { if( m_nMemoryLimit == 0 ) return OdTvLimitManager::kPassed; PROCESS_MEMORY_COUNTERS pmc; if( GetProcessMemoryInfo( m_processId, &pmc, sizeof( pmc ) ) ) { OdUInt64 workingSetBytes = pmc.WorkingSetSize; if( workingSetBytes + nApproximateMemoryUsage < m_nMemoryLimit ) { return OdTvLimitManager::kPassed; } return OdTvLimitManager::kNotPassed; } else { ODA_FAIL(); } return OdTvLimitManager::kPassed; } }; OdTvMainWindow::OdTvStreamingLimitManager* createLimitManager() { OdUInt64 tmp = GetMemoryUsageInternalInfo( 0 ); if( tmp != 0 ) //allocator-based manager { return new OdTvAllocatorLimitManager(); } else //GetProcessMemoryInfo-based manager { return new OdTvWindowsLimitManager(); } } void OdTvMainWindow::onUpdate() { OdTvGsDeviceId devId = m_pWidget->renderingDevice(); if( !devId.isNull() ) { m_pWidget->update(); } } void OdTvMainWindow::on_pushButton_clicked() { OdTvStreamingClient::Status clStatus = m_pClient->clientStatus(); if( clStatus != OdTvStreamingClient::Status::kOk ) { if( clStatus == OdTvStreamingClient::Status::kNotConnected ) { QMessageBox::information( this, tr( "Connection warning" ), tr( "Connection is not established, try later" ), QMessageBox::Ok ); return; } else { QMessageBox::critical( this, tr( "Connection error" ), tr( "Connection error" ), QMessageBox::Ok ); this->close(); return; } } cancelActiveCommands(); OdTvQtStreamingDialog dlg( m_pClient ); dlg.resize( 400, 300 ); QDialog::DialogCode retCode = (QDialog::DialogCode)dlg.exec(); if( retCode == QDialog::Rejected ) { reset( false ); return; } if( dlg.requestedFile().isEmpty() || ( dlg.isAssemblyMode() && dlg.assemblyInput().empty() ) ) { reset( false ); return; } reset( true ); OdUInt64 nLimit = 0; bool bLowMemMode = dlg.lowMemoryModeOptions( nLimit ); m_pDbReceiver = odTvGetFactory().createDatabaseReceiver(); if( dlg.isAssemblyMode() ) { m_pDbReceiver->enablePartialMode( true, m_pReceivingReactor ); OdTvDatabaseReceiverManagementOptions opts; opts.setObjectsUnloading( bLowMemMode ); if( nLimit != 0 ) { if( !m_pLimitManager ) { m_pLimitManager = createLimitManager(); } m_pLimitManager->setMemoryLimit( nLimit ); opts.setLimitManager( m_pLimitManager ); } else { opts.setLimitManager( nullptr ); } m_pDbReceiver->setManagementOptions( opts ); m_pAssemblyReactor = new AssemblyReactor( this ); m_pDbReceiver->createAssembly( dlg.assemblyInput(), m_pAssemblyReactor ); } else { if( dlg.isPartialMode() ) { m_pReceivingReactor->setCurrentFile( dlg.requestedFile() ); m_pDbReceiver->enablePartialMode( true, m_pReceivingReactor ); OdTvDatabaseReceiverManagementOptions opts; opts.setObjectsUnloading( bLowMemMode ); if( nLimit != 0 ) { if( !m_pLimitManager ) { m_pLimitManager = createLimitManager(); } m_pLimitManager->setMemoryLimit( nLimit ); opts.setLimitManager( m_pLimitManager ); } else { opts.setLimitManager( nullptr ); } m_pDbReceiver->setManagementOptions( opts ); } else { m_pReceivingReactor->setCurrentFile( dlg.requestedFile() ); m_pDbReceiver->enablePartialMode( false, m_pReceivingReactor ); } OdUInt64 nCmd = m_pClient->requestFile( dlg.requestedFile() ); if( nCmd == (OdUInt64)( -1 ) ) { QMessageBox::warning( this, tr( "File request error" ), tr( "File request error" ), QMessageBox::Ok ); return; } CommandPtr pCommand = std::make_shared(); pCommand->cmdID = nCmd; pCommand->bPartial = dlg.isPartialMode();; pCommand->fileName = dlg.requestedFile(); m_commands.insert( { nCmd, pCommand } ); //Let's implement client processing through QTimer::singleShot askClient(); } return; //Reset widget rendering device m_pWidget->setRenderingDevice( OdTvGsDeviceId() ); //Read file QString fileName = QFileDialog::getOpenFileName( this, tr( "Open VSFX" ), ".", tr( "Visualize File Format (*.vsfx)" ) ); m_dbId = odTvGetFactory().readVSFX( fileName.toStdWString().c_str() ); if( m_dbId.isNull() ) { QMessageBox::critical( nullptr, tr( "Error" ), tr( "Can not open file" ), QMessageBox::Ok ); return; } OdTvGsDeviceId deviceId = m_dbId.openObject()->getDevicesIterator()->getDevice(); if( deviceId.isNull() ) { QMessageBox::critical( nullptr, tr( "Error" ), tr( "Can not find device" ), QMessageBox::Ok ); return; } //Set widget rendering device m_pWidget->setRenderingDevice( deviceId ); } void OdTvMainWindow::askClient( int delay ) { if( !m_bTimerIsOn ) { QTimer::singleShot( delay, this, &OdTvMainWindow::onTimer ); m_bTimerIsOn = true; } } void OdTvMainWindow::closeEvent( QCloseEvent* event ) { if( !m_pDbReceiver.isNull() ) { m_pDbReceiver->reset(); m_pDbReceiver.release(); } if( m_pAssemblyReactor ) { delete m_pAssemblyReactor; m_pAssemblyReactor = nullptr; } if( m_pReceivingReactor ) { delete m_pReceivingReactor; m_pReceivingReactor = nullptr; } m_pClient->closeConnection(); m_clientThread.join(); if( m_pLimitManager ) { delete m_pLimitManager; m_pLimitManager = nullptr; } } void OdTvMainWindow::onTimer() { if( m_commands.empty() ) { m_bTimerIsOn = false; if( m_pWidget && !m_pWidget->renderingDevice().isNull() ) { m_pWidget->renderingDevice().openObject()->update( nullptr, true ); } return; } m_bTimerIsOn = false; m_cmdToRemove.clear(); bool bUpdateDatabase = false; for( const auto& it : m_commands ) { OdUInt64 cmdID = it.second->cmdID; if( m_pClient->hasResponse( cmdID ) ) { OdTvStreamingClientDataPtr pData = m_pClient->getResponse( cmdID ); if( pData->type() != OdTvStreamingClientData::Type::kBinData ) { QMessageBox::warning( this, tr( "File request error" ), tr( "Response parsing error" ), QMessageBox::Ok ); continue; } if( pData->status() == OdTvStreamingClientData::Status::kError ) { QMessageBox::warning( this, tr( "File request error" ), tr( "File access error" ), QMessageBox::Ok ); m_cmdToRemove.push_back( cmdID ); continue; } OdTvStreamingClientBinaryData* pBinData = dynamic_cast( pData.get() ); OdUInt32 nSz = pBinData->buffer.size(); if( it.second->requestID == (OdUInt64)(-1) ) m_pDbReceiver->doReceive( (OdUInt8*)( pBinData->buffer.asArrayPtr() ), nSz ); else { m_pDbReceiver->doReceiveResponse( it.second->requestID, (OdUInt8*)( pBinData->buffer.asArrayPtr() ), nSz ); } if( pData->status() == OdTvStreamingClientData::Status::kComplete ) { if( it.second->requestID != (OdUInt64)( -1 ) ) { m_pDbReceiver->onRequestResponseComplete( it.second->requestID ); } m_cmdToRemove.push_back( cmdID ); } OdString str; if( nSz < 1024 ) { str.format( OD_T( "Received %d bytes\n" ), nSz ); } else if( nSz < 1024 * 1024 ) { str.format( OD_T( "Received %f KB\n" ), (float)nSz/ (1024.f) ); } else { str.format( OD_T( "Received %f MB\n" ), (float)nSz / ( 1024.f*1024.f ) ); } printf( "%s\n", str.operator const char *() ); bUpdateDatabase = true; } } if( bUpdateDatabase ) { if( m_pDbReceiver->isDatabaseRenderable() ) { if( m_dbId.isNull() ) { m_dbId = m_pDbReceiver->database(); OdTvDatabasePtr pDb = m_dbId.openObject(); if( !m_dbId.openObject()->getDevicesIterator().isNull() ) { OdTvGsDeviceId deviceId = m_dbId.openObject()->getDevicesIterator()->getDevice(); if( deviceId.isNull() ) { QMessageBox::critical( nullptr, tr( "Error" ), tr( "Can not find device" ), QMessageBox::Ok ); return; } m_pWidget->setRenderingDevice( deviceId ); } } m_pWidget->update(); } } for( unsigned i = 0; i < m_cmdToRemove.size(); ++i ) { m_commands.erase( m_cmdToRemove[ i ] ); } m_cmdToRemove.clear(); if( !m_commands.empty() ) { askClient(); } } void OdTvMainWindow::cancelActiveCommands() { for( const auto& it : m_commands ) { m_pClient->cancelCommand( it.second->cmdID ); } m_commands.clear(); } bool OdTvMainWindow::cancelRequest( OdUInt64 requestID ) { for( auto it = m_commands.cbegin(); it != m_commands.cend(); ++it ) { if( it->second->requestID == requestID ) { m_pClient->cancelCommand( it->second->cmdID ); m_cmdToRemove.push_back( it->second->cmdID ); askClient(); return true; } } return false; } bool OdTvMainWindow::cancelRequest( const OdString& file ) { for( auto it = m_commands.cbegin(); it != m_commands.cend(); ++it ) { if( it->second->fileName == file ) { m_pClient->cancelCommand( it->second->cmdID ); m_cmdToRemove.push_back( it->second->cmdID ); askClient(); return true; } } return false; } void OdTvMainWindow::reset( bool bResetDB ) { cancelActiveCommands(); if( !m_pDbReceiver.isNull() ) { m_pDbReceiver->reset(); m_pDbReceiver.release(); } if( bResetDB ) { m_pWidget->setRenderingDevice( OdTvGsDeviceId() ); if( !m_dbId.isNull() ) { odTvGetFactory().removeDatabase( m_dbId ); } m_dbId.setNull(); } } bool OdTvMainWindow::requestFile( OdUInt64 requestID, const OdString& file, OdUInt32 nParts, const OdTvMessage::FilePart* pParts ) { if( nParts == 0 ) { OdUInt64 nCmd = m_pClient->requestFile( file ); if( nCmd == (OdUInt64)( -1 ) ) { QMessageBox::warning( this, tr( "File request error" ), tr( "File request error" ), QMessageBox::Ok ); return false; } CommandPtr pCommand = std::make_shared(); pCommand->cmdID = nCmd; pCommand->bPartial = false; pCommand->fileName = file; m_commands.insert( { nCmd, pCommand } ); askClient(); return true; } OdVector< OdTvMessage::FilePart > parts; parts.resize( nParts ); memcpy( parts.asArrayPtr(), pParts, sizeof( OdTvMessage::FilePart ) * nParts ); OdUInt64 nCmd = m_pClient->requestFileParts( file, parts ); if( nCmd == (OdUInt64)( -1 ) ) { QMessageBox::warning( this, tr( "File request error" ), tr( "File request error" ), QMessageBox::Ok ); return false; } CommandPtr pCommand = std::make_shared(); pCommand->cmdID = nCmd; pCommand->bPartial = true; pCommand->fileName = file; pCommand->requestID = requestID; m_commands.insert( { nCmd, pCommand } ); askClient(); return true; } //Reactor OdTvMainWindow::Reactor::Reactor( OdTvMainWindow* pWindow ) { m_pWindow = pWindow; } bool OdTvMainWindow::Reactor::onServicePartReceived( bool bHasPartialIndex ) { return m_pWindow->cancelRequest( m_currentFile ); } void OdTvMainWindow::Reactor::onRequest( OdUInt32 requestID, OdUInt32 nRecCount, const OdTvRequestRecord* pRecords ) { m_pWindow->requestFile( requestID, m_currentFile, nRecCount, reinterpret_cast< const OdTvMessage::FilePart* >( pRecords ) ); } void OdTvMainWindow::Reactor::onRequestResourceFile( OdUInt32 requestID, const OdString& fileName, OdUInt64 offset, OdUInt64 size ) { OdTvMessage::FilePart part = OdTvMessage::FilePart{ offset, size }; m_pWindow->requestFile( requestID, fileName, 1, &part ); } void OdTvMainWindow::Reactor::onRequestResourceFile( OdUInt32 requestID, const OdString& fileName, OdUInt32 nRecCount, const OdTvRequestRecord* pRecords ) { OdString file = fileName; m_pWindow->requestFile( requestID, file, nRecCount, reinterpret_cast( pRecords ) ); } void OdTvMainWindow::Reactor::onRequestResponseParsed( OdUInt32 requestID ) { } void OdTvMainWindow::Reactor::onRequestAborted( OdUInt32 requestID ) { m_pWindow->cancelRequest( requestID ); } //Assembly reactor OdTvMainWindow::AssemblyReactor::AssemblyReactor( OdTvMainWindow* pWindow ) : m_pWindow( pWindow ) { } bool OdTvMainWindow::AssemblyReactor::onCreateAssembly( OdTvAssemblyTemplate& devices ) const { OdTvQtAssemblyTreeDialog dlg( devices ); dlg.resize( 400, 300 ); QDialog::DialogCode retCode = (QDialog::DialogCode)dlg.exec(); if( retCode == QDialog::Accepted ) { for( auto& devIt : devices ) { for( auto& viewIt : devIt.views ) { viewIt.viewParams.bZoomToExtents = true; } } } return retCode == QDialog::Accepted; } bool OdTvMainWindow::AssemblyReactor::onFileParsed( const OdString& databasePath, OdVector& models ) const { return true; /*OdString title = OD_T("Models from "); title += databasePath; OdTvQtAssemblyStringSelectionDialog dlg( OD_T( "Models selection" ), title, models ); dlg.resize( 400, 300 ); QDialog::DialogCode retCode = (QDialog::DialogCode)dlg.exec(); if( retCode == QDialog::Accepted && !dlg.selectedResult().empty() ) { models.clear(); for( const auto& it : dlg.selectedResult() ) { models.push_back( it ); } return true; } return false;*/ } void OdTvMainWindow::AssemblyReactor::onAssemblyCreated( OdTvDatabaseId dbId ) const { if( dbId.isNull() ) return; m_pWindow->m_dbId = dbId; if( !m_pWindow->m_dbId.openObject()->getDevicesIterator().isNull() ) { OdTvGsDeviceId deviceId = m_pWindow->m_dbId.openObject()->getDevicesIterator()->getDevice(); if( deviceId.isNull() ) { QMessageBox::critical( nullptr, tr( "Error" ), tr( "Can not find device" ), QMessageBox::Ok ); return; } m_pWindow->m_pWidget->setRenderingDevice( deviceId ); } m_pWindow->m_pWidget->update(); }