Added initial stl processor
This commit is contained in:
parent
ab64ca3474
commit
77daec9224
@ -14,6 +14,7 @@
|
|||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import argparse
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
@ -21,8 +22,11 @@ import subprocess
|
|||||||
import pkg_resources
|
import pkg_resources
|
||||||
|
|
||||||
from mediagoblin import mg_globals as mgg
|
from mediagoblin import mg_globals as mgg
|
||||||
from mediagoblin.processing import create_pub_filepath, \
|
from mediagoblin.processing import (
|
||||||
FilenameBuilder
|
FilenameBuilder, MediaProcessor,
|
||||||
|
ProcessingManager, request_from_args,
|
||||||
|
get_orig_filename, store_public,
|
||||||
|
copy_original)
|
||||||
|
|
||||||
from mediagoblin.media_types.stl import model_loader
|
from mediagoblin.media_types.stl import model_loader
|
||||||
|
|
||||||
@ -75,49 +79,60 @@ def blender_render(config):
|
|||||||
env=env)
|
env=env)
|
||||||
|
|
||||||
|
|
||||||
def process_stl(proc_state):
|
class CommonStlProcessor(MediaProcessor):
|
||||||
"""Code to process an stl or obj model. Will be run by celery.
|
"""
|
||||||
|
Provides a common base for various stl processing steps
|
||||||
A Workbench() represents a local tempory dir. It is automatically
|
|
||||||
cleaned up when this function exits.
|
|
||||||
"""
|
"""
|
||||||
entry = proc_state.entry
|
|
||||||
workbench = proc_state.workbench
|
|
||||||
|
|
||||||
queued_filepath = entry.queued_media_file
|
def common_setup(self):
|
||||||
queued_filename = workbench.localized_file(
|
# Pull down and set up the original file
|
||||||
mgg.queue_store, queued_filepath, 'source')
|
self.orig_filename = get_orig_filename(
|
||||||
name_builder = FilenameBuilder(queued_filename)
|
self.entry, self.workbench)
|
||||||
|
self.name_builder = FilenameBuilder(self.orig_filename)
|
||||||
|
|
||||||
ext = queued_filename.lower().strip()[-4:]
|
self._set_ext()
|
||||||
if ext.startswith("."):
|
self._set_model()
|
||||||
ext = ext[1:]
|
self._set_greatest()
|
||||||
else:
|
|
||||||
|
def _set_ext(self):
|
||||||
|
ext = self.name_builder.ext
|
||||||
|
|
||||||
|
if not ext:
|
||||||
ext = None
|
ext = None
|
||||||
|
|
||||||
# Attempt to parse the model file and divine some useful
|
self.ext = ext
|
||||||
# information about it.
|
|
||||||
with open(queued_filename, 'rb') as model_file:
|
|
||||||
model = model_loader.auto_detect(model_file, ext)
|
|
||||||
|
|
||||||
# generate preview images
|
def _set_model(self):
|
||||||
greatest = [model.width, model.height, model.depth]
|
"""
|
||||||
|
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()
|
greatest.sort()
|
||||||
greatest = greatest[-1]
|
self.greatest = greatest[-1]
|
||||||
|
|
||||||
def snap(name, camera, width=640, height=640, project="ORTHO"):
|
def copy_original(self):
|
||||||
filename = name_builder.fill(name)
|
copy_original(
|
||||||
workbench_path = workbench.joinpath(filename)
|
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 = {
|
shot = {
|
||||||
"model_path": queued_filename,
|
"model_path": self.orig_filename,
|
||||||
"model_ext": ext,
|
"model_ext": self.ext,
|
||||||
"camera_coord": camera,
|
"camera_coord": camera,
|
||||||
"camera_focus": model.average,
|
"camera_focus": self.model.average,
|
||||||
"camera_clip": greatest*10,
|
"camera_clip": self.greatest*10,
|
||||||
"greatest": greatest,
|
"greatest": self.greatest,
|
||||||
"projection": project,
|
"projection": project,
|
||||||
"width": width,
|
"width": size[0],
|
||||||
"height": height,
|
"height": size[1],
|
||||||
"out_file": workbench_path,
|
"out_file": workbench_path,
|
||||||
}
|
}
|
||||||
blender_render(shot)
|
blender_render(shot)
|
||||||
@ -126,70 +141,139 @@ def process_stl(proc_state):
|
|||||||
assert os.path.exists(workbench_path)
|
assert os.path.exists(workbench_path)
|
||||||
|
|
||||||
# copy it up!
|
# copy it up!
|
||||||
with open(workbench_path, 'rb') as rendered_file:
|
store_public(self.entry, keyname, workbench_path, filename)
|
||||||
public_path = create_pub_filepath(entry, filename)
|
|
||||||
|
|
||||||
with mgg.public_store.get_file(public_path, "wb") as public_file:
|
def generate_thumb(self, thumb_size=None):
|
||||||
public_file.write(rendered_file.read())
|
if not thumb_size:
|
||||||
|
thumb_size = (mgg.global_config['media:thumb']['max_width'],
|
||||||
|
mgg.global_config['media:thumb']['max_height'])
|
||||||
|
|
||||||
return public_path
|
self._snap(
|
||||||
|
"thumb",
|
||||||
thumb_path = snap(
|
|
||||||
"{basename}.thumb.jpg",
|
"{basename}.thumb.jpg",
|
||||||
[0, greatest*-1.5, greatest],
|
[0, self.greatest*-1.5, self.greatest],
|
||||||
mgg.global_config['media:thumb']['max_width'],
|
thumb_size,
|
||||||
mgg.global_config['media:thumb']['max_height'],
|
|
||||||
project="PERSP")
|
project="PERSP")
|
||||||
|
|
||||||
perspective_path = snap(
|
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",
|
"{basename}.perspective.jpg",
|
||||||
[0, greatest*-1.5, greatest], project="PERSP")
|
[0, self.greatest*-1.5, self.greatest],
|
||||||
|
size,
|
||||||
|
project="PERSP")
|
||||||
|
|
||||||
topview_path = snap(
|
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",
|
"{basename}.top.jpg",
|
||||||
[model.average[0], model.average[1], greatest*2])
|
[self.model.average[0], self.model.average[1],
|
||||||
|
self.greatest*2],
|
||||||
|
size)
|
||||||
|
|
||||||
frontview_path = snap(
|
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",
|
"{basename}.front.jpg",
|
||||||
[model.average[0], greatest*-2, model.average[2]])
|
[self.model.average[0], self.greatest*-2,
|
||||||
|
self.model.average[2]],
|
||||||
|
size)
|
||||||
|
|
||||||
sideview_path = snap(
|
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",
|
"{basename}.side.jpg",
|
||||||
[greatest*-2, model.average[1], model.average[2]])
|
[self.greatest*-2, self.model.average[1],
|
||||||
|
self.model.average[2]],
|
||||||
|
size)
|
||||||
|
|
||||||
## Save the public file stuffs
|
def store_dimensions(self):
|
||||||
model_filepath = create_pub_filepath(
|
"""
|
||||||
entry, name_builder.fill('{basename}{ext}'))
|
Put model dimensions into the database
|
||||||
|
"""
|
||||||
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 = {
|
dimensions = {
|
||||||
"center_x" : model.average[0],
|
"center_x": self.model.average[0],
|
||||||
"center_y" : model.average[1],
|
"center_y": self.model.average[1],
|
||||||
"center_z" : model.average[2],
|
"center_z": self.model.average[2],
|
||||||
"width" : model.width,
|
"width": self.model.width,
|
||||||
"height" : model.height,
|
"height": self.model.height,
|
||||||
"depth" : model.depth,
|
"depth": self.model.depth,
|
||||||
"file_type" : ext,
|
"file_type": self.ext,
|
||||||
}
|
}
|
||||||
entry.media_data_init(**dimensions)
|
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)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user