#define STB_IMAGE_IMPLEMENTATION #define STB_IMAGE_WRITE_IMPLEMENTATION #include #include #include #include #include #include #include "stb_image.h" #include "stb_image_write.h" #include "bella_sdk/bella_engine.h" // Core rendering engine #include "bella_sdk/bella_scene.h" // Scene management #include "dl_core/dl_logging.h" // Logging utilities #include // For std::this_thread (C++ standard library) #include // For timing operations (C++ standard library) #include // For freopen #include std::string directory_path = "."; bool STOP = false; dl::bella_sdk::Engine engine; std::string fileToRender = ""; bool pendingRender = false; int width = 800; int height = 600; //void flip_image_horizontally(unsigned char* data, int width, int height, int channels); void flip_image_vertically(unsigned char* data, int width, int height, int channels); void sigend( int ) { std::cout << std::endl << "close file monitoring" << std::endl; STOP = true; } /* * MyEngineObserver Class * This class receives callbacks from the Bella rendering engine to track rendering progress. * It implements the EngineObserver interface and provides methods to: * - Handle render start/stop events * - Track rendering progress * - Handle error conditions * - Store and retrieve the current progress state */ struct MyEngineObserver : public dl::bella_sdk::EngineObserver { public: // Called when a rendering pass starts void onStarted(dl::String pass) override { //std::cout << "Started pass " << pass.buf() << std::endl; // dl::logInfo("Started pass %s", pass.buf()); } // Called to update the current status of rendering //void onStatus(String pass, String status) override //{ // logInfo("%s [%s]", status.buf(), pass.buf()); //} // Called to update rendering progress (percentage, time remaining, etc) void onProgress(dl::String pass, dl::bella_sdk::Progress progress) override { //std::cout << progress.toString().buf() << std::endl; setString(new std::string(progress.toString().buf())); //dl::logInfo("%s [%s]", progress.toString().buf(), pass.buf()); } //void onImage(String pass, Image image) override //{ // logInfo("We got an image %d x %d.", (int)image.width(), (int)image.height()); //} // Called when an error occurs during rendering void onError(dl::String pass, dl::String msg) override { //dl::logError("%s [%s]", msg.buf(), pass.buf()); } // Called when a rendering pass completes void onStopped(dl::String pass) override { // dl::logInfo("Stopped %s", pass.buf()); std::atomic_bool active_render = false; } // Returns the current progress as a string std::string getProgress() const { std::string* currentProgress = progressPtr.load(); if (currentProgress) { return *currentProgress; } else { return ""; } } // Cleanup resources in destructor ~MyEngineObserver() { setString(nullptr); } private: // Thread-safe pointer to current progress string std::atomic progressPtr{ nullptr }; // Helper function to safely update the progress string void setString(std::string* newStatus) { std::string* oldStatus = progressPtr.exchange(newStatus); delete oldStatus; // Clean up old string if it exists } }; MyEngineObserver engineObserver; //void render_thread(dl::bella_sdk::Engine& engine, MyEngineObserver& engineObserver, std::string selected_name) { // // Construct the full path once // dl::String fullPath = dl::String(directory_path.c_str()) + "/" + dl::String(selected_name.c_str()); // // // Load and then Read using the SAME full path // engine.loadScene(fullPath); // if (!engine.scene().read(fullPath)) { // std::cout << "Failed to read scene: " << selected_name << std::endl; // return; // } // // //dl::String belPath = dl::String(directory_path.c_str()) + "\\"; // //engine.loadScene(belPath + dl::String(selected_name.c_str())); // // //if (!engine.scene().read(dl::String(selected_name.c_str()))) return; // // dl::bella_sdk::Node bPass = engine.scene().beautyPass(); // // // Setup the outputImagePath node as we did in the synchronous version // dl::bella_sdk::Node pathNode = engine.scene().createNode("outputImagePath"); // pathNode["dir"] = dl::String(directory_path.c_str()); // pathNode["name"] = "render_result"; // pathNode["ext"] = ".png"; // // In render_thread // pathNode["dir"] = dl::String("../renders/"); // bPass["overridePath"] = pathNode; // bPass["timeLimit"] = 30.0f; // // engine.start(); // // // While rendering, sleep and then tell the UI to refresh // while (engine.rendering()) { // std::this_thread::sleep_for(std::chrono::milliseconds(500)); // // This is the key to seeing progress while it's "asynchronous" // //screen.PostEvent(ftxui::Event::Custom); // //std::cout << "rendering..."; // } // std::this_thread::sleep_for(std::chrono::milliseconds(1000)); // Finalize save //} void do_sync_render(std::string fileToRender) { // This removes any trailing slashes from directory_path to prevent the \/ issue if (!directory_path.empty() && (directory_path.back() == '/' || directory_path.back() == '\\')) { directory_path.pop_back(); } // 1. Force the path to look in the 'test' folder specifically std::string fullPathStr = directory_path + "/" + fileToRender; dl::String fullPath(fullPathStr.c_str()); std::cout << ">>> ATTEMPTING TO READ: " << fullPathStr << std::endl; engine.loadScene(fullPath); if (!engine.scene().read(fullPath)) { std::cout << ">>> ERROR: Bella could not read scene file!" << std::endl; return; } // 2. Setup the output path using a relative path that goes OUTSIDE 'test' dl::bella_sdk::Node pathNode = engine.scene().createNode("outputImagePath"); pathNode["dir"] = dl::String(directory_path.c_str()); //pathNode["dir"] = dl::String("."); pathNode["name"] = dl::String("render_result"); pathNode["ext"] = dl::String(".png"); // Get the global settings node instead of the beautyPass node dl::bella_sdk::Node settings = engine.scene().settings(); dl::bella_sdk::Node cam = settings["camera"].asNode(); auto inputs = settings.inputs(); // This gets all valid attribute names for (const auto& input : inputs) { std::cout << "Valid attribute: " << input.name() << std::endl; } if (cam) { auto inputs = cam.inputs(); for (const auto& input : inputs) { // This will print every valid attribute name for YOUR camera std::cout << "Camera Attribute: " << input.name() << std::endl; } if (cam) { std::cout << ">>> Set resolution to " << width <<"x" << height << " on camera : " << cam.name() << std::endl; float fWidth = static_cast(width); float fHeight = static_cast(height); cam["resolution"] = dl::Vec2{ fWidth, fHeight }; } } else { std::cout << ">>> ERROR: No camera found in settings!" << std::endl; } dl::bella_sdk::Node bPass = engine.scene().beautyPass(); bPass["overridePath"] = pathNode; std::cout << ">>> STARTING BELLA ENGINE..." << std::endl; engine.start(); // 3. Monitor progress while (engine.rendering()) { std::this_thread::sleep_for(std::chrono::milliseconds(500)); std::cout << "rendering..." << std::endl; } std::this_thread::sleep_for(std::chrono::milliseconds(1000)); // Give Windows time to finish the file write std::cout << ">>> RENDER FINISHED. Saved in current directory" << std::endl; } void render_thread(dl::bella_sdk::Engine& engine, MyEngineObserver& engineObserver, std::string selected_name) { // This removes any trailing slashes from directory_path to prevent the \/ issue if (!directory_path.empty() && (directory_path.back() == '/' || directory_path.back() == '\\')) { directory_path.pop_back(); } // 1. Force the path to look in the 'test' folder specifically std::string fullPathStr = directory_path + "/" + selected_name; dl::String fullPath(fullPathStr.c_str()); std::cout << ">>> ATTEMPTING TO READ: " << fullPathStr << std::endl; engine.loadScene(fullPath); if (!engine.scene().read(fullPath)) { std::cout << ">>> ERROR: Bella could not read scene file!" << std::endl; return; } // 2. Setup the output path using a relative path that goes OUTSIDE 'test' // This assumes your 'renders' folder is in x64/release/renders dl::bella_sdk::Node pathNode = engine.scene().createNode("outputImagePath"); pathNode["dir"] = dl::String("../renders/"); pathNode["name"] = dl::String("render_result"); pathNode["ext"] = dl::String(".png"); dl::bella_sdk::Node bPass = engine.scene().beautyPass(); bPass["overridePath"] = pathNode; std::cout << ">>> STARTING BELLA ENGINE..." << std::endl; engine.start(); // 3. Monitor progress while (engine.rendering()) { std::this_thread::sleep_for(std::chrono::milliseconds(500)); std::cout << "rendering..." << std::endl; } std::this_thread::sleep_for(std::chrono::milliseconds(1000)); // Give Windows time to finish the file write std::cout << ">>> RENDER FINISHED. Check x64/release/renders/" << std::endl; } /// Processes a file action class UpdateListener : public efsw::FileWatchListener { public: UpdateListener() {} std::string getActionName( efsw::Action action ) { switch ( action ) { case efsw::Actions::Add: return "Add"; case efsw::Actions::Modified: return "Modified"; case efsw::Actions::Delete: return "Delete"; case efsw::Actions::Moved: return "Moved"; default: return "Bad Action"; } } void handleFileAction( efsw::WatchID watchid, const std::string& dir, const std::string& filename, efsw::Action action, std::string oldFilename = "" ) override { std::cout << "Watch ID " << watchid << " DIR (" << dir + ") FILE (" + ( oldFilename.empty() ? "" : "from file " + oldFilename + " to " ) + filename + ") has event " << getActionName( action ) << std::endl; if (action == efsw::Actions::Add || action == efsw::Actions::Modified) { directory_path = dir; std::string fullPath = dir + filename; int width, height, channels; if (filename.substr(filename.length() - 3) == "bsz" ) { std::cout << "Converting " << filename << " to a PNG" << std::endl; // 2. Start the thread and DETACH it // Note: We pass 'screen' as a reference so render_thread can call PostEvent fileToRender = filename; pendingRender = true; //std::thread(render_thread, std::ref(engine), std::ref(engineObserver), filename).detach(); } } } }; efsw::WatchID handleWatchID( efsw::WatchID watchid ) { switch ( watchid ) { case efsw::Errors::FileNotFound: case efsw::Errors::FileRepeated: case efsw::Errors::FileOutOfScope: case efsw::Errors::FileRemote: case efsw::Errors::WatcherFailed: case efsw::Errors::Unspecified: { std::cout << efsw::Errors::Log::getLastErrorLog().c_str() << std::endl; break; } default: { std::cout << "Added WatchID: " << watchid << std::endl; } } return watchid; } void flip_image_vertically(unsigned char* data, int width, int height, int channels) { // ... (your vertical flip implementation from before) int row_stride = width * channels; unsigned char* temp_row = new unsigned char [row_stride]; for (int y = 0; y < height / 2; ++y) { unsigned char* top_row = data + y * row_stride; unsigned char* bottom_row = data + (height - 1 - y) * row_stride; std::memcpy(temp_row, top_row, row_stride); std::memcpy(top_row, bottom_row, row_stride); std::memcpy(bottom_row, temp_row, row_stride); } delete[] temp_row; } //int main( int argc, char** argv ) { #include "dl_core/dl_main.inl" #include "dl_core/dl_args.h" int DL_main(dl::Args& args) { signal( SIGABRT, sigend ); signal( SIGINT, sigend ); signal( SIGTERM, sigend ); // 1. Initialize engine and observer locally within the logic block engine.subscribe(&engineObserver); std::cout << "Press ^C to exit demo" << std::endl; bool commonTest = true; bool useGeneric = false; std::string path; args.add("wd", "watchdir", "", "mode file watch"); args.add("w", "width", "800", "render width"); args.add("h", "height", "600", "render height"); /*if (argc >= 2) { path = std::string( argv[1] ); if ( efsw::FileSystem::isDirectory( path ) ) { commonTest = false; } if ( argc >= 3 ) { if ( std::string( argv[2] ) == "true" ) { useGeneric = true; } } }*/ //std::cout << "args: " << args. << std::endl; if (args.have("--watchdir")) { path = args.value("--watchdir").buf(); std::cout << "Watching path: " << path << std::endl; if (efsw::FileSystem::isDirectory(path)) { commonTest = false; useGeneric = true; // Force generic for testing } } if (args.have("--width")) { width = std::stoi(args.value("--width").buf()); } if (args.have("--height")) { height = std::stoi(args.value("--height").buf()); } UpdateListener* ul = new UpdateListener(); /// create the file watcher object efsw::FileWatcher fileWatcher( useGeneric ); fileWatcher.followSymlinks( false ); fileWatcher.allowOutOfScopeLinks( false ); //std::cout << "commonTest: " << commonTest << std::endl; if ( commonTest ) { std::string CurPath( efsw::System::getProcessPath() ); std::cout << "CurPath: " << CurPath.c_str() << std::endl; /// starts watching fileWatcher.watch(); /// add a watch to the system handleWatchID( fileWatcher.addWatch( CurPath , ul, true ) ); } else { if ( fileWatcher.addWatch( path, ul, true ) > 0 ) { fileWatcher.watch(); /// add a watch to the system handleWatchID(fileWatcher.addWatch(path, ul, true)); std::cout << "Watching directory: " << path.c_str() << std::endl; directory_path = path; if ( useGeneric ) { std::cout << "Using generic backend watcher" << std::endl; } } else { std::cout << "Error trying to watch directory: " << path.c_str() << std::endl; std::cout << efsw::Errors::Log::getLastErrorLog().c_str() << std::endl; } } while ( !STOP ) { if (pendingRender) { // We are now in the main thread. // 1. Give the OS a moment to release file locks std::this_thread::sleep_for(std::chrono::milliseconds(500)); // 2. Call render logic (not in a thread) // Note: You'll need to move render_thread logic into a regular function do_sync_render(fileToRender); pendingRender = false; } efsw::System::sleep( 100 ); } return 0; }