From: Aeva Ntsc Date: Mon, 15 Oct 2012 06:36:26 +0000 (-0500) Subject: Added the stl/obj mediatype. X-Git-Url: https://vcs.fsf.org/?a=commitdiff_plain;h=76918e52e078a1b313ce650b4b11e4178dfa8453;p=mediagoblin.git Added the stl/obj mediatype. --- diff --git a/mediagoblin/media_types/stl/__init__.py b/mediagoblin/media_types/stl/__init__.py new file mode 100644 index 00000000..edffc633 --- /dev/null +++ b/mediagoblin/media_types/stl/__init__.py @@ -0,0 +1,27 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from mediagoblin.media_types.stl.processing import process_stl, \ + sniff_handler + + +MEDIA_MANAGER = { + "human_readable": "stereo lithographics", + "processor": process_stl, + "sniff_handler": sniff_handler, + "display_template": "mediagoblin/media_displays/stl.html", + "default_thumb": "images/media_thumbs/video.jpg", + "accepted_extensions": ["obj", "stl"]} diff --git a/mediagoblin/media_types/stl/migrations.py b/mediagoblin/media_types/stl/migrations.py new file mode 100644 index 00000000..f54c23ea --- /dev/null +++ b/mediagoblin/media_types/stl/migrations.py @@ -0,0 +1,17 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +MIGRATIONS = {} diff --git a/mediagoblin/media_types/stl/model_loader.py b/mediagoblin/media_types/stl/model_loader.py new file mode 100644 index 00000000..12a400e2 --- /dev/null +++ b/mediagoblin/media_types/stl/model_loader.py @@ -0,0 +1,153 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + +import struct + + +class ThreeDeeParseError(Exception): + pass + + +class ThreeDee(): + """ + 3D model parser base class. Derrived classes are used for basic + analysis of 3D models, and are not intended to be used for 3D + rendering. + """ + + def __init__(self, fileob): + self.verts = [] + self.average = [0, 0, 0] + self.min = [None, None, None] + self.max = [None, None, None] + self.width = 0 # x axis + self.depth = 0 # y axis + self.height = 0 # z axis + + self.load(fileob) + if not len(self.verts): + raise ThreeDeeParseError("Empyt model.") + + for vector in self.verts: + for i in range(3): + num = vector[i] + self.average[i] += num + if not self.min[i]: + self.min[i] = num + self.max[i] = num + else: + if self.min[i] > num: + self.min[i] = num + if self.max[i] < num: + self.max[i] = num + + for i in range(3): + self.average[i]/=len(self.verts) + + self.width = abs(self.min[0] - self.max[0]) + self.depth = abs(self.min[1] - self.max[1]) + self.height = abs(self.min[2] - self.max[2]) + + + def load(self, fileob): + """Override this method in your subclass.""" + pass + + +class ObjModel(ThreeDee): + """ + Parser for textureless wavefront obj files. File format + reference: http://en.wikipedia.org/wiki/Wavefront_.obj_file + """ + + def __vector(self, line, expected=3): + nums = map(float, line.strip().split(" ")[1:]) + return tuple(nums[:expected]) + + def load(self, fileob): + for line in fileob: + if line[0] == "v": + self.verts.append(self.__vector(line)) + + +class BinaryStlModel(ThreeDee): + """ + Parser for ascii-encoded stl files. File format reference: + http://en.wikipedia.org/wiki/STL_%28file_format%29#Binary_STL + """ + + def __num(self, fileob, hint): + assert hint == "uint" or hint == "real" or hint == "short" + form = None + bits = 0 + if hint == "uint": + form = ". + + +from mediagoblin.db.sql.base import Base + +from sqlalchemy import ( + Column, Integer, Float, ForeignKey) +from sqlalchemy.orm import relationship, backref + + +class StlData(Base): + __tablename__ = "stl__mediadata" + + # The primary key *and* reference to the main media_entry + media_entry = Column(Integer, ForeignKey('core__media_entries.id'), + primary_key=True) + get_media_entry = relationship("MediaEntry", + backref=backref("stl__media_data", cascade="all, delete-orphan")) + + center_x = Column(Float) + center_y = Column(Float) + center_z = Column(Float) + + width = Column(Float) + height = Column(Float) + depth = Column(Float) + + +DATA_MODEL = StlData +MODELS = [StlData] diff --git a/mediagoblin/media_types/stl/processing.py b/mediagoblin/media_types/stl/processing.py new file mode 100644 index 00000000..0a5a24c1 --- /dev/null +++ b/mediagoblin/media_types/stl/processing.py @@ -0,0 +1,107 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import os +import logging + +from mediagoblin import mg_globals as mgg +from mediagoblin.processing import create_pub_filepath, \ + FilenameBuilder + +from mediagoblin.media_types.stl import model_loader + + +_log = logging.getLogger(__name__) +SUPPORTED_FILETYPES = ['stl', 'obj'] + + +def sniff_handler(media_file, **kw): + if kw.get('media') is not None: + name, ext = os.path.splitext(kw['media'].filename) + clean_ext = ext[1:].lower() + + if clean_ext in SUPPORTED_FILETYPES: + _log.info('Found file extension in supported filetypes') + return True + else: + _log.debug('Media present, extension not found in {0}'.format( + SUPPORTED_FILETYPES)) + else: + _log.warning('Need additional information (keyword argument \'media\')' + ' to be able to handle sniffing') + + return False + + +def process_stl(entry): + """ + Code to process an stl or obj model. + """ + + workbench = mgg.workbench_manager.create_workbench() + # Conversions subdirectory to avoid collisions + conversions_subdir = os.path.join( + workbench.dir, 'conversions') + os.mkdir(conversions_subdir) + 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) + + # TODO: generate blender previews + + # 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 + mgg.queue_store.delete_file(queued_filepath) + 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'] = ["mgoblin_static/images/404.png"] + + # 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, + } + entry.media_data_init(**dimensions) + + # clean up workbench + workbench.destroy_self()