From 77daec9224bf78a71d49a5ddfca72f9bb10efb11 Mon Sep 17 00:00:00 2001 From: Rodney Ewing Date: Wed, 14 Aug 2013 10:38:13 -0700 Subject: [PATCH] Added initial stl processor --- mediagoblin/media_types/stl/processing.py | 298 ++++++++++++++-------- 1 file changed, 191 insertions(+), 107 deletions(-) diff --git a/mediagoblin/media_types/stl/processing.py b/mediagoblin/media_types/stl/processing.py index 53751416..6adc68ae 100644 --- a/mediagoblin/media_types/stl/processing.py +++ b/mediagoblin/media_types/stl/processing.py @@ -14,6 +14,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +import argparse import os import json import logging @@ -21,8 +22,11 @@ import subprocess import pkg_resources from mediagoblin import mg_globals as mgg -from mediagoblin.processing import create_pub_filepath, \ - FilenameBuilder +from mediagoblin.processing import ( + FilenameBuilder, MediaProcessor, + ProcessingManager, request_from_args, + get_orig_filename, store_public, + copy_original) from mediagoblin.media_types.stl import model_loader @@ -75,49 +79,60 @@ def blender_render(config): env=env) -def process_stl(proc_state): - """Code to process an stl or obj model. Will be run by celery. - - A Workbench() represents a local tempory dir. It is automatically - cleaned up when this function exits. +class CommonStlProcessor(MediaProcessor): + """ + Provides a common base for various stl processing steps """ - entry = proc_state.entry - workbench = proc_state.workbench - - queued_filepath = entry.queued_media_file - queued_filename = workbench.localized_file( - mgg.queue_store, queued_filepath, 'source') - name_builder = FilenameBuilder(queued_filename) - - ext = queued_filename.lower().strip()[-4:] - if ext.startswith("."): - ext = ext[1:] - else: - ext = None - - # Attempt to parse the model file and divine some useful - # information about it. - with open(queued_filename, 'rb') as model_file: - model = model_loader.auto_detect(model_file, ext) - - # generate preview images - greatest = [model.width, model.height, model.depth] - greatest.sort() - greatest = greatest[-1] - def snap(name, camera, width=640, height=640, project="ORTHO"): - filename = name_builder.fill(name) - workbench_path = workbench.joinpath(filename) + def common_setup(self): + # Pull down and set up the original file + self.orig_filename = get_orig_filename( + self.entry, self.workbench) + self.name_builder = FilenameBuilder(self.orig_filename) + + self._set_ext() + self._set_model() + self._set_greatest() + + def _set_ext(self): + ext = self.name_builder.ext + + if not ext: + ext = None + + self.ext = ext + + def _set_model(self): + """ + Attempt to parse the model file and divine some useful + information about it. + """ + with open(self.orig_filename, 'rb') as model_file: + self.model = model_loader.auto_detect(model_file, self.ext) + + def _set_greatest(self): + greatest = [self.model.width, self.model.height, self.model.depth] + greatest.sort() + self.greatest = greatest[-1] + + def copy_original(self): + copy_original( + self.entry, self.orig_filename, + self.name_builder.fill('{basename}{ext}')) + + def _snap(self, keyname, name, camera, size, project="ORTHO"): + filename = self.name_builder.fill(name) + workbench_path = self.workbench.joinpath(filename) shot = { - "model_path": queued_filename, - "model_ext": ext, + "model_path": self.orig_filename, + "model_ext": self.ext, "camera_coord": camera, - "camera_focus": model.average, - "camera_clip": greatest*10, - "greatest": greatest, + "camera_focus": self.model.average, + "camera_clip": self.greatest*10, + "greatest": self.greatest, "projection": project, - "width": width, - "height": height, + "width": size[0], + "height": size[1], "out_file": workbench_path, } blender_render(shot) @@ -126,70 +141,139 @@ def process_stl(proc_state): assert os.path.exists(workbench_path) # copy it up! - with open(workbench_path, 'rb') as rendered_file: - public_path = create_pub_filepath(entry, filename) - - with mgg.public_store.get_file(public_path, "wb") as public_file: - public_file.write(rendered_file.read()) - - return public_path - - thumb_path = snap( - "{basename}.thumb.jpg", - [0, greatest*-1.5, greatest], - mgg.global_config['media:thumb']['max_width'], - mgg.global_config['media:thumb']['max_height'], - project="PERSP") - - perspective_path = snap( - "{basename}.perspective.jpg", - [0, greatest*-1.5, greatest], project="PERSP") - - topview_path = snap( - "{basename}.top.jpg", - [model.average[0], model.average[1], greatest*2]) - - frontview_path = snap( - "{basename}.front.jpg", - [model.average[0], greatest*-2, model.average[2]]) - - sideview_path = snap( - "{basename}.side.jpg", - [greatest*-2, model.average[1], model.average[2]]) - - ## Save the public file stuffs - model_filepath = create_pub_filepath( - entry, name_builder.fill('{basename}{ext}')) - - with mgg.public_store.get_file(model_filepath, 'wb') as model_file: - with open(queued_filename, 'rb') as queued_file: - model_file.write(queued_file.read()) - - # Remove queued media file from storage and database. - # queued_filepath is in the task_id directory which should - # be removed too, but fail if the directory is not empty to be on - # the super-safe side. - mgg.queue_store.delete_file(queued_filepath) # rm file - mgg.queue_store.delete_dir(queued_filepath[:-1]) # rm dir - entry.queued_media_file = [] - - # Insert media file information into database - media_files_dict = entry.setdefault('media_files', {}) - media_files_dict[u'original'] = model_filepath - media_files_dict[u'thumb'] = thumb_path - media_files_dict[u'perspective'] = perspective_path - media_files_dict[u'top'] = topview_path - media_files_dict[u'side'] = sideview_path - media_files_dict[u'front'] = frontview_path - - # Put model dimensions into the database - dimensions = { - "center_x" : model.average[0], - "center_y" : model.average[1], - "center_z" : model.average[2], - "width" : model.width, - "height" : model.height, - "depth" : model.depth, - "file_type" : ext, - } - entry.media_data_init(**dimensions) + store_public(self.entry, keyname, workbench_path, filename) + + def generate_thumb(self, thumb_size=None): + if not thumb_size: + thumb_size = (mgg.global_config['media:thumb']['max_width'], + mgg.global_config['media:thumb']['max_height']) + + self._snap( + "thumb", + "{basename}.thumb.jpg", + [0, self.greatest*-1.5, self.greatest], + thumb_size, + project="PERSP") + + def generate_perspective(self, size=None): + if not size: + size = (mgg.global_config['media:medium']['max_width'], + mgg.global_config['media:medium']['max_height']) + + self._snap( + "perspective", + "{basename}.perspective.jpg", + [0, self.greatest*-1.5, self.greatest], + size, + project="PERSP") + + def generate_topview(self, size=None): + if not size: + size = (mgg.global_config['media:medium']['max_width'], + mgg.global_config['media:medium']['max_height']) + + self._snap( + "top", + "{basename}.top.jpg", + [self.model.average[0], self.model.average[1], + self.greatest*2], + size) + + def generate_frontview(self, size=None): + if not size: + size = (mgg.global_config['media:medium']['max_width'], + mgg.global_config['media:medium']['max_height']) + + self._snap( + "front", + "{basename}.front.jpg", + [self.model.average[0], self.greatest*-2, + self.model.average[2]], + size) + + def generate_sideview(self, size=None): + if not size: + size = (mgg.global_config['media:medium']['max_width'], + mgg.global_config['media:medium']['max_height']) + + self._snap( + "side", + "{basename}.side.jpg", + [self.greatest*-2, self.model.average[1], + self.model.average[2]], + size) + + def store_dimensions(self): + """ + Put model dimensions into the database + """ + dimensions = { + "center_x": self.model.average[0], + "center_y": self.model.average[1], + "center_z": self.model.average[2], + "width": self.model.width, + "height": self.model.height, + "depth": self.model.depth, + "file_type": self.ext, + } + self.entry.media_data_init(**dimensions) + + +class InitialProcessor(CommonStlProcessor): + """ + Initial processing step for new stls + """ + name = "initial" + description = "Initial processing" + + @classmethod + def media_is_eligible(cls, entry=None, state=None): + """ + Determine if this media type is eligible for processing + """ + if not state: + state = entry.state + return state in ( + "unprocessed", "failed") + + @classmethod + def generate_parser(cls): + parser = argparse.ArgumentParser( + description=cls.description, + prog=cls.name) + + parser.add_argument( + '--size', + nargs=2, + metavar=('max_width', 'max_height'), + type=int) + + parser.add_argument( + '--thumb-size', + nargs=2, + metavar=('max_width', 'max_height'), + type=int) + + return parser + + @classmethod + def args_to_request(cls, args): + return request_from_args( + args, ['size', 'thumb_size']) + + def process(self, size=None, thumb_size=None): + self.common_setup() + self.generate_thumb(thumb_size=thumb_size) + self.generate_perspective(size=size) + self.generate_topview(size=size) + self.generate_frontview(size=size) + self.generate_sideview(size=size) + self.store_dimensions() + self.copy_original() + self.delete_queue_file() + + +class StlProcessingManager(ProcessingManager): + def __init__(self): + super(self.__class__, self).__init__() + self.add_processor(InitialProcessor) -- 2.25.1