diff options
author | Aeva Ntsc <aeva.ntsc@gmail.com> | 2012-10-15 01:36:26 -0500 |
---|---|---|
committer | Christopher Allan Webber <cwebber@dustycloud.org> | 2012-12-03 14:40:47 -0600 |
commit | 76918e52e078a1b313ce650b4b11e4178dfa8453 (patch) | |
tree | e54a3e1dcd91dee2c4a684b0366e420ab6479a18 /mediagoblin | |
parent | 62cc81fb99c6302c15a5843cf9e8727d3f7048c4 (diff) | |
download | mediagoblin-76918e52e078a1b313ce650b4b11e4178dfa8453.tar.lz mediagoblin-76918e52e078a1b313ce650b4b11e4178dfa8453.tar.xz mediagoblin-76918e52e078a1b313ce650b4b11e4178dfa8453.zip |
Added the stl/obj mediatype.
Diffstat (limited to 'mediagoblin')
-rw-r--r-- | mediagoblin/media_types/stl/__init__.py | 27 | ||||
-rw-r--r-- | mediagoblin/media_types/stl/migrations.py | 17 | ||||
-rw-r--r-- | mediagoblin/media_types/stl/model_loader.py | 153 | ||||
-rw-r--r-- | mediagoblin/media_types/stl/models.py | 44 | ||||
-rw-r--r-- | mediagoblin/media_types/stl/processing.py | 107 |
5 files changed, 348 insertions, 0 deletions
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 <http://www.gnu.org/licenses/>. + +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 <http://www.gnu.org/licenses/>. + +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 <http://www.gnu.org/licenses/>. + + +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 = "<I" # little-endian unsigned int + bits = 32 + elif hint == "real": + form = "<i" # little-endian signed int + bits = 32 + elif hint == "short": + form = "<H" # little-endian unsigned short + bits = 16 + return struct.unpack(form, fileob.read(bits/8))[0] + + def __vector(self, fileob): + return tuple([self.__num(fileob, "real") for n in range(3)]) + + def load(self, fileob): + fileob.seek(80) # skip the header + triangle_count = self.__num(fileob, "uint") + for i in range(triangle_count): + self.__vector(fileob) # skip the normal vector + for v in range(3): + # - FIXME - traingle_count IS reporting the correct + # number, but the vertex information appears to be + # total nonsense :( + self.verts.append(self.__vector(fileob)) + self.__num(fileob, "short") # skip the attribute byte count + + +def auto_detect(fileob, hint): + """ + Attempt to divine which parser to use to divine information about + the model / verify the file.""" + + if hint == "obj" or not hint: + try: + return ObjModel(fileob) + except ThreeDeeParseError: + pass + + if hint == "stl" or not hint: + try: + # HACK Ascii formatted stls are similar enough to obj + # files that we can just use the same parser for both. + # Isn't that something? + return ObjModel(fileob) + except ThreeDeeParseError: + pass + try: + # It is pretty important that the binary stl model loader + # is tried second, because its possible for it to parse + # total garbage from plaintext =) + return BinaryStlModel(fileob) + except ThreeDeeParseError: + pass + except MemoryError: + pass + + raise ThreeDeeParseError("Could not successfully parse the model :(") diff --git a/mediagoblin/media_types/stl/models.py b/mediagoblin/media_types/stl/models.py new file mode 100644 index 00000000..d0873176 --- /dev/null +++ b/mediagoblin/media_types/stl/models.py @@ -0,0 +1,44 @@ +# 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 <http://www.gnu.org/licenses/>. + + +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 <http://www.gnu.org/licenses/>. + +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() |