//***************************************************************************************** // Truevision - a 3d modeler for gnome and povray // // povpreview.cc // // Vincent LE PRINCE // Copyright (C) 2000-2005 Vincent LE PRINCE // This file is part of the TRUEVISION Package // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ //******************************************************************************************* #include #include #include #include #include #include #include "include/povpreview.h" #include "include/dlgutils.h" #include "include/preferences.h" #include #include #include "config.h" #include "include/scene.h" #include "include/tvio.h" // Predefined objects for material preview const int predef_obj_num = 7; const char *predef_obj_list[predef_obj_num] = { N_("Sphere"), N_("Box"), N_("Cone"), N_("Ground"), N_("Sky sphere"), N_("Fisheye sky"), N_("Truncated sphere") }; // Debug stream //ofstream debug( "debug.txt" ); //************************************************************** // Constructor //************************************************************** MaterialPreview::MaterialPreview( app_objs *appref ) { app_ref = appref; PREF_DEF widget = NULL; preview_widget = NULL; image = NULL; current_material = NULL; start_button = NULL; progress_bar = NULL; pipe_console_name = NULL; predef_sizes = NULL; render_status = false; // Image & render options width = new TvWidget_int( N_("Width"), "W", NULL, app_ref, 640 ); height = new TvWidget_int( N_("Height"), "H", NULL, app_ref, 480 ); antialias = new TvWidget_bool( N_("Antialiasing"), "ANTI", NULL, app_ref, true ); // Predefined scenes widgets predef_scene = new TvWidget_option_combo( N_("Object"), "OBJ",NULL, app_ref ); predef_scene->set_list( predef_obj_list, predef_obj_num, pref->preview_object->value() ); render_wall = new TvWidget_bool( N_("Render wall"), "WALL", NULL, app_ref, false ); render_wall->set( pref->preview_wall->value() ); render_floor = new TvWidget_bool( N_("Render floor"), "FLOOR", NULL, app_ref, true ); render_floor->set( pref->preview_floor->value() ); render_sky = new TvWidget_bool( N_("Render sky"), "SKY", NULL, app_ref, false ); render_sky->set( pref->preview_sky->value() ); } //***************************************************** // Destructor //***************************************************** MaterialPreview::~MaterialPreview() { if ( image != NULL ) delete image; if ( predef_sizes != NULL ) delete predef_sizes; delete width; delete height; delete antialias; delete predef_scene; delete render_wall; delete render_floor; delete render_sky; } //************************************************************** // Show image as widget // // Get the preview widget //************************************************************** const int predef_sizes_num = 5; const gchar *predef_sizes_lab[predef_sizes_num] = { "80x80", "140x140", "200x200", "300x300", "400x400" }; void MaterialPreview::get_widget( GtkWidget *box, bool tt, bool for_save_dialog ) { if ( ! for_save_dialog ) { // The preview widget widget = gtk_frame_new(NULL); gtk_box_pack_start( GTK_BOX(box), widget, FALSE, FALSE, 5 ); preview_widget = gtk_image_new( ); gtk_container_add( GTK_CONTAINER(widget), preview_widget ); // The progress bar progress_bar = gtk_progress_bar_new(); gtk_box_pack_start( GTK_BOX(box), progress_bar, FALSE, FALSE, 5 ); gtk_progress_bar_update( GTK_PROGRESS_BAR(progress_bar), 0 ); gtk_progress_set_show_text( GTK_PROGRESS(progress_bar), TRUE ); gtk_progress_set_text_alignment( GTK_PROGRESS(progress_bar), 0.5, 0.3 ); // Start button start_label = gtk_label_new( _("Preview") ); start_button = gtk_button_new(); gtk_container_add( GTK_CONTAINER(start_button), start_label ); gtk_box_pack_start( GTK_BOX(box), start_button, FALSE, FALSE, 5 ); gtk_signal_connect( GTK_OBJECT(start_button), "clicked", GTK_SIGNAL_FUNC(sign_matpreview_start_clicked), this ); // Predefined sizes PREF_DEF predef_sizes = new TvWidget_option_combo( _("Size"), NULL, NULL, app_ref ); predef_sizes->set_list( predef_sizes_lab, predef_sizes_num, pref->preview_size->value() ); predef_sizes->get_widget( box, tt ); antialias->set( pref->preview_antialias->value() ); } // Control widgets predef_scene->get_widget( box, tt ); render_wall->get_widget( box, tt ); render_floor->get_widget( box, tt ); render_sky->get_widget( box, tt ); antialias->get_widget( box, tt ); } //************************************************************** // Flush //************************************************************** void MaterialPreview::flush() { predef_scene->flush(); render_wall->flush(); render_floor->flush(); render_sky->flush(); antialias->flush(); } //******************************************************** // Start clicked // // start button callback //******************************************************** void MaterialPreview::start_clicked() { if ( pipe_console_name == NULL ) { // Start current_material->flush_material(); flush(); gtk_label_set_text( GTK_LABEL(start_label), _("Cancel") ); start_preview( current_material, false, false ); } else { // Stop modal = true; stop_preview( true ); modal = false; } } //************************************************************** // Start preview // // start the preview process //************************************************************** bool MaterialPreview::start_preview( Material *mat, bool save, bool ismodal ) { if ( mat == NULL ) return false; if ( mat->get_type() != TV_MAT_POV ) return false; modal = ismodal; scene_file = get_scene_file( mat ); if ( scene_file == NULL ) { app_warning( _("Cannot create temporary scene file ") ); return false; } render_status = false; //debug << "\nSetting size !"; //debug.flush(); // Image buffer if ( image != NULL ) delete image; image = NULL; image_size = width->value() * height->value() * 3; image = new char[image_size]; //debug << "\nLaunching preview !"; //debug.flush(); // Execution de povray if ( render_preview( scene_file ) == false ) { app_warning( _("Cannot execute povray !") ); stop_render_preview(); unlink( scene_file ); delete scene_file; return false; } //debug << "\nCreating threads !"; //debug.flush(); // Thread de lecture de l'image pthread_attr_t thread_attr; pthread_attr_init( &thread_attr ); pthread_attr_setdetachstate( &thread_attr, PTHREAD_CREATE_DETACHED ); if( pthread_create( &output_thread, &thread_attr, reading_image_thread, this ) ) { app_warning( _("Cannot create image reading thread !") ); stop_render_preview(); unlink( scene_file ); delete scene_file; return false; } // Thread de lecture de la console pthread_attr_t thread_attr2; pthread_attr_init( &thread_attr2 ); pthread_attr_setdetachstate( &thread_attr2, modal ? PTHREAD_CREATE_JOINABLE : PTHREAD_CREATE_DETACHED ); if( pthread_create( &console_thread, &thread_attr2, reading_console_thread, this ) ) { app_warning( _("Cannot create status reading thread !") ); stop_render_preview(); unlink( scene_file ); delete scene_file; return false; } pthread_join( console_thread, NULL ); return render_status; } //*********************************************************************** // Stop preview // // Stop the preview process //********************************************************************** void MaterialPreview::stop_preview( bool close_console ) { stop_render_preview(); unlink( scene_file ); delete scene_file; pthread_cancel( output_thread ); if ( close_console ) pthread_cancel( console_thread ); //debug << "\n===== Closing render ======"; //debug.flush(); if ( !modal ) gdk_threads_enter(); //debug << "\nEntered gdk threads !"; //debug.flush(); if ( start_button != NULL ) gtk_label_set_text( GTK_LABEL(start_label), _("Preview") ); if ( progress_bar != NULL ) gtk_progress_bar_update( GTK_PROGRESS_BAR(progress_bar), 0 ); gdk_flush(); if ( !modal ) gdk_threads_leave(); //debug << "\nLabel set !"; //debug.flush(); } //*********************************************************************** // Cancel preview // // cancel the preview process //********************************************************************** void MaterialPreview::cancel_preview() { if ( pipe_console_name == NULL ) return; stop_render_preview(); pthread_cancel( output_thread ); pthread_cancel( console_thread ); unlink( scene_file ); delete scene_file; } //************************************************************** // Predefined scene // // Create a scene file for preview, from parameters //************************************************************** char * MaterialPreview::get_scene_file( Material *mat ) { char *tempfile = get_temp_filename(); ofstream file( tempfile, ios::out ); file.setf( ios::fixed, ios::floatfield ); if ( ! file ) return NULL; //debug << "\nCreating temp scene file -> " << tempfile; //debug.flush(); // General scene settings PREF_DEF file << "\n\n// Global settings\nglobal_settings {"; file << "\n\tassumed_gamma " << pref->gamma->value(); file << "\n\tmax_trace_level 5" << "\n\t}"; // Material definition mat->output_to_povray( file ); // Camera - special one for sky_sphere & fisheye preview switch ( predef_scene->value() ) { // sky sphere - special camera parameters for this one case 4: file << "\n\ncamera { perspective location <0, .6, -0.5> angle 90.0 up <0,1.0,0> right <1.0,0,0> sky <0,1.0,0> look_at <0,.8,0>}"; break; // Fisheye camera case 5: file << "\n\ncamera { fisheye location <0, .6, 0> angle 180.0 up <0,1.0,0> right <1.0,0,0> sky <0,1.0,0> look_at <0,1,0>}"; break; // Normal default: file << "\n\ncamera { perspective location <0.47, .6, -0.47> angle 40.0 up <0,1.0,0> right <1.0,0,0> sky <0,1.0,0> look_at <0,.2,0>}"; break; } // Lights file << "\nlight_source { <-0.4,1.6,-0.6> color rgb<1,1,1> media_interaction }"; file << "\n\nlight_source { <1.29,.36,-0.6> color rgb<0.94, 0.94, 0.94> shadowless }"; // Floor and wall if ( render_floor->value() ) file << "\nplane { <0,1,0>, 0 pigment{ checker color rgb <0.6, 0.6, 0.6>, color rgb <0.7, 0.7, 0.7> scale 0.3 } }"; if ( render_wall->value() && predef_scene->value() != 4 ) file << "\nplane { <0,1,0>, 0 texture { pigment{ checker color rgb<.43,.58,.45>, color rgb<.29,.44,.33> } scale 0.3 finish { ambient 0.65 } } rotate z*-90 rotate y*45 translate <-.5,0,.5>}"; // Decorative sky sphere ( not for texture preview ! ) if ( render_sky->value() && predef_scene->value() != 4 ) { file << "\nsky_sphere {"; file << "\n\tpigment { gradient y"; file << "\n\tpigment_map { \ [ 0.010000 rgbft<0.847000,0.749000,0.847000,0.000000,0.000000> ] \ [ 0.250000 wrinkles \ color_map { \ [ 0.000000 rgbft<0.850000,0.850000,0.850000,0.000000,0.000000> ] \ [ 0.100000 rgbft<0.750000,0.750000,0.750000,0.000000,0.000000> ] \ [ 0.500000 rgbft<0.258000,0.258000,0.435000,0.000000,0.000000> ] \ [ 1.000000 rgbft<0.258000,0.258000,0.435000,0.000000,0.000000> ] }"; file << "\n\tturbulence <0.650000,0.650000,0.650000> \ omega 0.700000 translate <0.000000,0.000000,0.000000> scale <8.000000,1.250000,8.000000> rotate <0.000000,0.000000,5.000000> ]"; file << "[ 1.000000 bozo \ color_map { \ [ 0.000000 rgbft<0.850000,0.850000,0.850000,0.000000,0.000000> ] \ [ 0.100000 rgbft<0.550000,0.600000,0.650000,0.000000,0.000000> ] \ [ 0.500000 rgbft<0.184000,0.184000,0.309000,0.000000,0.000000> ] \ [ 1.000000 rgbft<0.100000,0.100000,0.200000,0.000000,0.000000> ] \ }"; file << "\nturbulence <0.650000,0.650000,0.650000> \ omega 0.707000 translate <0.000000,0.000000,0.000000> scale <8.000000,4.500000,8.000000> rotate <0.000000,0.000000,10.000000> ] \ } } }"; } // Preview objects switch ( predef_scene->value() ) { case 0: file << "\nsphere { <0,0,0>, 1\nscale <0.2,0.2,0.2> "; file << "\n\tmaterial { "; mat->get_underscore_name(file); file << " } translate <0,.2,0> }"; break; case 1: file << "\nbox { <-0.125,-0.125,0.125>, <0.125,0.125,-0.125> "; file << "\n\tmaterial { "; mat->get_underscore_name(file); file << " } rotate z*-30 rotate y*10 translate <0, 0.2, -0.02> }"; break; case 2: file << "\ncone { <0,-0.13,0>, 1, <0,0.17,0>, 0\n scale <0.21,0,0.21> "; file << "\n\tmaterial { "; mat->get_underscore_name(file); file << " } translate <-0.02,0.23,0.02> }"; break; case 3: file << "\n\nplane {\n\t<0,1,0>,0.01"; file << "\n\tmaterial { "; mat->get_underscore_name(file); file << " } }"; break; case 4: case 5: { file << "\n\nsky_sphere{\n\t"; PovMaterial *tex = ((PovMaterial*)mat); if ( tex->has_pigment() ) tex->output_to_povray_pigment( file ); else { file << "pigment { color 0.3 }"; app_warning( _("Sky sphere requires a pigment in texture.") ); } file << "\n}"; } break; case 6: { file << "\ndifference { sphere { <0,0,0>, 1}\nbox { <0,0,0>, <1,1,-1>}\nscale <0.2,0.2,0.2> "; file << "\n\tmaterial { "; mat->get_underscore_name(file); file << " } translate <0,0.2,0> }"; } break; } file.close(); return tempfile; } //************************************************************** // Povray command // // create povray command for material preview //************************************************************** bool MaterialPreview::render_preview( char * source_file ) { // Predefined sizes if ( predef_sizes != NULL ) { int nsize = 80; predef_sizes->flush(); switch( predef_sizes->value() ) { case 0: nsize = 80; break; case 1: nsize = 140; break; case 2: nsize = 200; break; case 3: nsize = 300; break; case 4: nsize = 400; break; } if ( width->value() != nsize ) { set_size( nsize, nsize ); if ( image != NULL ) delete image; image_size = width->value() * height->value() * 3; image = new char[image_size]; } } // Pipes creation int res; pipe_console_name = get_temp_filename(); res = mkfifo( pipe_console_name, S_IRUSR | S_IWUSR | O_NDELAY ); pipe_output_name = get_temp_filename(); res += mkfifo( pipe_output_name, S_IRUSR | S_IWUSR | O_NDELAY ); if ( res != 0 ) return false; //debug << "\ncreated console pipe -> " << pipe_console_name; //debug.flush(); //debug << "\ncreated output pipe -> " << pipe_output_name; //debug.flush(); // Command PREF_DEF char *args[256]; int arg_num = 0; // Povray command args[arg_num++] = pref->povcmd->value(); // Don't pause at end char arg_p[] = "-P"; args[arg_num++] = arg_p; // Don't continue render from previous session char arg_c[] = "-C"; args[arg_num++] = arg_c; // Don't display image char arg_ud[] = "-UD"; args[arg_num++] = arg_ud; // Set output format to raw char arg_fp[] = "+FP"; args[arg_num++] = arg_fp; // Set quality level to 9 char arg_q[] = "+Q9"; args[arg_num++] = arg_q; // Set antialiasing if ( antialias->value() ) { char arg_a[] = "+A"; args[arg_num++] = arg_a; } // Redirect all stream to console pipe char arg_ga[ strlen(pipe_console_name) + 5 ]; sprintf( arg_ga, "+GA%s", pipe_console_name ); args[arg_num++] = arg_ga; // Redirect image to data pipe char arg_o[ strlen(pipe_output_name) + 5 ]; sprintf( arg_o, "+O%s", pipe_output_name ); args[arg_num++] = arg_o; // Set predefined scene file char arg_i[ strlen(scene_file) + 5 ]; sprintf( arg_i, "+I%s", scene_file ); args[arg_num++] = arg_i; // Set height char arg_h[10]; sprintf( arg_h, "+H%u", height->value() ); args[arg_num++] = arg_h; // Set width char arg_w[10]; sprintf( arg_w, "+W%u", width->value() ); args[arg_num++] = arg_w; // Launch args[arg_num] = NULL; povray_pid = fork(); if ( povray_pid == -1 ) return false; if ( povray_pid == 0 ) { if ( execve( *args, args, app_ref->envp ) == -1 ) { unlink( pipe_console_name ); unlink( pipe_output_name ); exit(-1); } exit(-1); } //debug << "\nOpening pipes..."; //debug.flush(); pipe_console_id = open( pipe_console_name, O_RDONLY | O_NONBLOCK ); if ( pipe_console_id == -1 ) return false; pipe_output_id = open( pipe_output_name, O_RDONLY | O_NONBLOCK ); //debug << "\nPipes opened..."; //debug.flush(); return true; } //************************************************* // Stop render preview //************************************************* void MaterialPreview::stop_render_preview() { kill( povray_pid, 1 ); close( pipe_output_id ); close( pipe_console_id ); unlink( pipe_output_name ); unlink( pipe_console_name ); waitpid( povray_pid, NULL, 0 ); unlink( pipe_output_name ); unlink( pipe_console_name ); delete pipe_output_name; delete pipe_console_name; pipe_console_name = NULL; } //************************************************* // Get pipe line // // get a line from a named pipe //************************************************* bool MaterialPreview::get_pipe_line( int file, char *buffer, int size ) { //debug << "\nStart reading from pipe !"; debug.flush(); char ch; int i = 0; while ( i < size ) { int res = read( file, &ch, 1 ); if ( res == 0 ) continue; if ( res == -1 && errno == EAGAIN ) continue; if ( res == -1 ) return false; if ( ch == '\n' || ch == '\r' ) break; buffer[i++] = ch; } buffer[i] = '\0'; //debug << "\nRead one line from pipe !"; debug.flush(); return true; } //******************************************************* // Read console // // Read the console thread //******************************************************* void MaterialPreview::read_console() { pthread_setcancelstate( PTHREAD_CANCEL_ENABLE, NULL ); pthread_setcanceltype( PTHREAD_CANCEL_ASYNCHRONOUS, NULL ); //debug << "\nStarted console reading thread !!"; //debug.flush(); char console_line[255] = "\0"; bool finished = false; if ( get_pipe_line( pipe_console_id, console_line, 254 ) == false || !strstr( console_line, "Options" ) ) { //debug << "\nError : read_console init planting !"; //debug << "\nline ->" << console_line << "<-"; //debug.flush(); stop_preview( false ); if ( ! modal ) gdk_threads_enter(); app_warning( _("Error while executing povray ! ( init error )") ); gdk_flush(); if ( !modal ) gdk_threads_leave(); pthread_exit(NULL); } // lecture de la console int console_line_offset = 0; int res; char ch; while ( finished == false ) { pthread_testcancel(); console_line[console_line_offset] = '\0'; res = read( pipe_console_id, &ch, 1 ); if ( res == -1 || res == 0 ) continue; console_line[console_line_offset++] = ch; pthread_testcancel(); if ( console_line_offset == 254 || ch == '\n' ) { // Analyse et stockage ( gestion des erreurs ) if ( strstr( console_line, "Total Time" ) != NULL ) finished = true; bool error = false; if ( strstr( console_line, "Error" ) != NULL ) error = true; if ( strstr( console_line, "error" ) != NULL ) error = true; //if ( strstr( console_line, "Possible" ) != NULL ) // error = false; if ( error ) { if ( !modal ) gdk_threads_enter(); app_warning( _("Povray reports an error, possible artifacts or truevision bug.") ); gdk_flush(); if ( !modal ) gdk_threads_leave(); stop_preview( modal ); pthread_exit(NULL); } console_line_offset = 0; } } render_status = true; while ( render_finished == false ) sleep(1); stop_preview( false ); //debug << "\nLeaving console thread !! "; //debug.flush(); pthread_exit(NULL); } //******************************************************* // Read utput // // Image data thread read process //******************************************************* void MaterialPreview::read_output() { //debug << "\nStarted output reading thread !!"; //debug.flush(); pthread_setcancelstate( PTHREAD_CANCEL_ENABLE, NULL ); pthread_setcanceltype( PTHREAD_CANCEL_ASYNCHRONOUS, NULL ); render_finished = false; // Flow control int largeur = width->value(); int hauteur = height->value(); guchar output_line[largeur*3]; int output_line_num = 0; int output_line_offset = 0; int header = 0; char ch; int res; bool finished = false; GdkRectangle rect; rect.width = largeur; rect.height = 1; rect.x= 0; while ( finished == false ) { pthread_testcancel(); res = read( pipe_output_id, &ch, 1 ); //debug << "\nres -> " << res; if ( res == -1 || res == 0 ) continue; if ( header < 3 ) { if ( ch == '\n' ) header++; continue; } output_line[output_line_offset++] = ch; if ( output_line_offset == largeur*3 ) { //debug << "\none line completed "; //debug.flush(); if ( !modal ) gdk_threads_enter(); if ( image != NULL ) memcpy( image + (largeur-1) * output_line_num * 3, output_line, (largeur-1)*3 ); GdkPixbuf *buffer = gdk_pixbuf_new_from_data( (const guchar*)image, GDK_COLORSPACE_RGB, FALSE, 8, largeur, hauteur, (largeur-1)*3, NULL, NULL ); gtk_image_set_from_pixbuf( GTK_IMAGE(preview_widget), buffer ); g_object_unref( buffer ); if ( progress_bar != NULL ) gtk_progress_bar_update( GTK_PROGRESS_BAR(progress_bar), (float)output_line_num / (float)hauteur ); //debug << "\nProgress -> " << (float)output_line_num / (float)height->value(); //debug.flush(); rect.y = output_line_num++; gdk_flush(); if ( !modal ) gdk_threads_leave(); output_line_offset = 0; if ( output_line_num == hauteur -1 ) finished = true; } } //debug << "\nLeaving output thread !!"; //debug.flush(); render_finished = true; pthread_exit(NULL); } //******************************************************** // Save as preview // // save image for thumnails //******************************************************** void MaterialPreview::save_as_preview( ofstream & file ) { if ( image == NULL ) return; // compression char *zimage = new char[image_size+30]; long int zimage_size = image_size+30; compress2( (Bytef*)zimage, (uLongf*)&zimage_size, (Bytef*)image, (uLong)image_size, 9 ); delete image; image = NULL; // sauvegarde file << "\nPREVIEW{ SIZE=" << zimage_size << " DATA="; for( int i = 0 ; i < zimage_size ; i++ ) file << zimage[i]; file << " }\n"; delete zimage; } //******************************************************** // Save preview settings // // save preview parameters in preferences //******************************************************** void MaterialPreview::save_preview_settings() { PREF_DEF if ( ! pref->save_preview_settings->value() ) return; predef_sizes->flush(); predef_scene->flush(); render_wall->flush(); render_floor->flush(); render_sky->flush(); antialias->flush(); pref->preview_size->set( predef_sizes->value() ); pref->preview_object->set( predef_scene->value() ); pref->preview_wall->set( render_wall->value() ); pref->preview_floor->set( render_floor->value() ); pref->preview_sky->set( render_sky->value() ); pref->preview_antialias->set( antialias->value() ); }