import functools import os.path import blf import bpy from ..foundation import util from ..submission import exceptions from .. import version from . import G WELCOME_TEXT = "Run the Quick Benchmark on the selected device to " \ "get a fast measurement of your hardware's performance.\n" \ "Usually takes less than 30 minutes." BLURB_TEXT = "Share your results with the world!\n" \ "Manage the uploaded benchmark data on your Blender ID." ################################################################################ # Draw Utilities. font_id = 0 def viewport_size(): import bgl viewport = bgl.Buffer(bgl.GL_INT, 4) bgl.glGetIntegerv(bgl.GL_VIEWPORT, viewport) return viewport[2], viewport[3] def draw_text_center(text, x, y, shadow=False): dim = blf.dimensions(font_id, text) cx = x - int(dim[0] / 2) cy = y - int(dim[1] / 2) if shadow: delta = 1 blf.color(font_id, 0.2, 0.2, 0.2, 1.0) blf.position(font_id, cx + delta, cy - delta, 0) blf.draw(font_id, text) blf.color(font_id, 1.0, 1.0, 1.0, 1.0) blf.position(font_id, cx, cy, 0) blf.draw(font_id, text) def draw_text_multiline(text, x, y, shadow=False): ui_scale = bpy.context.user_preferences.system.ui_scale height = int(blf.dimensions(font_id, "Dummy Text")[1]) space = int(8 * ui_scale) for line in text.split('\n'): if shadow: delta = 1 blf.color(font_id, 0.2, 0.2, 0.2, 1.0) blf.position(font_id, x + delta, y - height - delta, 0) blf.draw(font_id, line) blf.color(font_id, 1.0, 1.0, 1.0, 1.0) blf.position(font_id, x, y - height, 0) blf.draw(font_id, line) y -= height + space def draw_rect(x, y, w, h, color): import gpu gpu.draw.rect(x, y, x + w, y + h, color[0], color[1], color[2], color[3]) def draw_image(filepath, x, y, w, h): if filepath not in G.images: ima = bpy.data.images.load(filepath) G.images[filepath] = ima import gpu gpu.draw.image(G.images[filepath], x, y, x + w, y + h) ################################################################################ # Draw. def benchmark_draw_post_pixel(arg1, arg2): with G.progress_lock: progress_status = G.progress_status result_platform = G.result_platform result_stats = G.result_stats result_dict = G.result_dict submission_exception = G.submission_exception ui_scale = bpy.context.user_preferences.system.ui_scale blf.color(font_id, 1.0, 1.0, 1.0, 1.0) window_width, window_height = viewport_size() # Image image_h = 370 * ui_scale image_y = window_height - image_h if G.background_image_path: draw_image(G.background_image_path, 0, image_y, window_width, image_h) else: splash_dir = os.path.dirname(os.path.abspath(__file__)) splash_filepath = os.path.join(splash_dir, 'splash.png') draw_image(splash_filepath, 0, image_y, window_width, image_h) if result_dict: _draw_benchmark_has_run(image_y, ui_scale, window_width, window_height) elif result_stats or result_platform or progress_status: _draw_benchmark_is_running(image_y, result_platform, result_stats, ui_scale, window_width) else: _draw_introduction(image_y, ui_scale, window_width, window_height) # Bottom bar bottom_x = 0 bottom_y = 0 bottom_w = window_width bottom_h = 52 * ui_scale bottom_color = [0.2, 0.2, 0.2, 1.0] draw_rect(bottom_x, bottom_y, bottom_w, bottom_h, bottom_color) # Logo logo_width_unscaled = 326 logo_height_unscaled = 104 logo_dir = os.path.dirname(os.path.abspath(__file__)) logo_filepath = os.path.join(logo_dir, 'blender.png') logo_scale_factor = 1.0 while logo_height_unscaled * logo_scale_factor > bottom_h: logo_scale_factor *= 0.5 logo_width = logo_width_unscaled * logo_scale_factor logo_height = logo_height_unscaled * logo_scale_factor logo_padding = (bottom_h - logo_height) * 0.5 draw_image(logo_filepath, logo_padding, logo_padding, logo_width, logo_height) def _draw_introduction(image_y, ui_scale, window_width, window_height): """Draw title and welcome text.""" x = 0.5 * window_width y = 0.70 * window_height blf.size(font_id, int(32 * ui_scale), 72) draw_text_center(f"Blender Benchmark {version.formatted()}", x, y, shadow=True) y -= 32 * ui_scale blf.size(font_id, int(12 * ui_scale), 72) draw_text_center("Free and Open Data for everyone.", x, y, shadow=True) x = 50.0 * ui_scale y = image_y - (image_y - 52 * ui_scale - 18 * 3 * ui_scale) * 0.5 blf.size(font_id, int(12 * ui_scale), 72) text = word_wrap(WELCOME_TEXT, window_width * 0.45) draw_text_multiline(text, x, y) def _draw_benchmark_is_running(image_y, result_platform, result_stats, ui_scale, window_width): """Draw while the benchmark is running.""" blf.size(font_id, int(12 * ui_scale), 72) x = 50.0 * ui_scale y = image_y - 20 * ui_scale # Stats if result_platform: draw_text_multiline(result_platform, 0.5 * window_width + x, y) if result_stats: draw_text_multiline(result_stats, x, y) # Progress progress_x = 0.0 progress_y = image_y + 1 progress_w = window_width * G.current_progress progress_h = 15.0 * ui_scale progress_color = [0.8, 1.0, 1.0, 0.2] draw_rect(progress_x, progress_y, progress_w, progress_h, progress_color) # Current status if G.progress_status: blf.size(font_id, int(18 * ui_scale), 72) draw_text_multiline(G.progress_status, progress_x + 8.0 * ui_scale, progress_y + progress_h + int(22 * ui_scale), shadow=True) def _draw_benchmark_has_run(image_y, ui_scale, window_width, window_height): """Draw submit button and other after-running stuff.""" x = 0.5 * window_width y = 0.70 * window_height score = 0 for name_stats in G.result_dict["scenes"]: stat = name_stats['stats'] if stat["result"] == "OK": score += stat["total_render_time"] else: score = -1 if score >= 0: blf.size(font_id, int(32 * ui_scale), 72) draw_text_center("Your Time: {}".format( util.humanReadableTimeDifference(score)), x, y, shadow=True) else: blf.size(font_id, int(18 * ui_scale), 72) draw_text_center("Unfortunately, crash happened :(", x, y, shadow=True) blf.size(font_id, int(24 * ui_scale), 72) draw_text_center("You can still share data of succeeded scenes!", x, y - 26 * ui_scale, shadow=True) x = 50.0 * ui_scale y = image_y - (image_y - 52 * ui_scale - 18 * 3 * ui_scale) * 0.5 blf.size(font_id, int(12 * ui_scale), 72) text = _after_submission_text() text = word_wrap(text, window_width * 0.45) draw_text_multiline(text, x, y) def _after_submission_text() -> str: import requests.exceptions ex = G.submission_exception # Nothing wrong, just show the default text. if not ex: return BLURB_TEXT # Generic message when we cannot reach MyData. if isinstance(ex, requests.exceptions.ConnectionError): return f'Unable to connect to the Open Data platform. ' \ f'Please check your internet connection and try again.' if isinstance(ex, requests.exceptions.Timeout): return f'There was a timeout communicating with the Open Data platform. ' \ f'Please check your internet connection and try again.' if isinstance(ex, exceptions.TokenTimeoutError): return f'There was a timeout waiting for a Client Authentication token. ' \ f'This is fine, just try again.' # If not our own exception class, show generic message. if not isinstance(ex, exceptions.CommunicationError): return f'Error submitting your results: {ex}' # Return proper message based on the HTTP status code of the response. if ex.status_code in {502, 503}: # 502 Bad Gateway, happens when restarting uWSGI. # 503 Service Unavailable, happens when OpenData is down. text = f'There was a hickups with code {ex.status_code} ' \ f'in the Open Data platform, please try submitting again.' elif ex.status_code == 422: # 422 Unprocessable Entity, happens when the JSON doesn't pass schema validation. text = f'We somehow submitted invalid data, see below for details.' else: text = f'Error {ex.status_code} submitting your results.' if not ex.json: return text msg = ex.json.get('message') if msg: return f'{text}\nThe server said: {msg}' return text @functools.lru_cache() def word_wrap(string: str, width_in_px: int, *, depth=0) -> str: """Word-wrapping with variable character width. Newlines in the input string are kept in the output. """ assert depth < 3 if '\n' in string: # If the string already consists of multiple lines, wrap each line individually. return '\n'.join(word_wrap(line, width_in_px, depth=depth + 1) for line in string.splitlines(keepends=False)) # Do an estimate of the maximum number of characters to fit on a line. char_width = blf.dimensions(font_id, "i")[0] max_chars = int(width_in_px // char_width) wrapped_lines = [] while string: # The line won't be longer than max_chars, so start there. candidate_line = string[:max_chars] line_width = blf.dimensions(font_id, candidate_line)[0] # Keep removing the last word until the line fits the width. break_at_space = True while candidate_line and line_width >= width_in_px: if break_at_space: marker = len(candidate_line) - 1 while marker > 0 and not candidate_line[marker].isspace(): marker -= 1 if marker <= 0: # This line was unbreakable by whitespace. Let's just hard-break it. break_at_space = False continue else: marker = len(candidate_line) - 1 candidate_line = candidate_line[:marker] line_width = blf.dimensions(font_id, candidate_line)[0] string = string[len(candidate_line):] wrapped_lines.append(candidate_line.strip()) return '\n'.join(wrapped_lines)