From e16f069eb3dfa5dd9ad9433154f6f3a5b3008d77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Tue, 15 Mar 2016 15:44:19 +0100 Subject: [PATCH] Added downloading of textures when clicking on a menu item For this I made the operator a state machine; when its state is set to 'QUIT' it'll quit. Different states can have different OpenGL drawing functions. For now, to help development, the state is drawn in the bottom left corner. --- blender_cloud/__init__.py | 9 +++- blender_cloud/gui.py | 101 ++++++++++++++++++++++++++++++++------ 2 files changed, 92 insertions(+), 18 deletions(-) diff --git a/blender_cloud/__init__.py b/blender_cloud/__init__.py index 4717cd4..29f465f 100644 --- a/blender_cloud/__init__.py +++ b/blender_cloud/__init__.py @@ -45,7 +45,7 @@ else: import logging import bpy -from bpy.types import AddonPreferences, Operator, WindowManager +from bpy.types import AddonPreferences, Operator, WindowManager, Scene from bpy.props import StringProperty @@ -147,7 +147,12 @@ def register(): name="Blender Cloud node UUID", default='') # empty == top-level of project - logging.basicConfig(level=logging.INFO, + Scene.blender_cloud_dir = StringProperty( + name='Blender Cloud texture storage directory', + subtype='DIR_PATH', + default='//textures') + + logging.basicConfig(level=logging.DEBUG, format='%(asctime)-15s %(levelname)8s %(name)s %(message)s') async_loop.setup_asyncio_executor() gui.register() diff --git a/blender_cloud/gui.py b/blender_cloud/gui.py index b2ede1e..317de82 100644 --- a/blender_cloud/gui.py +++ b/blender_cloud/gui.py @@ -163,6 +163,8 @@ class BlenderCloudBrowser(bpy.types.Operator): _draw_handle = None + _state = 'BROWSING' + project_uuid = '5672beecc0261b2005ed1a33' # Blender Cloud project UUID node_uuid = '' # Blender Cloud node UUID async_task = None # asyncio task for fetching thumbnails @@ -202,11 +204,19 @@ class BlenderCloudBrowser(bpy.types.Operator): self.browse_assets() context.window_manager.modal_handler_add(self) - self.timer = context.window_manager.event_timer_add(1/30, context.window) + self.timer = context.window_manager.event_timer_add(1 / 30, context.window) return {'RUNNING_MODAL'} def modal(self, context, event): + if self._state == 'QUIT': + self._finish(context) + return {'FINISHED'} + + if event.type == 'TAB' and event.value == 'RELEASE': + self.log.info('Ensuring async loop is running') + async_loop.ensure_async_loop() + if event.type == 'TIMER': context.area.tag_redraw() return {'RUNNING_MODAL'} @@ -216,7 +226,7 @@ class BlenderCloudBrowser(bpy.types.Operator): self.mouse_x = event.mouse_region_x self.mouse_y = event.mouse_region_y - if event.type == 'LEFTMOUSE' and event.value == 'RELEASE': + if self._state == 'BROWSING' and event.type == 'LEFTMOUSE' and event.value == 'RELEASE': selected = self.get_clicked() if selected is None: @@ -227,9 +237,12 @@ class BlenderCloudBrowser(bpy.types.Operator): self.node_uuid = selected.node_uuid self.browse_assets() else: - self.handle_item_selection(selected) - self._finish(context) - return {'FINISHED'} + if selected.file_desc is None: + # This can happen when the thumbnail information isn't loaded yet. + # Just ignore the click for now. + # TODO: think of a way to handle this properly. + return {'RUNNING_MODAL'} + self.handle_item_selection(context, selected) elif event.type in {'RIGHTMOUSE', 'ESC'}: self._finish(context) @@ -352,27 +365,49 @@ class BlenderCloudBrowser(bpy.types.Operator): else: # TODO: add "nothing here" icon and trigger re-draw self.log.warning("Not node UUID and no project UUID, I can't do anything!") - pass - - loop = asyncio.get_event_loop() - loop.call_soon_threadsafe(self.downloading_done) def browse_assets(self): + self._state = 'BROWSING' self.log.debug('Browsing assets at project %r node %r', self.project_uuid, self.node_uuid) + self._new_async_task(self.async_download_previews(self.thumbnails_cache)) + + def _new_async_task(self, async_task: asyncio.coroutine): + """Stops the currently running async task, and starts another one.""" + + self.log.debug('Setting up a new task %r, so any existing task must be stopped', async_task) self._stop_async_task() # Download the previews asynchronously. self.signalling_future = asyncio.Future() - self.async_task = asyncio.ensure_future( - self.async_download_previews(self.thumbnails_cache)) + self.async_task = asyncio.ensure_future(async_task) + self.log.debug('Created new task %r', self.async_task) # Start the async manager so everything happens. async_loop.ensure_async_loop() - def downloading_done(self): - self.log.info('Done downloading thumbnails.') - def draw_menu(self, context): + """Draws the GUI with OpenGL.""" + + drawers = { + 'BROWSING': self._draw_browser, + 'DOWNLOADING_TEXTURE': self._draw_downloading, + } + + if self._state in drawers: + drawer = drawers[self._state] + drawer(context) + + # For debugging: draw the state + font_id = 0 + bgl.glColor4f(1.0, 1.0, 1.0, 1.0) + blf.size(font_id, 20, 72) + blf.position(font_id, 5, 5, 0) + blf.draw(font_id, self._state) + bgl.glDisable(bgl.GL_BLEND) + + def _draw_browser(self, context): + """OpenGL drawing code for the BROWSING state.""" + margin_x = 20 margin_y = 5 padding_x = 5 @@ -415,6 +450,28 @@ class BlenderCloudBrowser(bpy.types.Operator): bgl.glDisable(bgl.GL_BLEND) # bgl.glColor4f(0.0, 0.0, 0.0, 1.0) + def _draw_downloading(self, context): + """OpenGL drawing code for the DOWNLOADING_TEXTURE state.""" + + content_width = context.area.regions[4].width + content_height = context.area.regions[4].height + + bgl.glEnable(bgl.GL_BLEND) + bgl.glColor4f(0.0, 0.0, 0.2, 0.6) + bgl.glRectf(0, 0, content_width, content_height) + + font_id = 0 + text = "Downloading texture from Blender Cloud" + bgl.glColor4f(1.0, 1.0, 1.0, 1.0) + blf.size(font_id, 20, 72) + text_width, text_height = blf.dimensions(font_id, text) + + blf.position(font_id, + content_width * 0.5 - text_width * 0.5, + content_height * 0.7 + text_height * 0.5, 0) + blf.draw(font_id, text) + bgl.glDisable(bgl.GL_BLEND) + def get_clicked(self) -> MenuItem: for item in self.current_display_content: @@ -423,9 +480,21 @@ class BlenderCloudBrowser(bpy.types.Operator): return None - def handle_item_selection(self, item: MenuItem): + def handle_item_selection(self, context, item: MenuItem): """Called when the user clicks on a menu item that doesn't represent a folder.""" - pass + + self._state = 'DOWNLOADING_TEXTURE' + url = item.file_desc.link + local_path = os.path.join(context.scene.blender_cloud_dir, item.file_desc.filename) + local_path = bpy.path.abspath(local_path) + self.log.info('Downloading %s to %s', url, local_path) + + def texture_download_completed(_): + self.log.info('Texture download complete, inspect %r.', local_path) + self._state = 'QUIT' + + self._new_async_task(pillar.download_to_file(url, local_path)) + self.async_task.add_done_callback(texture_download_completed) # store keymaps here to access after registration